22.11.2014 Views

Symulacje komputerowe zjawisk fizycznych z zakresu mechaniki

Symulacje komputerowe zjawisk fizycznych z zakresu mechaniki

Symulacje komputerowe zjawisk fizycznych z zakresu mechaniki

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

Temat: <strong>Symulacje</strong> <strong>komputerowe</strong> <strong>zjawisk</strong> <strong>fizycznych</strong> z <strong>zakresu</strong> <strong>mechaniki</strong>.<br />

Spis treści<br />

1. Wstęp................................................................................................................................................2<br />

2. Modelowanie obiektów (część 1).....................................................................................................4<br />

Okno 3D......................................................................................................................................4<br />

Obiekty, nazwy i atrybuty...........................................................................................................5<br />

Opis obiektu w VPython.............................................................................................................5<br />

Standardowe obiekty w VPython................................................................................................6<br />

Modelowanie krzywych..............................................................................................................7<br />

Napisy.......................................................................................................................................10<br />

Rysowanie wykresów................................................................................................................12<br />

3. Ruch pojedynczego nieodkształcalnego ciała (punktu materialnego)............................................14<br />

Symulacja ruchu pojedynczego nieodkształcalnego ciała (punktu materialnego)....................14<br />

Rzut w jednorodnym polu grawitacyjnym................................................................................16<br />

Statek w wodzie........................................................................................................................18<br />

Rzut z uwzględnieniem oporu powietrza i wiatru....................................................................22<br />

4. Modelowanie obiektów (część 2)...................................................................................................25<br />

Budowa obiektów od podstaw..................................................................................................25<br />

Relacjonowanie z kilku kamer jednocześnie............................................................................27<br />

Wykresy – znakowanie danych.................................................................................................31<br />

5. Ruch bryły sztywnej.......................................................................................................................32<br />

Wyznaczanie drogi hamowania samochodu.............................................................................32<br />

Ruch postępowo obrotowy na równi........................................................................................34<br />

Poślizg kuli przy pchnięciu techniką bilardową.......................................................................35<br />

6. Modelowanie obiektów (część 3)...................................................................................................37<br />

Obiekt zmieniający geometrię w czasie....................................................................................37<br />

Wykresy – tworzenie histogramu..............................................................................................40<br />

7. Wiele ciał w przestrzeni..................................................................................................................43<br />

Ruchy planet.............................................................................................................................43<br />

Ruch ciała wokół nieskończenie ciężkiej planety................................................................43<br />

Prawo pól Keplera................................................................................................................47<br />

Ruch ciała w polu dowolnej siły centralnej..........................................................................47<br />

Układ podwójny planet........................................................................................................48<br />

Ruch wielu planet z uwzględnianiem wzajemnego oddziaływania grawitacyjnego między<br />

sobą.......................................................................................................................................49<br />

8. Wybrane zagadnienia fizyczne.......................................................................................................52<br />

Sprężyny i tłumienie drgań.......................................................................................................52<br />

Waga..........................................................................................................................................56<br />

Wahadło torsyjne.......................................................................................................................57<br />

Zderzenia ciał z przeszkodami - zderzenie kul w przestrzeni...................................................58<br />

Model gazu doskonałego. ........................................................................................................66<br />

Symulacja odbijającej się kulki wewnątrz pudełka..............................................................66<br />

Symulacja zachowania się gazu doskonałego w zamkniętym naczyniu..............................68<br />

Kinetyczny model gazu doskonałego. .................................................................................79<br />

9. Zakończenie....................................................................................................................................89<br />

10. Bibliografia...................................................................................................................................90<br />

1


1. Wstęp<br />

Podziwiając widoki krajobrazu ze szczytów gór, nurkując w bezkresnych głębinach oceanu,<br />

lub spoglądając wieczorem na rozgwieżdżone niebo możemy zauważyć, że otaczający nas świat<br />

daje nam możliwość obserwacji i badań praktycznie nieograniczonej liczby <strong>zjawisk</strong> <strong>fizycznych</strong>. Są<br />

to zarówno <strong>zjawisk</strong>a bezpośrednio nam dostępne i doświadczalne przez nasze zmysły na przykład<br />

związane z ciepłem, światłem, ruchem ciał, jak też <strong>zjawisk</strong>a, które badać możemy pośrednio za<br />

pomocą przyrządów stanowiących niejako przedłużenie naszych zmysłów. Do tej grupy zaliczyć<br />

możemy <strong>zjawisk</strong>a astrofizyczne obserwowane w naszej części wszechświata, jak i <strong>zjawisk</strong>a<br />

z mikroświata atomów.<br />

Zgłębianiem tych <strong>zjawisk</strong> zajmują się naukowcy reprezentujący różne dziedziny nauki.<br />

Jedną z fundamentalnych dziedzin jest mechanika. Przedmiotem tej nauki jest badanie i opisywanie<br />

ruchów ciał.<br />

Początkowo przedmiotem zainteresowań mechaników był ruch pojedynczego,<br />

nieodkształcalnego ciała, które obecnie zwykło się nazywać zagadnieniem punktu materialnego.<br />

Zagadnienie to w naturalny sposób uogólniono na zagadnienie wielu ciał, czyli oddziałujących ze<br />

sobą punktów materialnych.<br />

Jako kolejny etap w rozwoju <strong>mechaniki</strong> należy wymienić wprowadzenie do rozważań ciał<br />

rozciągłych, ale sztywnych, czyli takich których kształt podczas ruchu nie ulega zmianom.<br />

Rozciągłość obiektu ma tu fundamentalne znaczenie a pozycja takiego ciała jest określona przy<br />

pomocy sześciu niezależnych parametrów.<br />

Jeżeli przedmiotem naszego zainteresowania będą ciała, które oprócz ruchu postępowego<br />

i obrotowego będą wykazywały ruchy wewnętrzne jednych części względem innych, problem ruchu<br />

takiego ciała ulegnie znacznym komplikacjom. Zagadnieniami tego typu zajmuje się mechanika<br />

ośrodków ciągłych.<br />

Niewątpliwie możliwości rozwiązywania wyżej wymienionych zagadnień<br />

z wykorzystaniem techniki <strong>komputerowe</strong>j wpłynęły z kolei stymulująco na obserwowany do dziś<br />

intensywny rozwój teoretycznych i eksperymentalnych badań z <strong>zakresu</strong> <strong>mechaniki</strong>. Uzyskiwanie<br />

rozwiązań złożonych, wielowymiarowych, bardzo często nieliniowych zagadnień <strong>mechaniki</strong>, lub<br />

ogólniej – fizyki matematycznej, nazywa się często symulacją komputerową. Do określenia zespołu<br />

czynności związanych z rozwiązywaniem równań fizyki matematycznej używa się<br />

pojęcia - eksperyment komputerowy, aby podkreślić dodatkowo, że rozwiązanie dokonywane jest<br />

z wykorzystaniem komputera.<br />

Praca ma na celu zaznajomić czytelnika z elementarnymi zagadnieniami <strong>komputerowe</strong>go<br />

modelowania i symulowania dynamicznych <strong>zjawisk</strong> <strong>fizycznych</strong> z <strong>zakresu</strong> <strong>mechaniki</strong> punktów<br />

materialnych, brył sztywnych oraz ciał stałych. <strong>Symulacje</strong> oparte są o interpretowany, obiektowy,<br />

wysokopoziomowy język programowania którym jest Python, a wizualizacja oparta została<br />

o bibliotekę graficzną Visual.<br />

Grafika trójwymiarowa zrobiła ogromną karierę. Trudno dziś znaleźć komputer, który nie<br />

byłby wyposażony w kartę graficzną z akceleratorem 3D. Nawet te najtańsze, wbudowane w płytę<br />

główną, dysponują ogromnymi możliwościami. Przyczyną takiego stanu rzeczy jest w dużym<br />

stopniu popularność gier komputerowych – nie oszukujmy się, praca z pakietem biurowym, bazą<br />

danych czy kompilatorem rzadko kiedy daje okazję do podziwiania realistycznych<br />

trójwymiarowych obrazów. No chyba, że pracujemy nad własną grą, ale w dzisiejszych czasach gry<br />

są tworzone przez wieloosobowe zespoły zawodowców, a prace nad nią zwykle trwają kilka lat.<br />

Grafikę 3D można wykorzystać do celów edukacyjnych, ale trudno jest oczekiwać, że<br />

nauczyciel fizyki, chcąc przeprowadzić symulację komputerową jakiegoś <strong>zjawisk</strong>a, spędzi więcej<br />

czasu nad częścią swojego programu odpowiedzialną za grafikę trójwymiarową niż nad częścią<br />

2


zeczywiście dotyczącą interesującego go <strong>zjawisk</strong>a. Również nie wydaje się prawdopodobne,aby<br />

uczeń stawiający pierwsze kroki w programowaniu napisał silnik 3D. W takich sytuacjach pomocny<br />

może się okazać Python. Jest to język warty poznania – sprawdza się nie tylko w nauce<br />

programowania, ale może się okazać niezastąpiony w codziennej pracy. Wystarczy powiedzie, że<br />

jest on intensywnie używany przez inżynierów z tak potężnych firm jak Google czy Industrial<br />

Light&Magic.<br />

3


2. Modelowanie obiektów (część 1)<br />

VPython to język Python plus graficzny moduł 3D zwany „Visual” napisany przez Davida<br />

Scherera. W tym rozdziale poznamy niektóre podstawowe możliwości tego modułu, pozwalające<br />

nam budować obiekty, nadawać im parametry (także fizyczne), oraz wprawiać je w ruch. Animacje<br />

te staną się w przyszłości podstawą pozwalającą modelować i symulować <strong>zjawisk</strong>a fizyczne. Na<br />

początku jednak musimy poznać kilka podstawowych funkcji biblioteki Visual.<br />

Okno 3D<br />

Kiedy używamy VPython'a wyświetlane okno pokazuje nam obiekty w przestrzeni<br />

trójwymiarowej. Punkt (0,0,0) kartezjańskiego układu współrzędnych jest w centrum<br />

wyświetlanego okna. Dodatnie wartości współrzędnych x przebiegają w prawo, y<br />

przebiegają do góry, natomiast z wychodzą z płaszczyzny ekranu do użytkownika.<br />

Wartości x , y , i z mogą być wyrażane w dowolnych jednostkach. Okno 3D dopasuje się<br />

automatycznie skalując obiekty i dobierając odpowiednio zakres wyświetlanej sceny. Możemy, na<br />

przykład, zainicjować sferę o promieniu r=1 ⋅10 −15 m reprezentującą nukleon, lub sferę [G]<br />

o promieniu r=1 ⋅10 6 m przedstawiającą planetę, jednak nie ma sensu umieszczać obu tych<br />

obiektów w tej samej scenie.<br />

Wpisując poniższy skrypt używając IDLE (interactive development environment ) a następnie<br />

zapisując i uruchamiając go (poprzez naciśnięcie F5) zobaczyć możemy przykładowe okno 3D<br />

zawierające obiekty.<br />

from visual import *<br />

scene.background=(1.0,1.0,1.0)<br />

p=(0.0,0.0,0.0)<br />

strzalkax = arrow(pos=p, axis=(1,0,0),<br />

shaftwidth=1, color=color.red)<br />

strzalkay = arrow(pos=p, axis=(0,1,0),<br />

shaftwidth=1, color=color.green)<br />

strzalkaz = arrow(pos=p, axis=(0,0,1),<br />

shaftwidth=1, color=color.blue)<br />

pudelko=box(pos=vector(1.0,0.0,0.5),<br />

size=(1.0,0.5,0.2),color=(1.0,0.0,0.3))<br />

kula=sphere(pos=vector(2.5,0.0,0.0), radius=0.6,<br />

color=(0.3,0.8,0.4))<br />

Rysunek 1: Przykładowe obiekty<br />

wyświetlane w oknie 3D<br />

Widok wyświetlanej sceny możemy zmienić naciskając prawy przycisk myszy i przesuwając ją<br />

w górę, dół lub prawo, lewo. Zakres wyświetlanej sceny możemy zmienić klikając środkowym<br />

przyciskiem myszy i przesuwając ją w górę – zmniejszamy zakres widocznej sceny, lub w dół<br />

- powiększamy zakres.<br />

4


Obiekty, nazwy i atrybuty.<br />

Obiekty graficzne [G], które tworzymy, takie jak sphere lub box, są ciągle wyświetlane podczas<br />

gdy program jest uruchomiony. Aby móc odwołać się później do parametrów danego obiektu<br />

musimy nadać mu nazwę (taką jak pudelko i kula z przykładu u góry). Wszystkie obiekty<br />

posiadają atrybuty czyli właściwości takie jak kula.pos (pozycja kuli), kula.color (kolor kuli) i<br />

promień lub inne parametry rozmiarów obiektu. Jeżeli zmienimy atrybut pozycji lub koloru<br />

obiektu, Visual automatycznie zmieni pozycję lub kolor w oknie 3D.<br />

Możemy ustawiać wartości atrybutów w osobnym pliku (skrypcie służącym do definiowania<br />

obiektów), możemy również modyfikować atrybuty dynamicznie w trakcie wykonywania<br />

programu. Ponadto możemy tworzyć własne atrybuty. I tak na przykład kuli, którą nazwiemy<br />

„piłka”, po nadaniu parametrów takich jak pozycja czy kolor można dodać jej fizyczne atrybuty<br />

takie jak masa (pilka.masa) , prędkość (pilka.predkosc) lub moment bezwładności itp.<br />

Opis obiektu w VPython<br />

Poniżej znajduje się kompletny skrypt pokazujący jak stworzyć bryłę i zmieniać jej parametry.<br />

W tym przykładzie posłużymy się obiektem typu cylinder, który zostanie przypisany zmiennej<br />

bryla01. Posłuży on do dokładnego omówienia atrybutów obiektu.<br />

1 from visual import *<br />

2 scene.background=(1.0,1.0,1.0)<br />

3 p=(0.0,0.0,0.0)<br />

4 k=0.01<br />

5 bryla01 = cylinder(pos=(0.0,1.0,0.0),length=2.0,axis=(1,0,0))<br />

6 bryla01.radius=0.3<br />

7 bryla01.color=(0.75,0.95,0.05)<br />

8 strzalkax = arrow(pos=p, axis=(1,0,0), shaftwidth=k, color=color.red)<br />

9 strzalkay = arrow(pos=p, axis=(0,1,0), shaftwidth=k, color=color.green)<br />

10 strzalkaz = arrow(pos=p, axis=(0,0,1), shaftwidth=k, color=color.blue)<br />

Należy się teraz małe wyjaśnienie co oznaczają<br />

poszczególne linie kodu.<br />

Linia 1. To instrukcja dla programu by wgrać do pamięci<br />

bibliotekę Visual. Każdy nowo napisany skrypt<br />

powinien zaczynać się od tej linii.<br />

Linia 2. W tej linii został określony kolor tła przy pomocy<br />

schematu R,G,B. W tym schemacie maksymalne<br />

wartości R,G,B to 1.0,1 .0,1.0 - kolor biały,<br />

kolor czarny przyjmuje minimalne wartości<br />

R,G,B czyli 0.0 ,0.0 ,0 .0<br />

Linia 3. Zmiennej p przypisujemy krotkę 3 pozycyjną [1]<br />

Linia 4. Zmiennej k przypisujemy wartość 0.01<br />

Linia 5. Tutaj zmiennej o nazwie bryla01 przypisujemy obiekt typu cylinder. W VPython<br />

atrybuty każdego obiektu mogą być zdefiniowane w nawiasach. Jeżeli w tym miejscu<br />

ominiemy jakieś atrybuty, obiektowi zostaną przypisane ich wartości domyślne. Więcej<br />

informcji na temat listy parametrów domyślnych dla danego obiektu można uzyskać w<br />

5<br />

Rysunek 2: Obiekt typu cylinder


podręczniku systemowym. Pierwszym określonym atrybutem jest pos (pozycja środka<br />

jednego końca cylindra – w naszym przypadku znajduje się w punkcie x=0.0 ,<br />

y=1.0 i z=0.0 , czasami parametr ten bywa określany wektorem translacji i<br />

wówczas interpretuje się go jako przesunięcie od początku układu współrzędnych do<br />

punktu pos), kolejnym parametrem jest length czyli długość obiektu oraz axis czyli<br />

parametr określający oś na której leży obiekt. Jest to po prostu kierunek wyznaczony przez<br />

początek układu współrzędnych oraz axis<br />

Linia 6. Zmieniamy atrybut promienia obiektu bryla01 z wartości domyślnej na wartość równą 0.3<br />

Linia 7. Zmieniamy atrybut koloru używając schematu kolorów typu RGB (Red, Green, Blue).<br />

Linia 8,9 i 10 Zmiennym strzalkax, strzalkay i strzalkaz przypisujemy obiekt typu arrow<br />

(strzałka) , każda ze strzałek posiada inny kolor i jest skierowana wzdłuż innej osi układu<br />

współrzędnych<br />

Zatem cylinder leży wzdłuż osi x i posada długość równą 2 , więc środek drugiego końca<br />

cylindra znajduje się w punkcie 2.0,1.0 ,0.0 . Możemy modyfikować pozycję cylindra po tym<br />

jak został stworzony, zadając mu nową pozycję zauważyć możemy, że natychmiast przesunie się w<br />

zadane miejsce (wystarczy dopisać tekst na końcu skryptu: bryla01.pos=(0.0,3.0,0.0) )<br />

Jeżeli stworzymy obiekt taki jak cylinder, lecz nie przypiszemy go zmiennej nie będziemy mogli<br />

zmieniać parametrów obiektu. Nie ma to znaczenia w przypadku gdy parametry obiektu nie będą<br />

nigdy zmieniane.<br />

Standardowe obiekty w VPython<br />

Oprócz kostek i kulek biblioteka oferuje nam jeszcze wiele kształtów takich jak stożki czy torusy<br />

a także pozwala konstruować krzywe z podanych punktów [G]. Wszystkie obiekty przyjmują<br />

parametr pos, ustawiający ich miejsce w przestrzeni. Wektor podawany jako axis pozwala ustawić<br />

obiekty nierównolegle do osi układu współrzędnych. Te obiekty w przypadku których ma to sens,<br />

pozwalają na podanie promienia radius. Wszystkie przyjmują parametr color. Pozycję obiektów<br />

można odczytywać i zmieniać już po ich stworzeniu, odwołując się do atrybutu pos lub do<br />

poszczególnych współrzędnych x,y,z:<br />

szescian.pos = (1.0,2.0,0.0)<br />

szesian.x = 1.0<br />

Podobnie podczas działania programu możemy dowolnie<br />

zmieniać wymiary obiektów (height, width, length,<br />

radius). Obiekty także możemy obracać, wywołując dla nich<br />

metodę rotate(os,kat,punkt), której podajemy kąt i oś,<br />

wokół której chcemy obiekt obracać (oś jest wyznaczona<br />

przez punkt i wektor).<br />

Przykładowy skrypt ilustrujący obrót obiektu:<br />

from visual import *<br />

scene.range = 2.0<br />

stozek = cone(color=(0.0,0.4,1.0), radius = 0.4)<br />

punkt = (0.8,0.0,0.0)<br />

a=0.01<br />

b=a<br />

#-----------------<br />

6<br />

Rysunek 3: Animacja obrotu obiektu<br />

wokół wybranego punktu i wybranej osi


p=(0.0,0.0,0.0)<br />

k=0.01<br />

strzalkax = arrow(pos=p, axis=(1,0,0), shaftwidth=k, color=color.red)<br />

strzalkay = arrow(pos=p, axis=(0,1,0), shaftwidth=k, color=color.green)<br />

strzalkaz = arrow(pos=p, axis=(0,0,1), shaftwidth=k, color=color.blue)<br />

#-----------------<br />

while b


przykład moja_krzywa.pos[0] jest pozycją pierwszego punktu w moja_krzywa.<br />

Możemy również otrzymać krzywą bezpośrednio z listy współrzędnych [1]. Listę taką tworzymy<br />

podobnie jak krotkę (sekwencję) zamykając listę współrzędnych w okrągłych nawiasach. I tak na<br />

przykład aby narysować kwadrat wystarczy napisać:<br />

kwadrat = curve(pos=[(0,0),(0,1),(1,1),(1,0),(0,0)])<br />

Oczywiście (1,1) jest skrótem (1,1,0). Jednakże, nie możemy mieszać ze sobą punktów 2D oraz<br />

3D w jednej liście.<br />

Krzywa posiada grubość, definiowaną poprzez parametr radius. Zatem średnica krzywej jest<br />

równa podwójnemu promieniowi.<br />

krzywa=curve(pos=[(0,0,0), (1,0,0)], radius=0.05)<br />

Domyślny promień krzywej wynosi 0, wtedy jest rysowana bardzo cienka krzywa. Jeżeli podamy<br />

bardzo małą niezerową wartość, krzywa stanie się prawie niewidoczna dlatego zaleca się używać<br />

wartości zero ponieważ wtedy średnica krzywej jest domyślnie równa jednemu pikselowi.<br />

Krzywą narysować możemy również z wygenerowanej listy punktów, w tym celu możemy<br />

posłużyć się funkcją arange( [start,] stop[, step]) , która zwraca sekwencję liczb. Poniżej<br />

przedstawiony jest fragment skryptu pozwalającego narysować sprężynę.<br />

# Rysowanie sprężyny<br />

c = curve( x = arange(0,20,0.1) )<br />

c.y = sin( 2.0*c.x )<br />

c.z = cos( 2.0*c.x )<br />

Parametryczne rysowanie także nie powinno sprawiać problemu. Jak wspomniane było wczśniej<br />

niektóre atrybuty, na przykład color mogą być różne dla każdego punktu w krzywej. Kiedy<br />

będziemy chcieli nagle zmienić kolor krzywej, po prostu należy dodać kolejny punkt w miejscu<br />

zmiany koloru, lecz o innym kolorze.<br />

from visual import *<br />

c = curve( pos=[(0,0,0), (1,0,0)], color=color.red )<br />

c.append( pos=(1,1,0) ) # dodanie punktu w kolorze czerwonym<br />

c.append( pos=(1,1,0), color=color.blue) # ten sam punkt, lecz kolor niebieski<br />

c.append( pos=(0,1,0) ) # dodanie punktu w kolorze niebieskim<br />

Poniżej przedstawiony jest pełny skrypt obrazujący niektóre z wymienionych operacji na krzywych.<br />

Pierwsza krzywa (krzywa1) jest rysowana z punktów które mogą pochodzić na przykład z obliczeń,<br />

kolejna krzywa (krzywa2) zawiera jedynie dwa punkty w kolorze szarym. Do tak stworzonej<br />

krzywej dodajemy punkty przy pomocy funkcji append(). Ostatnia krzywa (krzywa3) jest<br />

rysowana parametrycznie. Do barwienia krzywej użyto pośrednio schematu HSV (Hue, Saturation,<br />

Value – barwa, nasycenie, wartość), który następnie zamieniony został na schemat RGB.<br />

from visual import *<br />

scene.background=(1.0,1.0,1.0)<br />

a=(0,1,0)<br />

b=(1,1,0)<br />

c=(2,2,0)<br />

d=(3,2,0)<br />

krzywa1=curve(pos=[a,b,c,d],color=(0.5,0.5,0.5), radius=0.05)<br />

krzywa2=curve(pos=[(0,0,0), (1,0,0)],color=(0.5,0.5,0.5), radius=0.05)<br />

# dodanie punktow do krzywej<br />

8


krzywa2.append(pos=(2,1,0), color=color.green)<br />

krzywa2.append(pos=(3,1,0), color=color.yellow)<br />

# krzywa rysowana parametrycznie<br />

n = 16 # ilosc pelnych cykli (zwojow) sprezyny na jednostke dlugosci<br />

r = 2 # promien sprezyny :)<br />

rd = 0.05 # promien drutu<br />

krzywa3=curve(pos=[], radius=0.05)<br />

for i in arange(0.,1.,0.001):<br />

k=2*(1/(i+1)*i)<br />

#print k<br />

c=(k,1,1)<br />

krzywa3.append(pos=(-4+10*i,-2+i*r*cos(n*2*math.pi*i),<br />

i*r*sin(n*2*math.pi*i)), color=color.hsv_to_rgb(c))<br />

#----- uklad trzech strzalek<br />

strzalkax = arrow(pos=(-2,-2,-2), axis=(1,0,0), shaftwidth=1, color=color.red)<br />

strzalkay = arrow(pos=(-2,-2,-2), axis=(0,1,0), shaftwidth=1, color=color.green)<br />

strzalkaz = arrow(pos=(-2,-2,-2), axis=(0,0,1), shaftwidth=1, color=color.blue)<br />

#-----<br />

Jednym z wielu sposobów zastosowania<br />

krzywych jest wizualizacja trajektorii<br />

punktu materialnego lub specyficznych<br />

aspektów ruchu bryły sztywnej. Kolejny<br />

skrypt przedstawia animację kuli<br />

poruszającej się w przestrzeni. Za każdym<br />

razem gdy kula podąża w kierunku<br />

rosnących wartości y do krzywej<br />

(trajectory) jest przypisywany punkt o<br />

współrzędnych równych położeniu kuli.<br />

Zatem poruszająca się kula pozostawi za<br />

sobą ślad w postaci krzywej. Dodatkowo na<br />

płaszczyznach xy, yz, oraz zx są rysowane<br />

rzuty tej trajektorii.<br />

from visual import *<br />

#scene.background=(1.0,1.0,1.0)<br />

scene.range = 3.0<br />

Rysunek 4: Wizualizacja krzywych.<br />

rc=0 # promien krzywych<br />

col=(0.1,0.7,0.7)<br />

trajectory=curve(pos=[],color=col, radius=rc) #trajektoria kuli xyz<br />

c1=curve(pos=[],color=(0.8,0.5,0.1), radius=rc) #rzut na plaszczyzne xy<br />

c2=curve(pos=[],color=(0.2,0.6,0.2), radius=rc) #rzut na plaszczyzne yz<br />

c3=curve(pos=[],color=(0.9,0.1,0.9), radius=rc) #rzut na plaszczyzne zx<br />

a=1.0<br />

b=1.0<br />

c=0.0<br />

kula=sphere(pos=vector(a,c,b), radius=0.05, color=col)<br />

#p1,p2,p3 - prowadnice<br />

szary=(0.7,0.7,0.7)<br />

p1=curve(pos=[(kula.pos),(kula.pos[0],kula.pos[1],0.0)],color=szary, radius=rc)<br />

p2=curve(pos=[(kula.pos),(kula.pos[0],0.0,kula.pos[2])],color=szary, radius=rc)<br />

p3=curve(pos=[(kula.pos),(0.0,kula.pos[1],kula.pos[2])],color=szary, radius=rc)<br />

r = 0.6 # promien okregu<br />

#----- uklad trzech strzalek<br />

p=(0.0,0.0,0.0)<br />

k=0.01<br />

strzalkax = arrow(pos=p, axis=(1,0,0), shaftwidth=k, color=color.red)<br />

9


strzalkay = arrow(pos=p, axis=(0,1,0), shaftwidth=k, color=color.green)<br />

strzalkaz = arrow(pos=p, axis=(0,0,1), shaftwidth=k, color=color.blue)<br />

#-----<br />

n=0<br />

f=5 # ilosc cykli do pokonania<br />

i=0.0<br />

while nn:<br />

kula.pos=(a,c,b)<br />

i=0.0<br />

c1.pos=[]<br />

c2.pos=[]<br />

c3.pos=[]<br />

trajectory.pos=[]<br />

Rysunek 5: Trajektoria punktu<br />

materialnego poruszającego się w<br />

przestrzeni.<br />

Napisy<br />

W obiektach typu label możemy wyświetlać tekst.<br />

Tekst ten umieszczony jest w ramce, która z kolei<br />

jest połączona linią z wyznaczonym przez nas<br />

punktem.<br />

Na dołączonym skrypcie obiekt typu sphere<br />

reprezentuje Ziemię. Pozycja obiektu tekstowego<br />

jest równa pozycji kuli. Gdziekolwiek będzie<br />

umieszczona kula, podąży za nią etykietka z<br />

napisem „Ziemia”, oprócz tego długość linii<br />

(parametr line) jest tak dobrana, ze kończy się na<br />

powierzchni kuli.<br />

Rysunek 6: Diagram przedstawiający parametry<br />

obiektu typu label.<br />

10


ziemia=sphere(pos=vector(1.5,0.0,0.0), radius=0.6, color=(0.3,0.6,0.9))<br />

napisZiemia = label(pos=ziemia.pos, text='Ziemia',color=(0.7, 0.2, 0.7),<br />

xoffset=20, yoffset=16, space=ziemia.radius,<br />

height=28, border=6,linecolor=(1.0, 0.0, 0.2),<br />

opacity=0.3,font="Arial Black")<br />

Unikalne cechy tego obiektu pozwalają nam kontrolować parametry tekstu wyrażone w pikselach<br />

niezależnie od parametrów sceny. I tak na przykład, wysokość tekstu jest podawana w pikselach,<br />

niezależnie od tego jaki będzie zakres sceny (powiększenie) oraz orientacja kamery w przestrzeni,<br />

wysokość tekstu pozostanie niezmienna oraz zwrócona zawsze w kierunku kamery. Cecha ta<br />

sprawia, że napis pozostaje czytelny niezależnie od tego jak daleko znajduje się od kamery (pod<br />

warunkiem, że będzie umieszczony w polu widzenia kamery).<br />

Oto pozostałe atrybuty obiektu tekstowego:<br />

pos; x,y,z<br />

punkt rozpoczynający rysowanie etykietki z tekstem<br />

xoffset, yoffset składowe x oraz y parametru line wyrażone w pikselach (patrz diagram)<br />

text<br />

height<br />

color<br />

tekst który będzie wyświetlany, na przykład „Ziemia”<br />

(Istnieje możliwość wyświetlania napisów w kilku liniach. W tym celu jako<br />

znak enter używa się napisu „\n” na przykład<br />

label.text=”Trzy\nlinie\ntekstu”)<br />

wysokość tekstu wyrażona w pikselach<br />

kolor tekstu<br />

opacity nieprzezroczystość tła parametru box, domyślna wartość wynosi 0.66<br />

border<br />

box=1<br />

line=1<br />

linecolor<br />

space<br />

font<br />

(0 całkowicie przezroczysty, 1 nieprzezroczysty, dlaobiektów znajdujących<br />

się za box)<br />

odległość tekstu od otaczającej ramki wyrażona w pikselach<br />

jeżeli box powinien być rysowany (wartość ta jest przyjmowana domyślnie),<br />

w innym przypadku 0<br />

jeżeli linia od box do pos powinna być rysowana (wartość domyślna),<br />

w innym przypadku należy podać wartość 0<br />

kolor linii line oraz ramki otaczającej tekst (box)<br />

odległość o promieniu równym space od punktu pos wokół którego linia<br />

łącząca box i pos nie jest rysowana<br />

Dodatkowy parametr charakteryzujący rodzaj czcionki jaką ma być<br />

wyświetlany napis, na przykład „Arial Black”. Podawanie tego parametru nie<br />

jest obowiązkowe.<br />

11


Rysowanie wykresów<br />

Ważnym elementem przy analizowaniu dużych ilości danych jest możliwość umieszczenia<br />

wyników, na przykład pochodzących z symulacji, na wykresach. W tym paragrafie zostanie opisane<br />

w jaki sposób dane umieścić na wykresach 2D ( two-dimensonal).<br />

Importując moduł graficzny visual.graph otrzymujemu dostęp do wszystkich obiektów biblioteki<br />

Visual a także dostęp do modułu odpowiedzialnego za rysowanie wykresów [G].<br />

from visual.graph import * # import modułu odpowiedzialnego za rysowanie<br />

wykresów<br />

Istotną cechą tego modułu jest możliwość automatycznego skalowania wykresów. Powoduje to, że<br />

zarówno skala na osiach rzędnych i odciętych a także zakres ich wartości mogą być dobierane<br />

automatycznie bez ingerencji z zewnątrz. Wykres jest zatem dopasowany do rozmiarów okna w<br />

którym jest wyświetlany. Można to zaobserwować uruchamiając poniższy skrypt.<br />

from visual import *<br />

from visual.graph import * # import modulu rysujacego wykresy<br />

funkcja_1 = gcurve(color=(0.1,0.9,0.1))<br />

for x in arange(0., 10.1, 0.01): # x przebiega wartosci od 0 do 10<br />

funkcja_1.plot(pos=(x,3*cos(1.2*x) + sin(20.*x)))<br />

Połączenie poszczególnych punktów pomiarowych przy pomocy łamanej (gcurve) jest tylko<br />

jednym z kilku rodzajów rysowania wykresów. Inne opcje to punkty rozrzucone bezładnie (gdots),<br />

pionowe słupki (gvbars), poziome słupki (ghbars), oraz dane posortowane i powiązane ze sobą<br />

jako pionowe słupki tzw. histogramy.<br />

Kiedy tworzymy jeden z tego typu obiektów, możemy wyszczególnić kolor jakim ma być rysowany<br />

wykres. Dla pionowych i poziomych słupków możemy również określić tak zwany parametr<br />

„delta” czyli po prostu szerokość słupka (wartością domyślną jest delta=1)<br />

Istnieje możliwość rysowania więcej niż jednego wykresu w jednym oknie:<br />

from visual import *<br />

from visual.graph import * # import modulu rysujacego wykresy<br />

funkcja_1 = gcurve(color=(0.1,0.9,0.1))<br />

funkcja_2 = gvbars(delta=0.03, color=(0.9,0.1,0.5))<br />

for x in arange(0., 10.1, 0.01): # x przebiega wartosci od 0 do 10<br />

funkcja_1.plot(pos=(x,3*cos(1.2*x) + sin(20.*x))) #curve<br />

for x in arange(0., 10.1, 0.06): # x przebiega wartosci od 0 do 10<br />

funkcja_2.plot(pos=(x,cos(x) + sin(x))) # vbars<br />

Wykresy typu gcurve, gdots, gvbars, lub ghbars można tworzyć na podstawie gotowych list<br />

punktów, które możemy nanosić na wykres. Listy mają strukturę identyczną, z tą z którą mieliśmy<br />

do czynienia w przypadku tworzenia obiektów typu curve.<br />

punkty = [(0,0), (1,2),(2.5,4), (5,2.1), (7,-3)]<br />

data=gdots(pos=punkty, color=(1.0,0.0,0.5))<br />

Ogólne opcje obiektów typu gdisplay.<br />

Możemy ustalać rozmiar wykresu, pozycję wyświetlania na monitorze, tytuł na belce stanu<br />

wykresu, tytuły dla każdej z osi układu współrzędnych, specyficzne wartości takie jak minimum<br />

12


i maksimum dla każdej z osi.<br />

bbs=0<br />

if bbs==1:<br />

pierwszoplanowy=(0.2,0.0,0.6)#kolor tekstu<br />

drugoplanowy=(1.0,1.0,1.0)#kolor tla<br />

else:<br />

pierwszoplanowy=(1.0,1.0,0.0)<br />

drugoplanowy=(0.0,0.0,0.0)<br />

wykres_a_graph = gdisplay(x=0, y=0, width=656, height=400,<br />

title='y=sin(x)', xtitle='x', ytitle='sin(x)',<br />

xmax=15.0, xmin=-1.0,<br />

ymax=1.5, ymin=-1.5,<br />

foreground=pierwszoplanowy,<br />

background=drugoplanowy)<br />

W tym przykładzie, okno wykresu będzie ulokowane w punkcie (0,0), jego rozmiar będzie wynosić<br />

656 (szer.) na 400 (wys.) pikseli, tytuł na belce stanu to „y=sin(x)”. Wykres ma nadaną nazwę dla<br />

osi odciętych „x” oraz dla osi rzędnych „sin(x)”. Zamiast automatycznego skalowania <strong>zakresu</strong> dla<br />

wszystkich danych, wykres posiada ograniczenia dla obu osi. I tak zakres dla osi poziomej rozciąga<br />

się w granicach od -1 do +15, a dla osi pionowej od -1,5 do +1,5. Kolor napisów jest żółty<br />

(domyślna wartość to kolor biały) a kolor tła - czarny (domyślna wartość – czarny). Jeżeli po prostu<br />

napiszemy gdisplay(), domyślnymi wartościami będą x=0, y=0, width=800, height=400, bez<br />

tytułu z pełnym auto-skalowaniem zarówno dla osi x jak i y.<br />

Ponadto tak jak każdy obiekt VPythona, wykres posiada atrybut visible. Przypisanie wartości<br />

moj_wykres.display.visible=0 sprawi, że wykres będzie niewidoczny.<br />

Oprócz tego możemy mieć więcej niż jedno okno typu gdisplay a w każdym oknie więcej niż jeden<br />

typ wykresu. W tym celu należy wywołać funkcję inicjującą wykres a następnie kolejno określać<br />

jakie typy wykresów mają być rysowane w poszczególnych oknach.<br />

13


3. Ruch pojedynczego nieodkształcalnego ciała (punktu<br />

materialnego)<br />

Symulacja ruchu pojedynczego nieodkształcalnego ciała (punktu<br />

materialnego)<br />

Umiejętność przewidywania odpowiedzi układu dynamicznego w wyniku oddziaływania<br />

z zewnątrz jest niezbędna przy wykonywaniu symulacji <strong>fizycznych</strong>. Wynika stąd potrzeba<br />

rozwiązywania (całkowania) równań różniczkowych. W tym rozdziale rozpatrywane będą<br />

numeryczne metody rozwiązywania tzw. zagadnienia początkowego (zagadnienia<br />

Cauchy'ego)[10],[11], to jest wyznaczaniem funkcji y 1<br />

t , ... , y M<br />

t , spełniających dla<br />

t∈[t 0<br />

,t 0<br />

T ] układ równań różniczkowych:<br />

dy m<br />

t <br />

= f<br />

dt m<br />

t , y 1<br />

,... , y M<br />

t dla m=1, ... , M<br />

z warunkami początkowymi:<br />

y 1<br />

t 0<br />

= y 1,0<br />

,... , y M<br />

t 0<br />

= y M ,0<br />

W zapisie wektorowym układ ten ma postać:<br />

y ' t= f t , yt , yt 0<br />

= y 0 gdzie yt =[ y 1<br />

t... y M<br />

t ] T .<br />

W fizyce zmienna niezależna t ma zazwyczaj sens czasu, natomiast funkcje y m<br />

t <br />

reprezentują na przykład zmienne w czasie ładunki, napięcia, siły itp. Zagadnienie początkowe<br />

można więc interpretować jako wyznaczenie odpowiedzi czasowej układu dynamicznego w czasie<br />

[t 0<br />

,t 0<br />

T ] . Najprostszą metodę rozwiązywania zagadnienia początkowego można uzyskać<br />

poprzez przybliżenie pochodnej za pomocą różnic skończonych. Taką metodą jest otwarta metoda<br />

Eulera:<br />

y n1<br />

= y n<br />

h f t n<br />

, y n<br />

, y 0<br />

= y t 0<br />

<br />

Pierwsza pochodna funkcji, wyznacza kierunek<br />

położenia nowego punktu rozwiązania czyli tak<br />

zwany predyktor. Odległość między predyktorem<br />

a rozwiązaniem dokładnym stanowi błąd tej<br />

metody. Metoda Eulera jest łatwa do<br />

zastosowania i dobrze oddaje charakter<br />

rozwiązania, ale często<br />

jest obarczona dużym błędem. W analizie<br />

numerycznej wyróżniane są zasadniczo dwa typy<br />

błędów. Jedne wynikające z wykonywania<br />

działań arytmetycznych tak zwane błędy<br />

zaokrągleń oraz drugie wynikające z<br />

dyskretyzacji (obcięcia) nazywa się je błędem<br />

metody. W przypadku obliczeń prowadzonych w<br />

języku Python mamy standardowo do czynienia z Rysunek 7: Graficzna interpretacja metody Eulera.<br />

podwójną precyzją obliczeń 1 , więc błędy<br />

1 Jest tak w przypadku obliczeń z użyciem liczb zmiennoprzecinkowych, natomiast w przypadku obliczeń<br />

prowadzonych na obiektach typu wektor dokładność jest pojedyncza.<br />

14


zaokrągleń niewiele wpływają na wynik obliczeń. Istotnym czynnikiem może jednak być błąd<br />

obcięcia związany z zastosowaną metodą. Zobaczmy jak duży jest ten błąd dla metody Eulera.<br />

Każdą funkcję można przedstawić za pomocą rozwinięcia w szereg Taylora.<br />

y i1<br />

= y i<br />

y i<br />

' h y ' ' i<br />

2 ! h2 ... y n<br />

i<br />

n ! hn R n<br />

gdzie R n<br />

= y n1<br />

i<br />

n1! hn1 przy czym h=x i1<br />

– x i oraz x i x i1<br />

y i1<br />

= y i<br />

f x i<br />

, y i<br />

h[ f ' x i , y i h 2<br />

... f n−1 x i , y i h n<br />

0⋅h n1<br />

2!<br />

n!<br />

]<br />

Z poprzedniego równania widać, że błąd obcięcia wynosi<br />

E t<br />

= f ' x , y i i h2<br />

...0⋅h n1 <br />

2 !<br />

a gdy błąd przybliżamy do pierwszego wyrazu błędu obcięcia:<br />

E t<br />

= f ' x i , y i h 2<br />

2 !<br />

Jest to błąd lokalny na jednym kroku proporcjonalny do kwadratu kroku. Można też wykazać, że<br />

błąd globalny (całkowity) jest proporcjonalny do pierwszej potęgi kroku.<br />

Zobaczmy jak będzie wyglądała<br />

implementacja powyższej metody dla<br />

punktu materialnego umieszczonego<br />

na równi pochyłej. Dla takiego<br />

punktu druga zasada dynamiki<br />

przyjmie postać:<br />

F c<br />

F s<br />

=ma<br />

zatem po rozpisaniu na składowe<br />

otrzymamy<br />

m ẍ=m g sin <br />

m ÿ=m g cos<br />

W klasycznej metodzie Eulera<br />

położenie otrzymujemy przez dodanie<br />

Rysunek 8: Siły działające na punkt materialny umieszczony na równi<br />

pochyłej.<br />

przedziału czasu mnożonego przez<br />

prędkość. Tylko, że prędkość na początku przedziału jest inna niż na końcu, więc algorytm taki<br />

należy poddać niewielkiej modyfikacji. Usprawnienie polega na podstawianiu do algorytmu,<br />

prędkości ze środka przedziału. Gdy prędkość się zmienia i wiemy jaka jest w chwili czasu t to<br />

stosując tę wartość nie otrzymamy poprawnego wyniku w czasie tdt . Analogicznie<br />

postępujemy w przypadku obliczania prędkości biorąc przyspieszenie w połowie przedziału między<br />

dwiema chwilami w których obliczamy prędkość.<br />

Stosując zmodyfikowaną metodę Eulera [7] otrzymamy<br />

xtdt=x tdt v x<br />

dt<br />

t<br />

2 <br />

v x<br />

dt<br />

t<br />

2 x =v dt<br />

t−<br />

2 dt at <br />

a x t=g sin <br />

a prędkość pośrednia dla chwili początkowej<br />

v x dt<br />

2 =v t x 0 dt<br />

2 a t x 0<br />

w analogiczny sposób wyznaczamy przyspieszenie, prędkość i położenie dla y-owej składowej.<br />

15


Przy pomocy powyższych równań została skonstruowana funkcja Modify_Euler() odpowiedzialna<br />

między innymi za całkowanie równań ruchu. W celu sprawdzenia wyników symulacji może zostać<br />

uruchomiona funkcja dane_precyzyjne(). Opiera się ona o następujące równania. Przyspieszenie<br />

jest wyliczane na podstawie znanego wzoru a=g sin ig cos j , prędkość ciała określa<br />

zależność v= g sin t i g cost j , oraz położenie<br />

2<br />

r= x g sin t<br />

0 2 j .<br />

Wzory te są prawdziwe przy założeniu zerowej prędkości początkowej.<br />

2 i y 0 g cost 2<br />

Rysunek 9: Symulacja ruchu punktu materialnego umieszczonego na równi pochyłej. Kolorem czerwonym<br />

przedstawione zostały wyniki symulacji, natomiast kolorem zielonym wartości teoretyczne. Całkowanie - metoda Eulera<br />

Rzut w jednorodnym polu grawitacyjnym<br />

Z zagadnieniem ruchu w przestrzeni dwu- lub trójwymiarowej [3],[6],[8],[9] łączy się wiele<br />

problemów, z którymi mamy do czynienia w życiu codziennym. Na przykład siły działające na<br />

ciało w czasie rzutu ukośnego są bardzo podobne do tych, które działają na samoloty, samochody i<br />

wszelkie inne obiekty poruszające się w wodzie, powietrzu i na lądzie.<br />

Przy pominięciu efektów aerodynamicznych, jedyna siła jaka działa na ciało które rzucimy, jest siła<br />

grawitacji. Jeżeli rzut jest wykonywany ze stosunkowo niewielkimi prędkościami (czyli trajektoria<br />

ciała przebiega na niedużych wysokościach i w odpowiednio krótkim czasie) możemy pominąć<br />

16


efekt Coriolisa oraz przyjąć stałe przyspieszenie. Symulację rzutu uwzględniającego wspomniany<br />

efekt Coriolisa można znaleźć na stronie [D]<br />

Kolejnym uproszczeniem jakie zostało przyjęte przy konstruowaniu symulacji rzutu jest płaskość<br />

powierzchni Ziemi to znaczy jej krzywizna jest dużo większa w porównaniu z zasięgiem rzuconego<br />

ciała. Dodatkowo aby porównać wyniki symulacji w wartościami wyliczonymi zostały<br />

zaimplementowane funkcje pozwalające między innymi na obliczenie zasięgu<br />

R= 2v 2 0<br />

sin cos<br />

,<br />

g<br />

czasu trwania lotu t lotu<br />

= 2v 0 sin<br />

,<br />

g<br />

oraz maksymalnej wysokości osiąganej przez ciało h= v 2 0 sin 2 <br />

2 g<br />

.<br />

Rysunek 10: Symulacja rzutu ukośnego na płaskiej powierzchni w stałym polu grawitacyjnym. Jedna z opcji programu<br />

pozwala na wizualizację wypadkowego wektora prędkości oraz jej składowych.<br />

Jak widać na powyższej ilustracji trajektoria ciała jest krzywą paraboliczną, na szczycie trajektorii<br />

składowa pionowa przyjmuje wartość równą zeru, a wartość prędkości w chwili wyrzucenia ciała<br />

jest równa wartości prędkości w chwili upadku na ziemię. Symetria zagadnienia pozwala także<br />

zauważyć, że czas dotarcia ciała do najwyższego punktu trajektorii jest równy czasowi spadania.<br />

Ponadto przy większej liczbie prób można zauważyć iż maksymalny zasięg otrzymujemy gdy<br />

w chwili wyrzucenia składowa x-owa jest równa składowej y-owej.<br />

17


Statek w wodzie<br />

Kolejny przykład to ruszanie z miejsca statku [3]. Łódź taka najpierw pozostaje w spoczynku,<br />

a następnie śruba zaczyna się obracać i wytwarzać pchającą go siłę T nazywaną często siłą ciągu<br />

silnika. Zakładając, że siła jest niewielka, siłę oporu możemy w przybliżeniu wyrazić następująco:<br />

R=−C v gdzie<br />

R - jest oporem całkowitym.<br />

Pierwszym krokiem rozwiązania jest identyfikacja wszystkich sił działających na statek.<br />

Zagadnienie znacząco się upraszcza gdy<br />

siła wyporu będzie równa ciężarowi, a<br />

statek nie będzie ulegał chwilowym<br />

zanurzeniom i wynurzeniom z wody, wtedy<br />

będziemy mieli do czynienia z ruchem<br />

jednowymiarowym wzdłuż osi x-ów.<br />

Zgodnie z drugą zasadą dynamiki Newtona<br />

można napisać:<br />

∑ F x<br />

=m a<br />

T −C v=m a<br />

Rysunek 11: Siły działające na statek.<br />

gdzie T - jest siłą ciągu silnika, C - współczynnik oporu cieczy, v - prędkość statku<br />

podstawiamy a= dv i przekształcamy<br />

dt<br />

v<br />

m<br />

dt=∫<br />

T −C v ] dv<br />

t<br />

∫ [<br />

0 0<br />

Scałkowanie tego równania po czasie pozwala na otrzymanie szybkości statku w funkcji czasu.<br />

W efekcie otrzymamy:<br />

v 2<br />

= T C − T <br />

C<br />

1 C m −v t<br />

e−<br />

następnie podstawiamy v= ds<br />

dt<br />

t<br />

∫<br />

0<br />

[ T C − T C −v 1 e−<br />

C m t] t 2<br />

dt= ∫ds<br />

t 1<br />

i przekształcamy<br />

ponowne scałkowanie pozwala na wyznaczenie przesunięcia statku w funkcji czasu:<br />

s 2<br />

=s 1<br />

<br />

T C t T C −v 1 m C e−<br />

C m t −<br />

T C −v 1 m C <br />

Równania określające szybkość i przemieszczenie w funkcji czasu służą kontroli precyzji<br />

otrzymywanych wyników symulacji.<br />

18


Rysunek 12: Symulacja ruchu punktu materialnego w jednym wymiarze z uwzględnieniem oporu ruchu<br />

(proporcjonalnego do prędkości). Kolorem czerwonym przedstawione zostały wyniki symulacji, natomiast kolorem<br />

zielonym wartości teoretyczne. Całkowanie – metoda Rungego-Kutty.<br />

Aby zapewnić większą precyzję obliczeń często zmniejsza się krok czasowy symulacji jednak tylko<br />

w nielicznych sytuacjach zabieg taki przynosi oczekiwane rezultaty. Istnieje wiele innych metod<br />

zapewniających zwiększenie precyzji przy zachowaniu tego samego kroku czasowego.<br />

Przedstawione poniżej metody (rzędu drugiego) [10] wymagają w każdym kroku czasowym<br />

obliczenia dwóch wartości funkcji f :<br />

• metoda Heuna<br />

y i1<br />

= y i<br />

1 2 k 1 1<br />

2<br />

2 k h<br />

k 1<br />

= f x i<br />

, y i<br />

<br />

k 2<br />

= f x i<br />

h ; y i<br />

k 1<br />

h<br />

• metoda punktu środkowego (ang. mid-point method)<br />

y i1<br />

= y i<br />

k 2<br />

h<br />

k 1<br />

= f x i<br />

, y i<br />

<br />

k 2<br />

= f<br />

x i 1 2 h ; y i 1 2 k 1 h <br />

19


y i1<br />

= y i<br />

1 3 k 2 1<br />

3<br />

2 k h<br />

• metoda Ralston'a zapewniająca mniejszy błąd obcięcia<br />

k 1<br />

= f x i<br />

, y i<br />

<br />

k 2<br />

= f<br />

x i 3 4 h; y i 3 4 k 1 h <br />

Kolejna metoda zapewnia jeszcze większą dokładność. Jest ona znana pod nazwą metody Rungego-<br />

Kutty . Odpowiadająca jej funkcja f ma postać:<br />

t , y ,h=∑ w r<br />

k r<br />

r =1<br />

przy czym:<br />

R<br />

k r<br />

=k r<br />

t , y ,h= f tha r<br />

, y∑ b r , <br />

k <br />

dla r=1,... , R<br />

R<br />

=1<br />

zaś a r , b r , , w r są parametrami. Jeżeli b r ,<br />

=0 to metoda jest typu otwartego (ang.:<br />

explicit). W przeciwnym wypadku metoda jest typu zamkniętego (ang.: implicit). Wyznaczenie<br />

wartości funkcji t , y , h wymaga wówczas rozwiązania równań nieliniowych ze względu na<br />

k r . Maksymalny rząd otwartej metody Rungego-Kutty, korzystającej z R wartości funkcji<br />

wynosi pR , przy czym p=R dla R=1,2 ,3 ,4 . Maksymalny rząd metody Rungego-<br />

Kutty może być równy 2 R , jednakże ze względu na konieczność rozwiązywania nieliniowych<br />

równań algebraicznych koszt wykonana jednego kroku jest dla takiej metody na ogół znacznie<br />

większy niż dla odpowiedniej metody otwartej.<br />

Najczęściej stosowana jest metoda Rungego-Kutty czwartego rzędu ze współczynnikami [11]:<br />

w 1<br />

=w 4<br />

= 1 , w<br />

6<br />

2<br />

=w 3<br />

= 1 3<br />

k 1<br />

= f t , y , k 2<br />

= f th /2 , yh k 1<br />

/2<br />

k 3<br />

= f th/2 , yh k 2<br />

/2 , k 4<br />

= f t , yh k 3 <br />

y i 1<br />

= y i<br />

1 6 k 1<br />

2k 2<br />

2k 3<br />

k 4 h<br />

oraz metoda Rungego-Kutty rzędu trzeciego:<br />

k 1<br />

= f x i<br />

, y i<br />

<br />

k 2<br />

= f<br />

x i 1 2 h; y i 1 2 k 1 h <br />

k 3<br />

= f x i<br />

h; y i<br />

−k 1<br />

h2 k 2<br />

h<br />

y i 1<br />

= y i<br />

1 6 k 1<br />

4k 2<br />

k 3 h<br />

20


Tabela. Wyniki symulacji dla ruchu punktu materialnego w jednym wymiarze z uwzględnieniem<br />

oporu ruchu (proporcjonalnego do prędkości) plik 04ruch1D_w_cieczy.py<br />

Eulera<br />

Heuna<br />

Mid-point<br />

Ralston'a<br />

Metoda<br />

Rungego-Kutty III<br />

Rungego-Kutty IV<br />

Wartość precyzyjna<br />

Eulera<br />

Heuna<br />

Mid-point<br />

Ralston'a<br />

Rungego-Kutty III<br />

Rungego-Kutty IV<br />

Wartość precyzyjna<br />

Eulera<br />

Heuna<br />

Mid-point<br />

Ralston'a<br />

Rungego-Kutty III<br />

Rungego-Kutty IV<br />

Wartość precyzyjna<br />

Eulera<br />

Heuna<br />

Mid-point<br />

Ralston'a<br />

Rungego-Kutty III<br />

Rungego-Kutty IV<br />

Wartość precyzyjna<br />

czas<br />

[s]<br />

10<br />

20<br />

50<br />

100<br />

przyspieszenie<br />

[m/s^2]<br />

prędkość<br />

[m/s]<br />

położenie<br />

[m]<br />

0.739459275299 12.679353174535 74.474403572099<br />

0.735734079146 12.642287625676 74.206061670345<br />

0.739449907828 12.642287625676 74.206061670345<br />

0.737591993487 12.642287625676 74.206061670345<br />

0.735796132966 12.642411485600 74.206957495689<br />

0.735758820166 12.642411175953 74.206955256137<br />

0.735758882343 12.642411176571 73.575888234288<br />

0.270666009814 17.320406502841 228.527975621877<br />

0.270665986917 17.293203430836 227.928280837193<br />

0.272032986851 17.293203430836 227.928280837193<br />

0.271349486884 17.293203430836 227.928280837193<br />

0.270684258843 17.293294562639 227.930285259632<br />

0.270670543622 17.293294334813 227.930280248621<br />

0.270670566473 17.293294335268 227.067056647323<br />

0.013273703116 19.868590339152 803.30095564239<br />

0.013476344869 19.865229745070 802.335972772794<br />

0.013544407217 19.865229745070 802.335972772794<br />

0.013510376043 19.865229745070 802.335972772794<br />

0.013476574003 19.865241088319 802.339204080840<br />

0.013475892864 19.865241059962 802.339196002693<br />

0.013475893998 19.865241060018 801.347589399826<br />

0.000087214641 19.999136575052 1802.008547906987<br />

0.000090810522 19.999091848919 1801.004011205801<br />

0.000091269161 19.999091848919 1801.004011205801<br />

0.000091039841 19.999091848919 1801.004011205801<br />

0.000090804422 19.999092001786 1801.007376393834<br />

0.000090799852 19.999092001404 1801.007367981006<br />

0.000090799860 19.999092001405 1800.009079985924<br />

Nazwa Rungego-Kutty III odnosi się do metody Rungego-Kutty trzeciego rzędu, natomiast metoda Rungego-Kutty IV odnosi się do tej samej<br />

metody rzędu czwartego.<br />

Symulacja była wykonana dla następujących parametrów początkowych: masa statku m=10000.0 kg, współczynnik oporu C = 1000.0 kg/s, siła<br />

ciągu silnika T=20000.0 N,krok czasowy symulacji dt=0.1 s<br />

21


Rzut z uwzględnieniem oporu powietrza i wiatru.<br />

W poprzedniej symulacji rzutu dokonane zostały uproszczenia polegające między innymi na<br />

pominięciu oporu powietrza. W praktyce jednak jest to tylko możliwe w przypadku gdy ciało<br />

porusza się w próżni. Kolejnym uproszczeniem było przyjęcie braku wiatru mogącego znosić ciało.<br />

Uwzględnienie ich w kolejnej symulacji sprawia, że trajektoria lotu staje się bardziej realistyczna<br />

i jest zbliżona do tych z którymi mamy do czynienia w przypadkach ciał poruszających się<br />

w atmosferze [3]. Niemniej jednak i i tym przypadku zostały poczynione pewne założenia<br />

upraszczające równania ruchu. Po pierwsze założone zostało, że ciało jest kulą a siła oporu<br />

powietrza jest proporcjonalna do prędkości ze stałym współczynnikiem proporcjonalności. Co<br />

przedstawić możemy w postaci:<br />

F d<br />

=−C d v gdzie C d jest współczynnikiem oporu powietrza.<br />

W rzeczywistości jednak siła oporu jest jednak funkcją kwadratu szybkości. Takie założenie sprawi,<br />

że otrzymamy rozwiązania analityczne. Po drugie założone zostało, że na ciało działa wiatr o stałej<br />

sile zależnej od stałego współczynnika oporu i szybkości wiatru:<br />

F w<br />

=−C w v w .<br />

Znak minus oznacza że siła jest przeciwnie zwrócona do ruchu ciała gdy wiatr wieje w kierunku<br />

przeciwnym niż porusza się ciało. Można zatem zapisać składowe x i y siły wiatru w zależności od<br />

jego kierunku:<br />

F wx =F w cos =−C w v w cos<br />

F wz =F w sin =−C w v w sin <br />

Równania ruchu dla każdej z osi układu<br />

współrzędnych przyjmą postać:<br />

∑ F x<br />

=F wx<br />

F dx<br />

=m a x<br />

∑ F y<br />

=F dy<br />

F g<br />

=ma y<br />

∑ F z<br />

=F wz<br />

F dz<br />

=m a z<br />

Na początek podstawiamy do równań ruchu<br />

wyrażenia odpowiadające działającym siłom:<br />

−C w<br />

v w<br />

cos−C d<br />

v x<br />

=m dv x<br />

dt<br />

−C d<br />

v y −mg=m dv y<br />

dt<br />

−C w<br />

v w<br />

sin −C d<br />

v z<br />

=m dv z<br />

Rysunek 13: Wyznaczenie kąta gamma.<br />

dt<br />

W oparciu o te równania skonstruowana jest funkcja metoda_EULERA() odpowiedzialna za<br />

wyznaczanie prędkości i położenia w kolejnych chwilach czasu. By móc kontrolować otrzymane<br />

wyniki musimy poddać całkowaniu te równania i wówczas otrzymamy:<br />

dla składowych x<br />

v x2<br />

= 1 <br />

C d<br />

[e − C d<br />

m t C w<br />

v w<br />

cos C d<br />

v x1 −C w<br />

v w<br />

cos]<br />

s x 2<br />

={ m C d<br />

<br />

C d<br />

e−<br />

m t[ C v cos<br />

w w<br />

C d<br />

−v 1]<br />

x<br />

[ − C v cos<br />

w w<br />

C d<br />

]} { t − m [ C d<br />

− C v cos<br />

w w<br />

C d<br />

−v 1]}<br />

x s x 1<br />

22


dla składowych y<br />

v y2<br />

= 1 C d<br />

<br />

{<br />

s y2<br />

=s − y1<br />

[ v y 1<br />

mg<br />

C d<br />

e−<br />

m t C d<br />

v y 1<br />

mg − mg<br />

C d<br />

C d<br />

]<br />

m <br />

C d<br />

C d<br />

e−<br />

m t −t mg<br />

} C d<br />

{ m C d<br />

[<br />

v y 1<br />

mg<br />

C d<br />

]}<br />

dla składowych z<br />

v z2<br />

= 1 <br />

C d<br />

[e − C d<br />

m t C w<br />

v w<br />

sin C d<br />

v z1 −C w<br />

v w<br />

sin ]<br />

s z 2<br />

={ m C d<br />

[ − C v w wsin <br />

C d<br />

C d<br />

e−<br />

m t[ C v w wsin <br />

C d<br />

−v 1]<br />

z<br />

t ]} − { m C d<br />

[<br />

− C v w wsin <br />

C d<br />

−v 1]}<br />

z s z 1<br />

i tak jak poprzednio równania te stają się elementami funkcji pozwalającej ocenić dokładność<br />

rozwiązania numerycznego.<br />

23


Rysunek 14: Symulacja ruchu punktu materialnego w trzech wymiarach z uwzględnieniem oporu ruchu<br />

(proporcjonalnego do prędkości) oraz wiatru (o stałej prędkości i kierunku). Kolorem czerwonym przedstawione<br />

zostały wyniki symulacji, natomiast kolorem zielonym wartości teoretyczne. Całkowanie - metoda Eulera<br />

24


4. Modelowanie obiektów (część 2)<br />

Budowa obiektów od podstaw<br />

• metoda frame<br />

Jedną z najprostszych metod tworzenia obiektów złożonych jest łączenie brył prymitywnych<br />

w większe grupy [G]. Przy pomocy funkcji frame możemy łączyć ze sobą zarówno różne<br />

bryły jak i krzywe, budując w ten sposób większe i bardziej złożone elementy. Połączone<br />

w ten sposób obiekty można przesuwać i obracać tak jak pojedyncze obiekty.<br />

magnes=frame()<br />

box(frame=magnes,pos=(0.0,0.0,0.0),<br />

length=1.0,height=0.25,width=0.25,<br />

color=(1.0,0.0,0.0))<br />

box(frame=magnes,pos=(1.0,0.0,0.0),<br />

length=1.0,height=0.25,width=0.25,<br />

color=(0.2,0.2,1.0))<br />

while 1:<br />

rate(25)<br />

#magnes.axis = (1,1,0)<br />

magnes.rotate(axis=(0,0,1), angle=a,<br />

origin=punkt)<br />

Domyślnie funkcja frame() przyjmuje pozycję<br />

(0,0,0) i orientację wzdłuż kierunku osi x. Gdy<br />

zmienione zostaną atrybuty funkcji frame() na<br />

przykład pozycji lub obrotu, cały obiekt zostanie<br />

przesunięty i obrócony.<br />

• funkcja convex<br />

Kolejna metoda umożliwia tworzenie obiektów na podstawie listy wierzchołków [G].<br />

Funkcja convex() pobiera listę pozycji punktów,<br />

(podobnie jak funkcja curve() ) z których generuje obiekt<br />

wypukły. Jeżeli się zdarzy, że któryś z punktów będzie<br />

leżał wewnątrz obiektu wypukłego to zostanie on<br />

pominięty. W przypadku gdy punkty będą leżeć w jednej<br />

płaszczyźnie utworzony obiekt będzie płaską<br />

powierzchnią. Poniżej przedstawiony został fragment<br />

skryptu przy pomocy którego można wykonać trójkąt<br />

równoboczny.<br />

Rysunek 15: Obiekty połączone przy pomocy<br />

funkcji frame.<br />

# trojkat<br />

POZ_x = -1.0<br />

POZ_y = 1.0<br />

POZ_z = 0.0<br />

Rysunek 16: Obiekty wykonane za<br />

trojkat = convex(color=(1,0,0.5))<br />

pomocą funkcji convex.<br />

x = arange(0,2*math.pi,2*math.pi/3)<br />

trojkat.pos = transpose( (sin(x)+POZ_x, cos(x)+POZ_y, 0*x+POZ_z) )<br />

25


• tworzenie obiektów z pojedynczych trójkątów – triangularyzacja obiektów<br />

Często zdarza się, że geometria bryły jest skomplikowana a przybliżanie jej budowy<br />

za pomocą dostępnych brył prymitywnych może okazać się zbyt pracochłonne lub<br />

niewystarczające. Kolejna funkcja jaka zostanie zaprezentowana umożliwia budowę obiektu<br />

z pojedynczych trójkątów [G]. Ogromna większość programów do grafiki 3D korzysta<br />

z modeli (brył), które składają się z trójkątów. Przedstawienie geometrii obiektu złożonego<br />

za pomocą trójkątów (lub w przestrzeni 3D za pomocą czworościanów) nazywane jest<br />

triangularyzacją. Gdy będziemy mieli listę punktów takiego ztriangularyzowanego obiektu<br />

oraz dla każdego z wierzchołków określony atrybut koloru i powierzchni normalnej (ang.<br />

normal) możemy użyć funkcji faces() do stworzenia powierzchni pomiędzy odpowiednimi<br />

trójkami punktów. Metoda oparta o tą zasadę jest stosowana z powodzeniem w wielu<br />

językach programowania.<br />

Rysunek 17: Przy konstruowaniu obiektów z trójkątów musimy pamiętać<br />

o zdefiniowaniu obydwu stron obiektów. Widoczny na ilustracji kwadrat<br />

czerwono-żółty ma zdefiniowaną tylko jedną stronę więc jest widoczny<br />

wyłącznie z jednej strony<br />

Obiekty tworzone przy pomocy funkcji faces() są tak naprawdę tablicami punktów<br />

(podobnie jak obiekty typu curve, convex, etc), tak więc aby uzyskać łatwy dostęp do całego<br />

obiektu niezbędne może się okazać scalenie przy pomocy funkcji frame(). Nadanie<br />

poszczególnych atrybutów dla poszczególnych punktów może wyglądać następująco:<br />

KLC=(1,1,0)#kolor zolty<br />

KLB=(1,0,0)#kolor czerwony<br />

#KWADRAT - widoczny tylko z przodu<br />

#triangle 1<br />

Tmap2 = faces()<br />

Tmap2.append(pos=(0,0,0), normal = (0,0,1), color = KLB)<br />

Tmap2.append(pos=(1,0,0), normal = (0,0,1), color = KLB)<br />

Tmap2.append(pos=(0,1,0), normal = (0,0,1), color = KLC)<br />

#triangle 2<br />

Tmap = faces()<br />

Tmap.append(pos=( 1,1,0), normal = (0,0,1), color = KLC)<br />

Tmap.append(pos=( 0,1,0), normal = (0,0,1), color = KLC)<br />

Tmap.append(pos=( 1,0,0), normal = (0,0,1), color = KLB)<br />

Każda ścianka trójkąta jest widoczna tylko z jednej strony. To, która strona jest widoczna<br />

zdeterminowane jest przez kolejność podawania punktów. Kiedy spojrzymy na ściankę,<br />

będzie ona widoczna tylko wówczas jeżeli kolejność podawania punktów do funkcji face()<br />

będzie zgodna z ruchem wskazówek zegara. Jeżeli zatem będziemy potrzebowali trójkąt<br />

widoczny z obu stron, musimy stworzyć kolejny trójkąt podając punkty w kierunku<br />

przeciwnym do ruchu wskazówek zegara.<br />

26


Jeżeli nie określimy atrybutu dla<br />

powierzchni normalnej, trójkąt taki będzie<br />

oświetlony tylko przez światło otaczające.<br />

Aby główne światło oddziaływało na<br />

powierzchnię trójkąta musimy określić<br />

atrybut powierzchni normalnej<br />

(oświetlenie) dla każdego z wierzchołków.<br />

W najprostszym przypadku kierunek<br />

światła głównego jest prostopadły do<br />

powierzchni trójkąta a przylegające na<br />

około trójkąty mają widoczne krawędzie<br />

(ang. solid).<br />

Gładkie krawędzie mogą być wytworzone<br />

przez uśrednienie atrybutów powierzchni<br />

normalnej dla dwóch przylegających<br />

ścianek w punkcie zetknięcia każdego z<br />

Rysunek 18: Obiekty wykonane z trójkątów przy pomocy<br />

funkcji faces. a) powierzchnia obiektu wygładzona<br />

(ang. smooth) b) powierzchnia obiektu bez wygładzenia<br />

(ang. solid)<br />

wierzchołków. Jasność powierzchni jest proporcjonalna do cos (gdzie - to kąt<br />

pomiędzy wektorami normalnymi) i natężenia światła głównego.<br />

Jeżeli określimy różne kolory dla poszczególnych wierzchołków w danym trójkącie, nie to<br />

otrzymamy jednobarwnego trójkąta gdyż VPython interpoluje kolory w poprzek<br />

powierzchni trójkąta. Podobnie jest również dla atrybutów powierzchni normalnej, jeżeli<br />

będą różnice w tych atrybutach to powierzchnia nie będzie jednolicie jasna.<br />

Funkcja face() jest zatem przeznaczona do konstruowania nowych brył. Jej elastyczność jest<br />

wystarczająca dla implementacji algorytmów wygładzania powierzchni, kolorowania<br />

wierzchołków, wykonywania powierzchni jedno lub obustronnie oświetlonych etc, ale<br />

należy pamiętać, że wszystkie kalkulacje muszą być wykonane przez programistę.<br />

W przypadku brył standardowych takich jak stożki i sfery algorytmy wygładzania<br />

powierzchni są wewnętrznie zaimplementowane w strukturze wywołującej daną bryłę.<br />

Relacjonowanie z kilku kamer jednocześnie<br />

Wywołując dowolną bryłę z biblioteki Visual inicjujemy automatycznie okno 3D, które nazwane<br />

jest scene. W przypadku gdy samodzielnie zdefiniujemy parametry okna nie zostanie ono<br />

stworzone dopóki nie zainicjujemy jakichś obiektów, które będą się tam znajdować, poza tym jeżeli<br />

już wcześniej w programie zainicjujemy własne okno 3D nie musimy interesować się oknem o<br />

nazwie scene.<br />

Polecenie display() tworzy w pamięci okno 3D wraz ze specyficznymi dla niego atrybutami.<br />

Powoduje także automatyczną selekcję tego okna, i zwraca je w wyżej wymienionym przypadku.<br />

Dla przykładu, poniższy skrypt wywołuje okno 3D o rozdzielczości 600 na 200 pikseli z tytułem<br />

„Moje okno” umieszczonym na belce stanu. Środek okna (centrum) znajduje się w punkcie (3,0,0) a<br />

kolor wypełnienia okna jest biały.<br />

scene2 = display(title='Moje okno',<br />

width=600, height=200,<br />

27


center=(3,0,0), background=(1,1,1))<br />

Ogólne opcje okna 3D (display window):<br />

select() - aktywuje specyficzne okno (wyselekcjonowane), zatem obiekty będą rysowane<br />

wewnątrz tego okna na przykład scene.celect()<br />

foreground – ustawia kolor który będzie używany podczas tworzenia obiektów na przykład takich<br />

jak sphere czy box, domyślną wartością tego parametru jest (1,1,1) – kolor biały,<br />

na przykład scene.foreground = (1,1,0)<br />

background - ustawia kolor który posłuży do wypełnienia okna (tzw. kolor tła);domyślnym<br />

kolorem jest czarny<br />

stereo – opcja obrazu dwukolorowego stosowanego przy wizualizacji trójwymiarowej.<br />

scene.stereo = 'redblue' generuje czerwoną scenę dla oka lewego oraz niebieską scenę dla<br />

oka prawego. Obraz taki należy oglądać<br />

przez czerwono-niebieskie okulary<br />

przystosowane do tego celu. Oprócz<br />

czerwono-niebieskiego odcienia możemy<br />

uzyskać obraz w kolorach „red-cyan” oraz<br />

„yellowblue”. Jeżeli obiekty znajdujące się<br />

w scenie nie będą w kolorze białym mogą<br />

w pewnym stopniu zmienić barwę na<br />

ciemniejszą i nie będą zbyt dobrze<br />

widoczne. Opcja scene.stereo = 'active'<br />

pozwala renderować obraz na przemian dla<br />

lewego oraz prawego oka, przy czym<br />

oglądanie takiego obrazu wymaga<br />

specjalnych okularów (a w zasadzie hełmu<br />

wyposażonego w dwa ciekłokrystaliczne<br />

wyświetlacze) pracujących w systemie<br />

quad buffered stereo. Opcja<br />

scene.stereo = 'passive' renderuje obraz<br />

podwójnie. Obraz taki może być<br />

Rysunek 19: Widok sceny przy włączonej opcji<br />

stereo.<br />

wyświetlany przez dwa zsynchronizowane ze sobą projektory, lub przy pomocy<br />

przeglądarki do stereogramów. Jeżeli karta graficzna nie posiada wspomagania wyżej<br />

wymienionych opcji ustawienia parametrów nie przyniosą żadnego efektu.<br />

Opcja quad buffered 'active' stereo jest tylko dostępna na specjalistycznych kartach<br />

graficznych posiadających niezbędne wspomaganie sprzętowe oraz okulary (hełm)<br />

wyposażony w zmienną migawkę dla każdego oka z osobna (shutter glass connector) na<br />

przykład system ten posiadają komputery SGI oraz komputery klasy PC wyposażone w<br />

kartę nVidia Quadro a także 3DLabs Wildcat graphics cards. Renderując każdy obraz<br />

podwójnie z różnego punktu obserwacji uzyskujemy sztucznie wygenerowaną iluzję głębi<br />

przestrzeni. Specjalne okulary (shutter glasses) zsynchronizowane z na przemian<br />

pojawiającymi się obrazami dla każdego oka sprawiają, że każde oko widzi właściwy<br />

obraz dla niego a nasz mózg wykonuje całą resztę. Metoda ta jest nazywana 'quad<br />

buffered' - „czterokrotnego powtarzania obrazu” ponieważ biblioteka OpenGL buforuje<br />

obraz dla każdego oka z osobna a obraz taki jest wyświetlany z podwójną częstością<br />

w celu uzyskania płynnych przejść ruchu. Opcja 'passive' wymaga kart graficznych<br />

mogących wyświetlać obraz na dwóch monitorach lub projektorach.<br />

28


Rysunek 20: Scena renderowana z użyciem opcji passive. Przyglądając się nieco dokładniej niniejszej ilustracji<br />

zauważymy, iż obraz jest renderowany z dwóch różnych ustawień kamery (analogicznie do pary oczu).<br />

ambient – ilość światła niekierunkowego (otaczającego).Wartością domyślną jest 0.2.<br />

lights – lista wektorów reprezentujących kierunek światła głównego. Jego parametrem<br />

charakteryzującym jest natężenie. I tak na przykład scene.lights = [vector(1,0,0)] wraz z<br />

parametrem scene.ambient = 0 sprawi, że światło będzie padało tylko z prawej strony<br />

ponieważ wartość światła padającego z innych kierunków niż główny wynosi 0, zatem<br />

parametr scene.ambient odnosi się do światła otaczającego. Domyślnie ustawiane są dwa<br />

światła, pierwsze w punkcie (0.17, 0.35, 0.70), którego jasność (magnitude) wynosi 0.8,<br />

oraz drugie (-0.26, -0.07, -0.13), magnitude 0.3. Atrybuty światła głównego i światła<br />

otacającego muszą by odpowiednio dobrane, ponieważ całkowita intensywność światła w<br />

dowolnym miejscu sceny musi wynosić 1.W przeciwnym wypadku otrzymamy scenę<br />

niedoświetloną lub oświetloną światłem o zbyt dużym natężeniu.<br />

cursor.visible – ustawienie tego parametru pozwala na wyłączenie widoczności kursora myszy<br />

scene.cursor.visible = 0. Ta opcja jest często stosowana podczas przesuwania i obracania<br />

obiektów lub w przypadku gdy kursor myszy w danej scenie po prostu staje się<br />

zbyteczny. Przywrócenie widoczności kursora następuje po wydaniu polecenia<br />

scene.cursor.visible = 1.<br />

objects – lista obiektów w danym oknie 3D posiadających atrybut visible. Obiekty nie posiadające<br />

możliwości wizualizacji nie wchodzą w skład tej listy. Lista taka pozwala kontrolować<br />

atrybuty danej grupy obiektów. Poniższy fragment skryptu pozwala na zmianę parametru<br />

koloru wszystkich kul znajdujących się w danej scenie.<br />

for obj in sceneB.objects:<br />

if obj.__class__ == sphere # możemy pisać sphere lub 'sphere'<br />

obj.color = (1.0,1.0,0.0)<br />

29


Parametry pozwalające kontrolować okno<br />

x, y - pozycja okna na ekranie. Punktem (0,0) jest lewy górny róg ekranu.<br />

width, height - szerokość i wysokość wyświetlanego okna na przykład scene.height =800,<br />

scene.height =600<br />

title – tekst wyświetlany na belce stanu okna np. scene.title = 'Trajektoria ruchu '<br />

visible – należy zawsze się upewnić że scena jest widoczna wydając polecenie sceneB.visible=1,<br />

które powoduje wizualizacje sceny, ustawienie tego parametru na zero spowoduje, że<br />

okno nie będzie wyświetlane.<br />

fullscreen – powoduje otworzenie okna w trybie pełnoekranowym bez ramki i belki tytułowej.<br />

Zamknięcie okna uruchomionego w tym trybie możemy uzyskać ponaciśnięciu klawisza<br />

Escape.<br />

exit – domyślną wartością tego parametru jest scene.exit = 1 powoduje on zamknięcie wszystkich<br />

okien (zarówno 3D jak i wykresów) i wyjście z programu po naciśnięciu ikony wyjścia.<br />

Jeżeli wartość tego parametru dla każdego wyświetlanego okna będzie wynosić 0, to<br />

podczas opuszczania programu będziemy musieli zamykać każde okno z osobna.<br />

Kontrola widoku<br />

center – punkt, na który jest nieustannie jest skierowana kamera, nawet wówczas gdy użytkownik<br />

obróci kamerę. W przypadku zmiany wartości tego punktu kamera zachowuje ten sam<br />

kierunek spojrzenia na centrum sceny dopóki nie zostanie zmieniony parametr foward.<br />

Wartość domyślna parametru center to środek układu współrzędnych (0.0,0.0,0.0).<br />

foward – parametr ten pozwala kontrolować ustawienie kamery w przestrzeni. Foward jest<br />

wektorem pomiędzy punktem położenia kamery oraz punktem na który jest skierowana<br />

kamera (punkt center). Użytkownik<br />

zmieniając zakres lub kąt patrzenia<br />

zmienia wartość tego parametru.<br />

Wartością domyślną jest (0,0,-1)<br />

range – zakres widoczności jaki ma być<br />

objęty kamerą. Obszar ten<br />

wyznaczany jest względem punktu<br />

center. Wartością domyślną jest<br />

(10,10,10). Automatyczną kontrolę<br />

<strong>zakresu</strong> możemy uzyskać włączając<br />

parametr autoscale.<br />

Niekiedy zachodzi potrzeba oglądania<br />

animacji bądź symulacji jednocześnie z kilku<br />

położeń kamery [F]. Możemy to osiągnąć<br />

inicjując kilka scen jednocześnie i kopiując<br />

wszystkie elementy z jednej sceny do innych.<br />

Uzyskamy w ten sposób możliwość<br />

indywidualnej kontroli parametrów dla<br />

każdej z kamer oddzielnie.<br />

Rysunek 21: Relacjonowanie przebiegu animacji z czterech<br />

niezależnych położeń kamery.<br />

30


Wykresy – znakowanie danych<br />

Przy rysowaniu wykresu na przykład typu (gdots) można określić inny kolor danych, które<br />

spełniają określony warunek czyli poddać dane filtrowaniu. Dla przykładu przedstawiono dane,<br />

które zostały poddane filtrowaniu ze względu na amplitudę (przebiegi A,B,C do których<br />

zastosowano filtr ograniczający), dane modyfikowane przez filtr progowy (przebieg D), oraz dane<br />

przekształcane przez filtry kierunkowe (przebiegi E,F).<br />

Przyjmując zatem, że dane oznaczone kolorem zielonym są przepuszczane przez filtr (spełniają<br />

określony warunek) a dane oznaczone kolorem czerwonym są ograniczane, przez filtr (nie spełniają<br />

określonego warunku) otrzymamy filtr typu ograniczającego (przebiegi A,B,C).<br />

Rysunek 22: Podczas rysowania wykresu istnieje możliwość wyróżnienia innym kolorem określonej grupy danych.<br />

31


5. Ruch bryły sztywnej<br />

Wyznaczanie drogi hamowania samochodu<br />

Droga zatrzymania pojazdu [3] podczas gwałtownego hamowania składa się z drogi reakcji<br />

kierowcy oraz drogi hamowania 2 b d . Pierwsza z nich wynika z pewnego stałego czasu<br />

upływającego pomiędzy dostrzeżeniem przeszkody a naciśnięciem hamulca i nosi nazwę czasu<br />

reakcji. Druga natomiast (przy założeniu stałej siły hamowania) jest wprost proporcjonalna do<br />

kwadratu prędkości i zależy między innymi od od rodzaju nawierzchni (asfalt, kostka kamienna<br />

itp.), od stanu nawierzchni (sucha, mokra, pokryta lodem, posypana piaskiem itp.) i kąta nachylenia<br />

jezdni do poziomu.<br />

v 2<br />

b d =<br />

2g k<br />

cossin<br />

Oczywiście siły tarcia zawsze opierają się ruchowi i najczęściej są zależne od wzajemnego<br />

oddziaływania stykających się powierzchni. Wynika z tego, że tarcie jest jedną z sił kontaktowych<br />

i zwykle jest ono styczne do oddziałujących ze sobą powierzchni a wartość siły tarcia jest funkcją<br />

siły normalnej do powierzchni trących oraz chropowatości powierzchni 3 .<br />

F T = k N gdzie N siła oddziaływania bloku z podłożem (prostopadłą) do powierzchni trących<br />

Rysunek 23: Siły działające na ciało, które powodują zmianę<br />

prędkości ciała.<br />

W kolejnej symulacji wyżej wymieniona siła będzie odgrywać kluczową rolę, gdyż to<br />

właśnie ona znacząco wpływa na ruch ciała powodując większe wytracanie prędkości a tym samym<br />

czyni <strong>zjawisk</strong>o bardziej realistycznym.<br />

Gdy bryła sztywna porusza się w górę równi pochyłej przyspieszenie ma stałą wartość i wynosi<br />

a= g cossin <br />

a prędkość maleje liniowo zczasem.<br />

Przejdźmy zatem do serca programu. Algorytm całkowania równań ruchu zbudowany został<br />

w oparciu o klasyczną metodę Eulera i wykonywany jest według poniższego schematu.<br />

v tdt =v x<br />

tdt at<br />

xtdt=x tdt v tdt <br />

Nie uwzględnia on wyliczania w każdym kroku czasowym przyspieszenia gdyż przyjmuje ono stałą<br />

wartość.<br />

Aby sprawdzić jak kształtuje się długość drogi hamowania w zależności od kąta nachylenia drogi<br />

2 b d skrót od ang. braking distance - droga hamowania<br />

3 Chropowatość powierzchni jest mierzona zazwyczaj w mikrometrach<br />

32


do poziomu wystarczy sporządzić jej wykres (patrz plik sd_wyniki_wykres.py).<br />

Rysunek 24: Zależność drogi hamowania od kąta nachylenia jezdni do poziomu. Przecięcie zielonych linii<br />

obrazuje rozwiązanie dla wprowadzonych przez użytkownika parametrów początkowych (patrz rys.<br />

poniżej)<br />

Rysunek 25: Wyznaczenie drogi hamowania dla bryły sztywnej.<br />

33


Ruch postępowo obrotowy na równi<br />

Jeżeli na równi pochyłej ustawimy walec [3],[8],[9] i puścimy swobodnie, zacznie się obracać i<br />

zjeżdżać. Będzie się tak działo tylko wtedy gdy współczynnik tarcia będzie miał odpowiednią<br />

wartość. Jeżeli nie byłoby tarcia lub byłoby ono zbyt małe, walec nie staczał by się, lecz zsuwał po<br />

równi a jego prędkość kątowa u podnóża równi mogłaby być nawet równa zeru. Przyjmijmy<br />

lokalny układ współrzędnych w którym oś x-ów jest równoległa po płaszczyzny równi a oś y-ów<br />

jest do niej prostopadła. Wówczas równania ruchu dla walca znajdującego się na równi pochyłej<br />

przyjmują następującą postać.<br />

∑ F y local<br />

=mg cos−mg cos=m a y<br />

∑ F xlocal<br />

=mg sin −T =m a x<br />

gdzie T - wartość siły tarcia a x -<br />

przyspieszenie liniowe środka ciężkości<br />

walca<br />

Gdy siła sprężystości równoważy siłę<br />

nacisku m a y<br />

=0 . Zakładamy także ,<br />

że walec stacza się bez poślizgu.<br />

Ruch obrotowy dookoła środka masy<br />

opisany jest równaniem<br />

= I śr.m. <br />

Obrotu walca nie powoduje jednak ani<br />

siła sprężystości F s ani F c , gdyż<br />

obie przechodzą przez środek masy i<br />

ich moment wypadkowy jest równy<br />

zeru. Moment siły różny od zera daje<br />

tylko siła tarcia T , której ramię jest równe promieniowi walca. Możemy więc zatem napisać:<br />

F R=I śr.m. <br />

moment bezwładności walca jest równy I śr.m.<br />

= 1 2 m R2 ,<br />

Rysunek 26: Rozkład sił działających na walec staczający się po<br />

równi pochyłej.<br />

a przyspieszenie kątowe = a R<br />

<br />

więc F =I śr.m.<br />

R = m a<br />

2<br />

Przyspieszenie walca na równi obliczymy podstawiając powyższe równanie do drugiego równania<br />

ruchu (dla osi x-ów). W rezultacie otrzymamy a= 2 3 g sin .<br />

Prędkość środka masy przy założeniu zerowej prędkości początkowej jest określona równaniem<br />

v= 4 3 g h gdzie h jest wysokością równi. 34


Rysunek 27: Symulacja ruchu walca na równi pochyłej.<br />

Poślizg kuli przy pchnięciu techniką bilardową.<br />

Kolejna symulacja ilustruje przykład z życia codziennego. Przy pchnięciu kuli dokładnie w środek<br />

masy wektor popędu jest dokładnie poziomy i przechodzi przez środek kuli. Tuż po uderzeniu kula<br />

bilardowa posiada liniową prędkość<br />

początkową, lecz po krótkim czasie<br />

zaczyna się obracać a chwilę później<br />

kontynuuje ruch bez poślizgu (toczy się).<br />

Niniejsza symulacja pozwoli wyznaczyć<br />

odległość na jaką przemieści się kula<br />

zanim przestanie się ślizgać [7],[8]. Tak jak<br />

w poprzednich przypadkach zostały<br />

poczynione założenia upraszczające.<br />

Przyjmujemy, że kula nie odkształca się<br />

podczas trwania uderzenia a popęd siły ma Rysunek 28: Pchnięcie kuli techniką bilardową.<br />

stałą wartość. Uderzenie kończy się<br />

nadaniem kuli liniowej prędkości początkowej v 0 , również stałą wartość ma współczynnik tarcia<br />

kinetycznego między kulą a podłożem. Przyspieszenie środka masy kuli jest stałe,ponieważ<br />

35


wszystkie działające siły są stałe. Dlatego też dla ruchu postępowego możemy napisać:<br />

F =m a=m v – v i f<br />

t przy czym v i<br />

=v 0 i v f = f R<br />

a v i , v f oznaczają odpowiednio liniową prędkość początkową i końcową<br />

Siła wypadkowa F = k m g<br />

<br />

tak, że k<br />

mg=m v – v i f<br />

t<br />

Przyspieszenie kątowe dookoła osi przechodzącej przez środek masy jest stałe, tak że dla ruchu<br />

obrotowego możemy napisać:<br />

= I =I f – i<br />

t<br />

k<br />

m g R= 2 5 m R2 v f<br />

Rt<br />

jeżeli i=0 to = I v f<br />

Rt<br />

liniowa prędkość końcowa będzie dana zależnością v f<br />

= 5 7 v i<br />

czas trwania poślizgu t= v −v i f<br />

g = 2 v i<br />

7 k<br />

g<br />

odległość na jaką przemieści się kula bilardowa zanim przestanie się ślizgać jest określona<br />

równaniem x= 12<br />

2<br />

v i<br />

49 k<br />

g .<br />

W analogiczny sposób została skonstruowana symulacja ilustrująca wyznaczenie drogi poślizgu<br />

krążka mającego początkową prędkość kątową, który puszczamy swobodnie na poziomą<br />

powierzchnię. (patrz plik z_piskiem_opon_04.py).<br />

Rysunek 29: Symulacja ilustrująca wyznaczanie drogi poślizgu przy pchnięciu<br />

kuli techniką bilardową.<br />

Przytoczone przykłady ilustrują istotny aspekt zagadnień kinetyki ciała sztywnego a mianowicie<br />

rozważając ruch obrotowy bryły sztywnej, trzeba pamiętać nie tylko o o kierunku i wielkości<br />

działającej siły, lecz także o punkcie jej przyłożenia.<br />

36


6. Modelowanie obiektów (część 3)<br />

Obiekt zmieniający geometrię w czasie.<br />

Dotychczas symulowane były różne aspekty ruchu punktów materialnych i brył sztywnych, teraz<br />

zaś głównym celem jest pokazanie jak postępować ze zbiorem punktów materialnych i sprężyn.<br />

Systemy złożone z dużej ilości punktów materialnych mogą służyć między innymi symulacji<br />

tkanin[3], ciał stałych sprężystych [F], przydatne także się stają przy modelowaniu włosów, trawy,<br />

ciał sypkich na przykład piasku [E], a nawet dymu czy ognia[5].<br />

Zastępowanie ciał sztywnych przez zbiory punktów materialnych ułatwia zadanie, gdyż nie musimy<br />

zajmować się ruchem obrotowym i jego równaniami co jest niezbędne przy ruchu brył sztywnych.<br />

Krok taki ułatwia nie tylko obliczanie sił i całkowanie równań ruchu, lecz również zdecydowanie<br />

upraszcza obliczenia dotyczące zderzeń, ponieważ mamy jedynie do czynienia z popędem w ruchu<br />

postępowym.<br />

Niewątpliwie ogromnym źródłem informacji dla osoby rozpoczynającej przygodę z symulowaniem<br />

<strong>zjawisk</strong> <strong>fizycznych</strong> może stanowić zestaw standardowych skryptów jakie są dostępne w katalogu<br />

C:\Program Files\Python23\Lib\site-packages\visual\demos\ 4<br />

Dlatego kolejna symulacja stanowi tylko modyfikację standardowego skryptu o nazwie drape.py<br />

Przedmiotem symulacji będzie uproszczony model sznura opadającego na przeszkodę, którą będzie<br />

kilka kul. Linę zbudowana będzie z punktów materialnych każdy o masie m połączonych przy<br />

pomocy elementu sprężynująco-tłumiącego. Dla zwiększenia szybkości obliczeń pominięta została<br />

wizualizacja dla mas punktowych, zaś elementy łączące te punkty przedstawione będą przy pomocy<br />

prostoliniowych odcinków. Przejdźmy zatem do omówienia głównej struktury programu. Na<br />

początek definiujemy stałe takie jak masa punktu materialnego, przyspieszenie grawitacyjne, krok<br />

czasowy symulacji, współczynnik sprężystości oraz tłumienia.<br />

restlength = 0.1 #dlugosc pojedynczego elementu<br />

m = 0.005 * restlength #masa pojedynczego elementu<br />

g = 9.81 #[m/s^2]<br />

dt = 0.003 #krok czasowy symulacji<br />

k = 3.5 #wspolczynnik sprezystosci<br />

damp = 0.992#wspolczynnik tlumienia<br />

Kolejno wykonujemy obiekt przedstawiający linę, który w tym przypadku jest krzywą. Oraz<br />

nadajemy każdemu punktowi tej krzywej atrybut fizyczny - pęd początkowy równy zeru.<br />

band = curve( x = arange(-1,1,restlength), y = 1,z = 0, color=color.black,radius<br />

= 0.01)<br />

band.p = band.pos * 0 #nadanie pedow poczatkowych<br />

Jak wspomniane było wcześniej, przeszkodę na którą opadać będzie sznur, stanowić będzie kilka<br />

sfer, które teraz wykonamy. Oczywiście położenie każdej ze sfer jest dowolne, niemniej pamiętać<br />

należy by wszystkie kule umieszczone zostały w jednej liście [4], by później możliwy był łatwy<br />

dostęp do atrybutu położenia każdej z nich.<br />

#inicjowanie przeszkody dla sznura<br />

spheres = []<br />

4 Katalog Program Files jest opcjonalny i przy niektórych wersjach Pythona nie zaleca się używania tego katalogu<br />

przy instalacji języka.<br />

37


for i in range(nspheres):<br />

for j in range(nspheres):<br />

s = sphere( pos = (i*0.4+0.1*j,0.1 + i*0.1-0.3*j,-0.01+0.2*j),<br />

radius = 0.25,<br />

color = color.yellow )<br />

spheres.append( s )<br />

W takiej właśnie symulacji zachowania się zbioru punktów materialnych sprężyny i tłumiki drgań<br />

odgrywają ważną rolę. Siły sprężystości łączą poszczególne punkty materialne ze sobą i utrzymują<br />

je w pewnej odległości. Tłumiki zapewniają łagodność ruchów eliminując gwałtowne drgania<br />

i wstrząsy. Tłumiki są też bardzo istotne z punktu widzenia stabilności numerycznej symulacji.<br />

Zobaczmy jak skonstruowana jest pętla odpowiedzialna za ruch obiektu.<br />

W każdym kroku czasowym pęd każdego z punktów materialnych zostaje zmniejszony o pewną<br />

wartość czyli nasz sznur porusza się w ośrodku tłumiącym jego ruch. Jak widać istnieje korelacja<br />

pomiędzy krokiem czasowym symulacji i oporem. Niemniej jednak w taki sposób będzie działał<br />

tłumik drgań.<br />

band.p = band.p * damp #tlumienie ruchu<br />

#w kazdym kroku czasowym<br />

Aby nieco uatrakcyjnić symulację załóżmy że jeden<br />

z końców liny jest przywiązany czyli pierwszy<br />

punk materialny będzie mieć zawsze pęd równy<br />

zeru.<br />

band.p[0] = 0# zaczepienie w punkcie<br />

startu punktu o numerze 0 (wyzerowanie<br />

pedu)<br />

Oczywiście w ten sam sposób możemy<br />

„przywiązać” dowolny punkt liny i tak na przykład<br />

chcąc unieruchomić ostatni element liny napiszemy<br />

band.p[-1] = 0<br />

Rysunek 30: Numerowanie elementów listy i tak<br />

element listy o numerze1 lub -4 odnosi się do punktu<br />

materialnego numer 2.<br />

Gdy zaś pominiemy tę opcję całkowicie spowodujemy że cała lina będzie opadać na przeszkodę.<br />

Jak zawsze w każdym kroku czasowym musi zostać zaktualizowana pozycja każdego z punktów<br />

p th<br />

materialnych. Stosując klasyczną metodę Eulera możemy napisać xth=xt h<br />

m<br />

gdzie h jest krokiem czasowym symulacji (dyskretnym przyrostem czasu)<br />

Na każdy z punktów działa siła grawitacji więc pęd każdego z punktów w każdym kolejnym kroku<br />

czasowym można zapisać jako m v th =mv tm hat<br />

band.pos = band.pos + band.p/m*dt #wyznaczenie nowego polozenia<br />

band.p[:,1] = band.p[:,1] - m * g * dt # p=p-m*g*t wyznaczenie nowych pędów<br />

kolejno przystępujemy do wyznaczenia wzajemnych odległości pomiędzy punktami najpierw<br />

w formie wektorów (każdego punktu z każdym) a następnie zamianie odległości wektorowych na<br />

skalary. Krok taki był wykonywany przy wyznaczaniu położeń między planetami więc w tym<br />

przypadku omówienie zostanie pominięte. Należy tylko pamiętać iż operacje takie są wykonywane<br />

na tablicach, które nie transformują się jak macierze.<br />

length = (band.pos[1:] - band.pos[:-1])#odleglosci pomiedzy punktami<br />

dist = sqrt(sum(length*length,-1))#wartosci odleglosci w formie listy<br />

38


W połączeniu między punktami działa siła zgodna z prawem Hook'a F =kx więc siły (skalarnie)<br />

działające pomiędzy każdymi dwoma punktami materialnymi wyznaczymy w następujący sposób:<br />

force = k * ( dist - restlength ) #F=k*x (skalarne)<br />

by zaś dostać siły w formie wektorów [2] musimy wartość poprzednio obliczonych sił pomnożyć<br />

przez wektory jednostkowe:<br />

force = length/dist[:,NewAxis] * force[:,NewAxis]<br />

Ostatnim krokiem pętli ruchu jest aktualizacja pędów. Z doświadczenia wiemy, że pomiędzy<br />

dowolną parą sąsiednich punktów materialnych w takiej linie działają siły naprężenia równe co do<br />

wartości lecz o przeciwnych zwrotach i punktach przyłożenia. Wyznaczyliśmy poprzednio n−1<br />

sił w formie wektorów, lecz punktów materialnych mamy n . Musimy zatem w oparciu o te<br />

wiadomości zaktualizować pędy wszystkich punktów materialnych. I tak od pierwszego punktu<br />

materialnego (mającego indeks z numerem zero - na liście - patrz rysunek wcześniejszy) do<br />

przedostatniego (włącznie) zapiszemy<br />

p th=p t F th<br />

band.p[:-1] = band.p[:-1] +<br />

force*dt<br />

i analogicznie od drugiego punktu Rysunek 31: Pary sił działające na poszczególne elementy liny.<br />

materialnego (mającego indeks<br />

z numerem 1 - na liście rys wcześniejszy) do ostatniego (włącznie) zapiszemy<br />

p th=p t − F th<br />

band.p[1:] = band.p[1:] - force*dt<br />

Pozostaje jeszcze kolizja z podłożem i przeszkodą. Zacznijmy od podłoża.<br />

Kontrola detekcji z podłożem jest bardzo prosta i sprowadza się do sprawdzania czy y-grekowa<br />

składowa któregokolwiek punktu materialnego nie jest mniejsza niż jest to określone przez poziom<br />

zerowy (czyli podłoże na które upadnie sznur). W przypadku gdy owa współrzędna jest mniejsza<br />

niż ten poziom, następuje zmiana jej wartości do poziomu zerowego oraz przypisanie tej składowej<br />

pędu równego zeru.<br />

if floor:<br />

#UDERZENIE O PODLOZE<br />

below = less(band.pos[:,1],poziom_zero)#below-ponizej<br />

band.p[:,1] = where( below, 0, band.p[:,1] )<br />

#dla kazdego punktu znajdujacgo sie ponizej poziom_zero ustaw p=0<br />

band.pos[:,1] = where( below, poziom_zero, band.pos[:,1] )<br />

#dla kazdego punktu znajdujacgo sie ponizej poziom_zero ustaw y=0<br />

Detekcja kolizji sznura ze sferą sprowadza się do sprawdzania czy którakolwiek ze składowych<br />

poszczególnych punktów materialnych nie znalazła się w odległości mniejszej niż promień sfery.<br />

W przypadku przekroczenia tej odległości korygowane jest położenie tego punktu materialnego do<br />

powierzchni sfery. Sama procedura wyznaczania miejsc dopuszczalnych i zakazanych dla ruchu<br />

sznura dotyczy wszystkich sfer jednocześnie i opiera się o bardzo wydajną metodę nakładania<br />

masek. Nieco bliżej problem ten zostanie omówiony przy symulacji gazu doskonałego. Zderzenie<br />

sznura z kulą jest niesprężyste i dlatego nie dochodzi do odbicia kolidujących punktów<br />

materialnych od sfery. Odbicie takie jest realizowane przez rozłożenie wektora pędu na dwa<br />

39


składowe wektory. Jeden prostopadły (normalny) do powierzchni sfery w punkcie kolizji oraz drugi<br />

styczny do tej powierzchni. Aby uzyskać zderzenie niesprężyste należy po prostu wyeliminować<br />

wektor normalny drugi natomiast podstawić jako wektor pędu tegoż punktu. I tak w najprostszym<br />

przypadku gdy którykolwiek z punktów uderzy z prędkością skierowaną dokładnie w środek sfery<br />

jego pęd zostanie całkowicie wygaszony.<br />

Rysunek 32: Symulacja kolizji ciała giętkiego z przeszkodą.<br />

Autorzy skryptu zadbali także o wizualizację naprężeń w sznurze powstających w wyniku kolizji<br />

z obiektem jak również powstających na skutek naciągnięcia liny spowodowanego działaniem jej<br />

własnego ciężaru.<br />

Wykresy – tworzenie histogramu<br />

Powinno by oczywiste, że poważna analiza statystyczna eksperymentu wymaga wykonania wielu<br />

zliczeń, pomiarów, etc. Oznaczać to będzie, że musimy zaprezentować dużą liczbę danych na<br />

jednym wykresie - histogramie. Jako przykład rozważmy grupę studentów zdających egzamin [12].<br />

Maksymalna liczba punktów jaką można zdobyć wynosi 50. Studenci otrzymują następujące<br />

wyniki:<br />

26, 33, 38, 41, 49, 28, 36, 38, 47, 41<br />

32, 37, 48, 44, 27, 32, 34, 44, 37, 30<br />

(Oceny te przepisano z ułożonej alfabetycznie listy studentów).<br />

Poniżej został przedstawiony histogram ocen, w którym przyjęto jako granice przedziałów<br />

następujące liczby: 25, 30, 35, 45, 50. Pionowa skala jest tak dobrana, że pole powierzchni każdego<br />

prostokąta wyraża ułamek liczby studentów zaliczających się do danego przedziału.<br />

40


Rysunek 33: Histogram ocen uzyskanych przez studentów.<br />

Symulacja rzutu monetą<br />

W wyniku przeprowadzania eksperymentu rzutu monetą oczekuje się, że połowa z nich będzie<br />

reszką, mimo tego iż możliwe jest uzyskanie dowolnej liczby reszek (całkowita liczba wyrzuconych<br />

reszek zawiera się od zera do liczby rzutów). Działanie skryptu rzut_moneta.py [1] polega na<br />

uruchamianiu eksperymentów, które symulują liczbę rzutów dla danego eksperymentu. W wyniku<br />

takiego doświadczenia otrzymujemy za każdym razem pewną liczbę reszek z <strong>zakresu</strong><br />

0iliczba rzutów . Wyniki następnie są zliczane i kumulowane w liście reszki[]. Zwięzłość<br />

programu uzyskana została przez zastosowanie metody indeksowania za pomocą wyliczanej<br />

wartości (liczba reszek staje się numerem indeksu listy zawierającej krotności występowania<br />

reszki). Ponadto program może drukować histogram w postaci tekstowej lub graficznej – z użyciem<br />

biblioteki VPython. Histogram ten za każdym razem powinien przypominać kształt krzywej Gaussa<br />

i rozciągać się wokół połowy liczby rzutów.<br />

Rysunek 34: Przykład działania skryptu rzut_monetą.<br />

41


eszki (liczebność wyniku):<br />

[0, 0, 2, 5, 18, 35, 58, 90, 96, 82, 56, 40, 12, 4, 0, 2, 0]<br />

histogram tekstowy:<br />

0<br />

1<br />

2 *<br />

3 *<br />

4 * *<br />

5 * * * *<br />

6 * * * * * *<br />

7 * * * * * * * * *<br />

8 * * * * * * * * * *<br />

9 * * * * * * * * *<br />

10 * * * * * *<br />

11 * * * *<br />

12 * *<br />

13 *<br />

14<br />

15 *<br />

16<br />

n_k<br />

f<br />

0.00 0.0000<br />

1.00 0.0000<br />

2.00 0.0040<br />

3.00 0.0100<br />

4.00 0.0360<br />

5.00 0.0700<br />

6.00 0.1160<br />

7.00 0.1800<br />

8.00 0.1920<br />

9.00 0.1640<br />

10.00 0.1120<br />

11.00 0.0800<br />

12.00 0.0240<br />

13.00 0.0080<br />

14.00 0.0000<br />

15.00 0.0040<br />

16.00 0.0000<br />

gdzie n_k - krotność występowania, f – częstość z jaką występuje reszka dla danej liczby<br />

eksperymentów<br />

Podsumowując informacje na temat wykresów widzimy, że okno wykresu jest blisko spokrewnione<br />

z oknem 3D. Główną różnicą jest to, że wykres jest zasadniczo dwu-wymiarowy i posiada<br />

nieznormalizowaną skalę dla osi x i y. Istotnym ułatwieniem jest to, że kiedy tworzymy wykres<br />

(obojętnie, czy zastrzegamy jaki to ma być typ wykresu, czy też nie) a następnie wywołujemy<br />

zwykły obiekt z biblioteki Visual taki jak box czy sphere, będzie on poprawnie skojarzony z<br />

oknem 3D (display window), a nie jak by się mogło wydawać z oknem gdisplay. Dzieje się tak<br />

dlatego, że zainicjowane okno wykresu jest zapisywane a dostęp do niego uzyskujemy dopiero<br />

wówczas gdy dokonujemy kreślenia wykresu.<br />

42


7. Wiele ciał w przestrzeni<br />

Ruchy planet<br />

Ruch ciała wokół nieskończenie ciężkiej planety<br />

Kolejna grupa symulacji dotyczy ruchów ciał w polu grawitacyjnym [7]. Prześledźmy zatem<br />

główne etapy konstrukcji takiej symulacji. Na początek zakładamy, że jedna z planet jest<br />

nieskończenie ciężka to znaczy nie będziemy uwzględniać jej ruchu. Wpływ innych ciał także<br />

pomijamy. Przyjmując za punkt wyjścia prawo powszechnego ciążenia oraz zasady dynamiki<br />

Newtona spróbujemy wyznaczyć numerycznie trajektorię niewielkiego ciała, które będzie się<br />

poruszać z pewną prędkością początkową.<br />

Na podstawie podobieństwa trójkątów stwierdzamy, że składowa siły w kierunku poziomym ma się<br />

tak do całkowitej siły jak pozioma współrzędna x do przeciwprostokątnej czyli<br />

F x<br />

=−∣F∣ x r<br />

.<br />

Rysunek 35: Siły działające na ciało znajdujące się w polu grawitacyjnym<br />

planety.<br />

Korzystając z prawa powszechnego ciążenia możemy napisać, że składowa ta równa się iloczynowi<br />

masy planety 2 i szybkości zmiany jej prędkości w kierunku x<br />

m dv x<br />

dt =−GMm x r 3<br />

podobnie postępując uzyskamy zależności na składową siły w kierunku y oraz z. Promień<br />

r wyznaczymy następująco r= x 2 y 2 z 2 .<br />

Aby dodatkowo uprościć rachunki przyjmujemy, że GM =1 .<br />

Prześledzimy trajektorię planety przyjmując jako warunki początkowe: położenie planety w chwili<br />

startu p 2<br />

=0.5 i0.0 j0.0k , prędkość v 2<br />

=0.0 i1.63 j0.0 k , krok czasowy symulacji<br />

dt=0.1 . Dobranie takich parametrów zagwarantuje nam, że ruch będzie się odbywał w<br />

płaszczyźnie x y. Ostatecznie przyspieszenie w kierunku osi x wynosić będzie a x<br />

=− x r 3<br />

a w<br />

43


kierunku y a y<br />

=− y r 3 odległość zredukuje się do postaci r= x 2 y 2<br />

Stosując zmodyfikowaną metodę Eulera otrzymamy<br />

dt<br />

x tdt=x t dt vt 2 <br />

dt<br />

vt 2 =v dt<br />

t−<br />

2 dt a t<br />

at =− x r 3<br />

v dt<br />

2 =vt 0 dt<br />

2 at 0 <br />

a prędkość pośrednia dla chwili początkowej<br />

Zatem schemat działania algorytmu będzie wyglądał następująco. W pierwszej kolejności<br />

wyznaczamy przyspieszenie działające na ciało w punkcie startu następnie, prędkość pośrednią to<br />

dt<br />

jest w chwili czasu równej<br />

2<br />

a=− x r<br />

v 3<br />

dt<br />

2 =vt 0 dt<br />

2 at 0<br />

W każdym następnym kroku czasowym dokonywane są obliczenia nowego położenia,<br />

przyspieszenia działającego na ciało oraz prędkości pośredniej.<br />

dt<br />

x new<br />

= xtdt vt 2 <br />

a=− x new<br />

∣x new ∣ 3<br />

dt<br />

v<br />

t<br />

2 =v dt<br />

t−<br />

2 dt at <br />

Algorytm dla zmodyfikowanej metody Eulera wygląda następująco:<br />

def euler(obj):<br />

"""Funkcja wylicza a,v,x na podstawie param.pocz.<br />

Zmodyfikowana metoda Eulera"""<br />

if czas==0:<br />

#dla chwili poczatkowej<br />

obj.acc=-obj.frame.pos/mag(obj.frame.pos)**3<br />

obj.v_posrednia = obj.velocity + (dt/2)*obj.acc<br />

#podstawienie nowych wartosci polozenia i predkosci<br />

obj.v_posrednia = vector(obj.v_posrednia)<br />

else:<br />

#dla nastepnych krokow czasowych<br />

S_new = obj.frame.pos + obj.v_posrednia * dt<br />

obj.acc = -(S_new*(1/(mag(S_new)**3)))<br />

obj.v_posrednia = obj.v_posrednia + obj.acc * dt<br />

#podstawienie nowych wartosci polozenia i predkosci<br />

obj.frame.pos=vector(S_new)<br />

obj.velocity=vector(obj.v_posrednia)<br />

return<br />

44


Wyniki symulacji można porównać z danymi precyzyjnymi które wyznaczane są przy pomocy<br />

funkcji drukuj_dane_precyzyjne(). W przypadku gdy m 1<br />

≫m 2 masę zredukowaną <br />

przyjmujemy jako ≈m 2 . Moment pędu układu dwu punktów materialnych w ruchu względem<br />

układu środka masy określamy jako [6],[8],[9]:<br />

J S =r× ˙r<br />

zaś energia kinetyczna układu dwu punktów materialnych w ruchu względem układu środka masy:<br />

E S = v2<br />

2 − gdzie zgodnie z przyjętymi założeniami =G m 1 m 2<br />

r<br />

wyznaczane są następująco:<br />

a= − ,<br />

2 E<br />

b= S 2<br />

−JS<br />

2 E S ,<br />

c=a 2 – b 2<br />

r max<br />

=ac ,<br />

r min<br />

=a−c ,<br />

okres obiegu planety T period<br />

=<br />

2 ab<br />

J S .<br />

Korzystając ze wzorów na parametry elipsy otrzymujemy 1 4<br />

a następnie wzoru na energię<br />

E S =− <br />

2 a ,<br />

uzyskamy zależność<br />

2<br />

T period<br />

= 22 a 2<br />

= 42 a 3<br />

E S <br />

a po uwzględnieniu założeń<br />

2<br />

=4 2 a 3 .<br />

T period<br />

J S <br />

T 2 2 period<br />

=− 2 a 2 J S 2<br />

2 E S <br />

. Parametry elipsy<br />

Proporcjonalność T 2 ~a 3 jest dla ruchów planet trzecim prawem Keplera. Widać wyraźnie, że to<br />

prawo nawet w mechanice nierelatywistycznej obowiązuje jedynie w przybliżeniu. Jednakże to<br />

przybliżenie jest bardzo dobre ze względu na to, że warunek m 1<br />

≫m 2 dla masy Słońca m 1 i<br />

masy planety m 2 spełniony jest bardzo wyraźnie.<br />

45


Rysunek 36: Symulacja ruchu niewielkiego ciała wokół planety. Czerwone kropki obrazują kolejne położenia planety.<br />

Uwaga:<br />

W przykładzie wykorzystano ruch po krzywej w płaszczyźnie x y, lecz przy tak zdefiniowanych<br />

algorytmach ruchu trajektoria może być krzywą zorientowaną dowolnie w trzech wymiarach<br />

niemniej jednak trajektoria takiego ciała jest krzywą płaską (2D).<br />

46


Prawo pól Keplera<br />

Ruch w czasie którego jest zachowany moment pędu:<br />

J =m r×v =const<br />

ma dodatkowo w mechanice nierelatywistycznej, ze względu na stałość masy, tę własność, że:<br />

J<br />

2m = 1 r×v =const<br />

2<br />

gdzie wektor r×v / 2 jest nazywany prędkością polową punktu materialnego[6],[8],[9]. Nazwa<br />

ta wywodzi się stąd, że wartość bezwzględna tego wektora pomnożona przez dt równa jest<br />

∣r× ds∣/2 gdyż ds=v dt , a zatem przedstawia pole trójkąta zakreślone przez r w ciągu<br />

t<br />

1<br />

czasu od t do tdt . Całka ∫ ∣r ×v∣dt daje więc pole zawarte pomiędzy wektorami<br />

2<br />

t 0<br />

r t 0<br />

, r t oraz łukiem toru w przedziale czasu 〈t 0<br />

, t〉 . Wywnioskować zatem można, że<br />

t<br />

J<br />

2m t – t = 1 ∫ 0<br />

∣r×v∣dt<br />

2<br />

t 0<br />

to znaczy pole rośnie liniowo z czasem, gdy J =const. , co stanowi jednocześnie treść drugiego<br />

prawa Keplera. Wizualizację pola powierzchni zakreślanego przez promień wodzący w czasie<br />

jednostkowego czasu (miesiąca) uzyskać możemy uruchamiając funkcję KEPLER().<br />

Rysunek 37: Pole powierzchni zakreślane przez promień<br />

wodzący w jednostkowym czasie.<br />

Ruch ciała w polu dowolnej siły centralnej.<br />

Przyjmując identyczne założenia jak poprzednio zobaczymy jak wygląda ruch w polu siły<br />

centralnej na przykład F ~ 1 . W ruchu takim obowiązują identyczne zasady zachowania jak<br />

r<br />

poprzednio. Wartość energii jest taka, że ruch odbywa się w przedziale 〈r 1,<br />

r 2<br />

〉 . oznacza to, że w<br />

płaszczyźnie prostopadłej do J ruch odbywa się wewnątrz pierścienia ograniczonego przez<br />

okręgi o promieniach r 1 i r 2 . Co więcej, w odległości r 1 i r 2 od centrum siły wartość<br />

prędkości radialnej równa jest zeru v r<br />

=0 . Zatem tor takiego ciała jest styczny do obu okręgów<br />

ograniczających pierścień dozwolonego ruchu [6],[9] (patrz plik ruch_w_pol_centr.py). Gdy<br />

symulacja będzie trwała odpowiedni okres czasu zobaczymy, że trajektoria planety przebiega przez<br />

47


obszar takiego właśnie pierścienia.<br />

Rysunek 38: Ruch w polu siły centralnej proporcjonalnej do 1/r<br />

Układ podwójny planet.<br />

Nie wszystkie układy planet zachowują się jak te przedstawione w poprzednich symulacjach.<br />

Istnieje bowiem cała gama planet o porównywalnych masach tzw. planet podwójnych. Przykładu<br />

takiego nie należy szukać zbyt daleko gdyż porównywalne rozmiary i masy Ziemi i Księżyca<br />

skłaniają nas do traktowania obydwu tych fizycznie związanych obiektów jako planety podwójnej.<br />

W pozostałych przypadkach z jakimi mamy do czynienia w układzie planetarnym Księżyce są<br />

tworami innej rangi niż ich macierzyste planety. Planeta podwójna Ziemia-Księżyc ma masę<br />

6,0485⋅10 24 kg z czego na Ziemię przypada 98,78%. Średnica Księżyca wynosi ponad 27%<br />

średnicy ziemskiej. Dzięki silnym więzom grawitacyjnym obydwa ciała obiegają wspólny środek<br />

masy w okresie miesiąca gwiazdowego ( 27 d ,3217 ) . Środek masy układu Ziemia-Księżyc<br />

znajduje się wewnątrz Ziemi, średnio 4671 km od jej środka w kierunku Księżyca. Kolejna<br />

symulacje jakie zostaną zaprezentowane uwzględniają oddziaływania grawitacyjne pomiędzy<br />

obiektami o porównywalnych masach. W analogiczny sposób zostały w nich zaimplementowane<br />

algorytmy rozwiązywania równań różniczkowych zwyczajnych metodą Eulera (patrz plik<br />

ruch_planet_podw_A.py). W jednej z nich parametry planet zostały tak dobrane aby środek masy<br />

układu pozostawał w spoczynku, w drugiej natomiast porusza się ze stałą prędkością.<br />

48


Rysunek 39: Symulacja trajektorii ruchu dla układu planet podwójnych. Środek masy znajduje się w spoczynku (z<br />

lewej) oraz środek masy porusza sie ruchem jednostajnym (z prawej).<br />

Ruch wielu planet z uwzględnianiem wzajemnego oddziaływania<br />

grawitacyjnego między sobą.<br />

Nieco trudniejszym zagadnieniem jest rozpatrywanie ruchów planet wchodzących w skład<br />

niewielkiego układu planetarnego na przykład złożonego z trzech lub więcej ciał<br />

o porównywalnych masach [7]. W każdym kroku czasowym będzie do wykonania większa ilość<br />

obliczeń, dlatego korzystanie z tradycyjnych metod może okazać się nieefektywne. Aby<br />

przyspieszyć nieco obliczenia do przechowywania danych wykorzystane zostaną tablice. Zobaczmy<br />

jak wygląda główna struktura algorytmu ruchu.<br />

Rozpoczynamy od określenia liczby ciał wchodzących w skład układu planetarnego oraz określenia<br />

ich położeń, mas i prędkości początkowych. Oprócz tego opcjonalnie możemy nadawać inne<br />

atrybuty na przykład nazwy poszczególnych ciał, określić kolory etc.<br />

#liczba cial bioracych udzial w symulacji<br />

n_of_bodies = 3<br />

# nadanie polozen poczatkowych<br />

pos=array([[-2.5,0.0,0.0],[-1.136366, -0.00017964, 0.],[2.5,0.0,0.0]])<br />

# nadanie mas<br />

lista_mas=[30.0, 0.01, 25.0]<br />

m = array(lista_mas)<br />

#nadanie predkosci poczatkowych<br />

vel=array([[0.0,-1.8,0.0],[0.0,-5.4,3.5],[0.,2.16,0.0]])<br />

Należy pamiętać, że listy zawierające parametry początkowe muszą ściśle odpowiadać liczbie ciał<br />

biorących udział w symulacji i tak zmieniając liczbę ciał do 4 musimy zmodyfikować listę położeń<br />

na przykład do postaci:<br />

pos=array([[-2.5,0.0,0.0],[-1.136366,0.00017964,0.], [2.5,0.0,0.0],<br />

[3.5,0.0,1.0]])<br />

W analogiczny sposób postępujemy z pozostałymi listami zawierającymi parametry początkowe.<br />

Przyjrzyjmy się nieco bliżej funkcji big_movable() odpowiedzialnej za wyznaczanie przyspieszenia<br />

prędkości i położenia w każdym kroku czasowym symulacji. Dla naszego układu planetarnego<br />

49


ównanie ruchu przybiera następującą formę:<br />

N<br />

d v<br />

m<br />

i<br />

i<br />

dt = ∑ − G m m x – i j i x j<br />

3<br />

j=1 r ij<br />

Symbol ∑ oznacza sumowanie po wszystkich wartościach j (po wszystkich pozostałych<br />

ciałach niebieskich) z wyłączeniem j=i , natomiast r ij to odległość między i-tą i j-tą planetą,<br />

którą wyznaczamy w następujący sposób:<br />

r ij<br />

= x i<br />

– x j<br />

2 y i<br />

– y j<br />

2 z i<br />

– z j<br />

2<br />

Więc przyspieszenie i-tej planety będzie wynosiło:<br />

N<br />

a i<br />

=∑<br />

3<br />

j=1 r ij<br />

Stosując zmodyfikowaną metodę Eulera otrzymamy:<br />

v i<br />

tdt /2= v i<br />

t−dt/2 dt a i<br />

t<br />

x i<br />

tdt= x i<br />

tdt v i<br />

tdt /2<br />

a prędkość pośrednia dla chwili początkowej<br />

v i<br />

dt /2=v i<br />

t 0<br />

dt /2 a i<br />

t 0<br />

<br />

Zobaczmy jak wygląda implementacja takiego algorytmu. Dla chwili początkowej tj. t=0 s<br />

musimy wyznaczyć przyspieszenie działające na każdą z planet. Zatem w pierwszej kolejności<br />

obliczane są wzajemne odległości między tymi obiektami, najpierw w postaci wektorów a następnie<br />

w postaci skalarów.<br />

− G m j x i – x j <br />

def big_movable():<br />

global pos,vel,vel_posrednia,acc<br />

if czas==0:<br />

b=pos[:,NewAxis]-pos #odległości w formie wektorów (każda z każdą)<br />

R_distans = sqrt(add.reduce(b*b,-1)) #odległości w formie skalarów<br />

(każda z każdą)<br />

acc_big_new=(-G*m)/(R_distans**3)<br />

Kolejno przypisujemy wartości zerowe przyspieszenia pomiędzy i-tą i i-tą planetą<br />

for i in arange(n_of_bodies+1):<br />

acc_big_new[i,i]=0#jest nieskonczona no ale...na potrzeby obliczeń = 0<br />

a także wyznaczamy wypadkowe przyspieszenie działające na każdą z planet oraz prędkość<br />

pośrednią<br />

acc_in_algorithm=[]<br />

for k in arange(n_of_bodies+1):<br />

for f in arange(n_of_bodies+1):<br />

acc_in_algorithm.append((b[k][f])*acc_big_new[k][f])<br />

acc[k]=sum(acc_in_algorithm) #wyznaczenie wypadkowego przyspieszenia<br />

acc_in_algorithm=[] #wyzerowanie zmiennej<br />

V_new = vel<br />

S_new = pos<br />

V_posr_inalgorithm= vel + (dt/2)*acc<br />

W ostatniej fazie musi nastąpić podstawienie nowych wartości prędkości i położenia dla zmiennych<br />

oraz aktualizacja położenia dla każdej z planet. Taka forma algorytmu pozwala na przeprowadzanie<br />

symulacji bez konieczności aktualizowania położeń wyświetlanych planet. Opcja taka może okazać<br />

się pomocna przy przeprowadzaniu obliczeń na komputerach o niewielkiej mocy obliczeniowej<br />

procesora graficznego.<br />

for i in arange(n_of_bodies+1):<br />

objekt_list[i].frame.pos=S_new[i]<br />

50


vel = V_posr_inalgorithm<br />

vel_posrednia = V_posr_inalgorithm<br />

pos = S_new<br />

Analogicznie przeprowadzane są obliczenia dla kolejnych kroków czasowych. Tak zdefiniowany<br />

algorytm ruchu pozwala na nadawanie dowolnych prędkości co pozwala obserwować trajektorie<br />

dowolnie zorientowane w przestrzeni.<br />

else:<br />

b=pos[:,NewAxis]-pos<br />

R_distans = sqrt(add.reduce(b*b,-1))<br />

acc_big_new=(-G*m)/(R_distans**3)<br />

for i in arange(n_of_bodies+1):<br />

acc_big_new[i,i]=0 #jest nieskonczona no ale... dla obliczeń<br />

przyjmuje 0<br />

acc_in_algorithm=[]<br />

for k in arange(n_of_bodies+1):<br />

for f in arange(n_of_bodies+1):<br />

acc_in_algorithm.append((b[k][f])*acc_big_new[k][f])<br />

acc[k]=sum(acc_in_algorithm)<br />

acc_in_algorithm=[]<br />

vel_posrednia= vel_posrednia + dt*acc<br />

vel=vel_posrednia<br />

S_new = pos + vel_posrednia*dt<br />

for i in arange(n_of_bodies+1):<br />

objekt_list[i].frame.pos=S_new[i]<br />

pos = S_new<br />

Dodatkowe algorytmy jakie znajdują się w tej funkcji służą do wyznaczenia środka masy układu,<br />

aktualizacji krzywych wizualizujących wzajemne odległości między planetami oraz środkiem masy<br />

a także dodaniu trajektorii dla każdej z planet.<br />

51


8. Wybrane zagadnienia fizyczne<br />

Sprężyny i tłumienie drgań.<br />

Sprężyny stanowią elementy strukturalne amortyzatorów i tłumików. Często służą do połączenia<br />

dwóch ciał i wówczas mogą wywierać na nie siły równe i przeciwnie skierowane. Siły te zależą<br />

między innymi od współczynnika sprężystości oraz od długości rozciągnięcia lub ściśnięcia<br />

sprężyny (długości względnej). Określana jest ona często zależnością F s<br />

=k s<br />

L−r gdzie F s<br />

jest siłą sprężystości, k s współczynnikiem sprężystości, L długością rozciągniętej lub<br />

ściśniętej sprężyny.<br />

W przypadku symulacji numerycznych wraz z elementami sprężystymi [3],[5],[7] stosuje się<br />

elementy tłumiące drgania tak zwane amortyzatory lub tłumiki. Służą one tłumieniu drgań a efekt<br />

ich działania podobny jest do sił oporu spowodowanego na przykład lepkością. Gdy dwa ciała<br />

zbliżające lub oddalające się od siebie zostaną połączone przy pomocy elementu tłumiącego, jego<br />

działanie będzie powodować zmniejszanie prędkości względnej. W najprostszych przypadkach<br />

przyjmuje się, że siła ta jest proporcjonalna do względnej prędkości ciał połączonych ze stałą<br />

proporcjonalności k tł co wyrażamy następująco:<br />

F tł<br />

=k tł v 1<br />

−v 2 <br />

Jest to siła tłumiąca czyli funkcja współczynnika tłumienia k tł i względnej prędkości ciał<br />

(punktów materialnych). Najczęściej sprężyny i tłumiki są połączone w jeden element<br />

sprężynująco-tłumiący. Siły wywierane przez taki element opisywane są wspólnym wzorem,<br />

określającym siłę będącą kombinacją siły sprężystości i tłumienia. Co zapisujemy w postaci:<br />

F 1<br />

=−{ k s<br />

L−r k tł [ v 1 − v 2 ⋅L ]<br />

L<br />

} L<br />

L<br />

gdzie F 1 - jest siłą wywieraną na ciało pierwsze, zaś siła F 2 wywierana na ciało drugie<br />

zgodnie z trzecią zasadą dynamiki dana jest wzorem:<br />

F 2<br />

=− F 1<br />

Nim skonstruowany zostanie taki element sprężynująco-tłumiący przedstawiony zostanie model,<br />

w którym siła działająca jest proporcjonalna do odchylenia i skierowana w przeciwnym kierunku,<br />

czyli model przedstawiający zachowanie ciężarka zawieszonego na idealnej sprężynie bez<br />

jakiegokolwiek tłumienia ruchu. Druga zasada dynamiki przyjmuje więc postać: −kx=m d v y<br />

dt <br />

Prędkość w kierunku y zmienia się z szybkością proporcjonalną do samego y. Stałe liczbowe nie<br />

wniosą nic specjalnie ciekawego, więc można przyjąć, że zmieniliśmy skalę czasu lub że jakoś<br />

zmieniliśmy układ jednostek, tak by k /m=1 . Pozostanie zatem do rozwiązania numerycznego<br />

dv<br />

równanie następującej postaci:<br />

y<br />

=−y . Przyjmujemy następujące warunki początkowe<br />

dt<br />

t=0 , y=1 , v y<br />

=0 i konstruujemy algorytm oparty o zmodyfikowaną metodę Eulera:<br />

dt<br />

ytdt = ytdt vt 2 <br />

dt<br />

vt 2 =v dt<br />

t−<br />

2 dt a t<br />

at =−y<br />

52


a prędkość pośrednia dla chwili początkowej<br />

v dt<br />

2 =vt 0 dt<br />

2 at 0<br />

Pozostaje jeszcze implementacja funkcji pozwalającej ocenić dokładność rozwiązań numerycznych.<br />

d 2 y<br />

Ogólne rozwiązanie równania<br />

dt =− k 2 m y<br />

lub zapisanego w innej postaci jako<br />

y= Acos 0 t Bsin 0 t <br />

gdzie A=a cos , B=−a sin<br />

d 2 y<br />

dt =− 2 2 0 y wyraża się następująco:<br />

Po uwzględnieniu założeń zależność położenia od czasu określona będzie równaniem<br />

y=cost ,<br />

prędkość<br />

dy<br />

dt =−sin t ,<br />

a przyspieszenie<br />

d 2 y<br />

dt =−cost .<br />

2<br />

Tabela. Wyniki symulacji dla ciała zawieszonego na idealnej sprężynie przy braku czynnika<br />

tłumiącego (plik 02sprezyna_ideal.py).<br />

t y v y a y y prec v y prec a y prec<br />

0.0 1.0 0.0 -1 1.0 0.0 -1<br />

-0.05 -0.0499791692707<br />

0.1 0.995 -0.995 0.995004165278 -0.995004165278<br />

-0.1495 -0.149438132474<br />

0.2 0.98005 -0.995 0.980066577841 -0.980066577841<br />

-0.247505 -0.247403959255<br />

0.3 0.9552995 -0.9552995 0.955336489126 -0.955336489126<br />

-0.34303495 -0.34303495<br />

0.4 0.920996005 -0.920996005 0.921060994003 -0.921060994003<br />

-0.4351345505 -0.434965534111<br />

0.5 0.87748254995 -0.87748254995 0.87758256189 -0.87758256189<br />

-0.522882805495 -0.522687228931<br />

0.6 0.825194269401 -0.825194269401 0.82533561491 -0.82533561491<br />

-0.605402232435 -0.605186405736<br />

0.7 0.764654046157 -0.764654046157 0.764842187284 -0.764842187284<br />

-0.681867637051 -0.681638760023<br />

0.8 0.696467282452 -0.696467282452 0.696706709347 -0.696706709347<br />

-0.751514365296 -0.75128040514<br />

0.9 0.621315845922 -0.621315845922 0.621609968271 -0.621609968271<br />

-0.813645949888 -0.813415504789<br />

1.0 0.539951250934 -0.539951250934 0.540302305868 -0.540302305868<br />

-0.867641074982 -0.867423225594<br />

1.1 0.453187143435 -0.453187143435 0.453596121426 -0.453596121426<br />

-0.912959789325 -0.912763940261<br />

1.2 0.361891164503 -0.361891164503 0.362357754477 -0.362357754477<br />

53


t y v y a y y prec v y prec a y prec<br />

-0.949148905775 -0.948984619356<br />

1.3 0.266976273925 -0.266976273925 0.267498828625 -0.266976273925<br />

-0.975846533168 -0.975723357827<br />

1.4 0.169391620609 -0.169391620609 0.1699671429 -0.1699671429<br />

-0.992785695229 -0.992712991038<br />

1.5 0.0701130510857 -0.0701130510857 0.0707372016677 -0.0707372016677<br />

-0.999797000337 -0.999783764189<br />

1.6 -0.0298666489481 0.0298666489481 -0.0291995223013 0.0291995223013<br />

Rozwiązania równania<br />

Zmodyfikowana metoda Eulera.<br />

dv y<br />

dt =−y dla kroku czasowego dt=0.1 . parametry początkowe y 0<br />

=1 , v 0<br />

=0 , k=1<br />

Analiza wyników w powyższej tabeli pozwala stwierdzić że zgodność pomiędzy wynikami<br />

symulacji i wynikami dokładnymi zachodzi z dokładnością do trzech cyfr znaczących.<br />

By symulacja wyglądała nieco bardziej realistycznie wykonane zostało połączenie tych dwóch<br />

punktów przy pomocy sprężyny. Linia<br />

sprężynowa (lub śrubowa jak często też bywa<br />

nazywana) jest krzywą przestrzenną opisaną na<br />

powierzchni walca przez punkt poruszający się<br />

w ten sposób, że stosunek jego przemieszczenia<br />

liniowego (wzdłuż osi) a do odpowiadającego<br />

mu przemieszczenia kątowego jest stały,<br />

ale różny od zera lub nieskończoności.<br />

Powstawanie takiej krzywej można zobrazować<br />

przez nawinięcie na powierzchnię walca prostej<br />

nachylonej do jego podstawy pod kątem ,<br />

zwanym kątem wzniosu. Jest to kąt ostry<br />

zawarty między styczną do linii śrubowej a<br />

płaszczyzną prostopadłą do osi linii śrubowej.<br />

Odległość osiową między dwoma sąsiednimi<br />

punktami przecięcia linii śrubowej z tworzącą<br />

walca nazywa się skokiem linii śrubowej.<br />

Rysunek 40: Powstawanie linii śrubowej dla sprężyny o<br />

jednym zwoju.<br />

Jeżeli sprężyna będzie zawierać n 0 zwojów to oznaczając przez L 0 długość nierozciągniętej<br />

sprężyny o n 0 zwojach, =2 n 0 , R 0 promień nierozciągniętej sprężyny, otrzymamy<br />

<br />

2<br />

związek L rect<br />

=L 2 0<br />

R 0<br />

2 a po przekształceniu L rect<br />

=L 1 0<br />

R 0<br />

.<br />

L 0<br />

Otrzymana zależność pozwala określić długość drutu z którego jest wykonana sprężyna. Wraz z<br />

rozciągnięciem sprężyny zmienia się jej promień. Oznaczając przez R aktualny promień<br />

sprężyny a przez L długość rozciągniętej lub ściśniętej sprężyny możemy napisać:<br />

R 2 L 2 2<br />

= L rect<br />

. Obliczając R z ostatniego związku dostaniemy zależność pozwalającą<br />

określić jaka jest długość promienia przy rozciągnięciu (ściśnięciu) sprężyny.<br />

2<br />

<br />

54


Kolejna symulacja ilustruje trudniejszy problem<br />

<strong>mechaniki</strong>, który poza tym dodaje poprzedniemu<br />

cech realizmu. Uwzględnijmy zatem opory ruchu.<br />

Istnieje wiele sytuacji, w których siła tarcia jest<br />

proporcjonalna do szybkości, z jaką porusza się ciało.<br />

Przykładem może być tarcie, pojawiające się przy<br />

wolnym ruchu ciała w oleju lub innej gęstej cieczy.<br />

Gdy ciało spoczywa wówczas nie występuje siła<br />

oporu. Jednak im szybciej się porusza,tym szybciej<br />

opływa je olej i tym większy jest opór. Zakładamy<br />

więc, że na ciało działa jeszcze jedna siła oporu<br />

proporcjonalnego do prędkości F tł<br />

=k tł v 1<br />

−v 2 zaś<br />

siła będzie równa<br />

F =−{ k s L−r k tł [ v 1 − v 2 ⋅L]<br />

} L<br />

L L .<br />

Wystarczy zatem wprowadzić kolejną zmienną –<br />

współczynnik tarcia oraz zaimplementować<br />

powyższy wzór na siłę (patrz plik<br />

03sprezyna_z_tlumikiem.py).<br />

Rysunek 41: Ściskanie i rozciąganie sprężyny<br />

powoduje zmianę jej promienia.<br />

Rysunek 42: Symulacja ruchu ciała zawieszonego na sprężynie przy współczynniku oporu ośrodka<br />

proporcjonalnym do prędkości. Całkowanie zmodyfikowana metoda Eulera.<br />

55


Waga.<br />

Poprzednią symulację można nieco uatrakcyjnić dodając skalę, na której można odczytać wagę<br />

ciała zawieszonego na sprężynie.<br />

Jeżeli oznaczymy długość sprężyny przez L spring to położenie równowagi statycznej końca<br />

sprężymy obciążonego ciałem o masie m wynosi<br />

h=L spring<br />

mg<br />

gdzie k s<br />

k s<br />

oznacza współczynnik sprężystości.<br />

W oparciu o tą zależność została skonstruowana funkcja rysująca skalę liniową wagi. Zmieniając<br />

wartość przyspieszenia grawitacyjnego, stałą sprężystości oraz długość sprężyny zmieniać się<br />

będzie zarówno skala wagi jak i miejsce rysowania tej skali.<br />

Rysunek 43: Waga<br />

56


Wahadło torsyjne.<br />

Wahadłem takim może być na przykład krążek zawieszony w środku masy. Drut na którym wisi<br />

krążek, jest sztywno zamocowany z obu stron w taki sposób by niemożliwe było ślizganie się. Jeśli<br />

krążek obrócimy w płaszczyźnie poziomej do położenia oznaczonego na poniższym rysunku żółtą<br />

strzałką, to drut zostanie skręcony. Wówczas na krążek będzie działać moment siły skręconego<br />

drutu i układ taki będzie dążył do położenia równowagi czyli takiego by położenie kątowe było<br />

równe zeru. Moment ten jest proporcjonalny do wielkości skręcenia: =− . Jest to prawo<br />

Hooke'a. Stała jest nazywana stałą skręcenia [8]. Równanie ruchu dla takiego układu<br />

przyjmuje postać:<br />

= I d 2 <br />

dt 2 k tł<br />

d <br />

dt<br />

.<br />

Widać zatem, że to równanie jest podobne do równania opisującego liniowy ruch harmoniczny z<br />

tłumieniem, którego symulacja była poprzednio omawiana.<br />

Wiele przyrządów pomiarowych zawiera w sobie wahadło torsyjne na przykład galwanometry, lub<br />

waga Cavendisha. Innym przykładem ruchu harmonicznego tłumionego może być ruch balansu w<br />

zegarkach. Gdy zabraknie przywracającego równowagę momentu siły, pochodzącego od spiralnego<br />

włosa-sprężyny, w wyniku tarcia ruch taki zaniknie.<br />

W tej symulacji dla uproszczenia została pominięta wizualizacja drutu (lub spiralnego włosa<br />

sprężyny), natomiast zaimplementowane zostały następujące metody całkowania numerycznego:<br />

zmodyfikowana metodą Eulera, metoda punktu środkowego oraz metoda Rungego-Kutty czwartego<br />

rzędu.<br />

Rysunek 44: Symulacja ruchu harmonicznego tłumionego. Metoda Rungego-Kutty.<br />

57


Zderzenia ciał z przeszkodami - zderzenie kul w przestrzeni<br />

Od wielu lat <strong>zjawisk</strong>a zderzeń ciał lub elementów tych ciał (cząstek) są przedmiotem<br />

niegasnącego zainteresowania wśród naukowców. W miarę rozwoju współczesnej techniki<br />

i inżynierii materiałowej zaczęto zajmować się szerokim spektrum dynamicznych obciążeń ciał<br />

takich jak uderzenia fragmentów komet lub innych obiektów kosmicznych w obudowę statku lub<br />

sondy kosmicznej, wielokrotne uderzenia kropel cieczy w twarde elementy konstrukcyjne,<br />

zderzenia ciał niesionych przez huragany i tornada z obiektami stacjonarnymi itp. Również<br />

zderzenia cząstek elementarnych z materią odgrywają we współczesnej fizyce niebagatelną rolę,<br />

ponieważ dzięki badaniom tego typu możliwe jest poznanie podstawowych składników materii i ich<br />

oddziaływań. Kolejna symulacja jaka zostanie zaprezentowana pokaże w jaki sposób symulować<br />

nierelatywistyczne zderzenia punktów materialnych a także ciał sztywnych.<br />

Pod pojęciem „zderzenie” należy rozumieć krótkotrwałe oddziaływanie zwykle dwóch ciał<br />

(cząstek elementarnych, atomów, brył sztywnych), zbliżonych na dostatecznie małą odległość, które<br />

powoduje zmianę kierunku ich ruchu [3]. Zmiana liczebności, stanu wewnętrznego i rodzaju<br />

elementów oddziałujących ze sobą, w rozpatrywanym <strong>zjawisk</strong>u nie będzie brana pod uwagę.<br />

Jako przykład posłuży nam zderzenie kul. Obiekty te będziemy traktować zgodnie z klasycznymi<br />

zasadami Newtona. Zderzające się ciała, niezależnie od materiału i budowy traktujemy jak<br />

jednorodne bryły sztywne. Również w trakcie i w wyniku zderzeń nie będą one zmieniać kształtu.<br />

Jest to oczywista idealizacja, gdyż w rzeczywistości zderzające się ciała wgniatają się, często może<br />

dochodzić w nich do wewnętrznych spękań i mikro lub makro uszkodzeń, trwale zgniatają się<br />

a nawet zapadają lub rozpadają na większą ilość elementów.<br />

Siły występujące pomiędzy zderzającymi się obiektami nazywa się siłami impulsowymi. Siły<br />

występujące w przypadku uderzenia bili kijem bilardowym, lub kopnięcia piłki są także siłami<br />

impulsowymi. Zasady dynamiki Newtona będą stanowiły fundament technik zastosowanych w tej<br />

symulacji. Siłę działającą na ciało możemy zapisać jako F = d p a po scałkowaniu po czasie<br />

dt<br />

otrzymujemy ∫<br />

t<br />

t 0<br />

t<br />

F dt=∫<br />

t 0<br />

d p<br />

dt<br />

t<br />

zatem ∫<br />

t 0<br />

F dt= p<br />

t<br />

Wektor ∫ F dt nazywa się impulsem (lub popędem) siły w przedziale czasu t=t – t 0 .<br />

t 0<br />

Mówiąc dokładniej, impuls (popęd) jest wektorem równym zmianie pędu, czyli zmiana pędu jest<br />

równa użytemu popędowi siły. Jeżeli masa ma stałą wartość to możemy napisać:<br />

t<br />

impuls liniowy=∫ F dt=m v final<br />

−v initial . Taki oto impuls działa na ciało podczas zderzenia. Na<br />

t 0<br />

potrzeby symulacji poczynimy jednak dodatkowe uproszczenia, które uproszczą zagadnienie jednak<br />

efekty będą nadal interesujące.<br />

W modelowaniu przebiegu <strong>zjawisk</strong>a zderzenia możemy wyróżnić dwie fazy. Pierwsza z nich<br />

dotyczy ruchu ciał przed zderzeniem i kończy się (z reguły) detekcją zderzenia, która daje<br />

odpowiedź na pytania czy nastąpiło zderzenie, a w przypadku odpowiedzi twierdzącej pozwala na<br />

ustalenie miejsca kolizji. Jest to zatem problem czysto geometryczny. Druga faza to odpowiedź<br />

zderzenia. Jest to problem fizyczny ruchu ciał po zderzeniu. Bazuje on na algorytmie detekcji<br />

zderzenia więc dokładne wyznaczenie odpowiedzi zderzenia zależy od precyzji i niezawodności<br />

algorytmu detekcji. Dla uzyskania szybkości działania zastosowana zostanie metoda detekcji<br />

za pomocą kul otaczających, która jest niezwykle prosta w implementacji.<br />

Analiza odpowiedzi zderzenia obejmuje zarówno uwzględnienie newtonowskiej zasady zachowania<br />

58


pędu jak i zmianę pędu równą impulsowi działających sił. Pierwsza z nich mówi, że w układzie<br />

izolowanym w przypadku gdy masy ciał są stałe, to suma iloczynów mas i prędkości przed<br />

zderzeniem jest równa sumie iloczynów mas i prędkości po zderzeniu<br />

∑ m i v i initial<br />

=∑ m i v i final .<br />

Poza tym przyjmuję się, że jedyną siłą mającą tu znaczenie jest siła impulsowa. Wszystkie inne siły,<br />

które działają w chwili zderzenia są pomijalnie małe. Z doświadczenia wiemy, że w zderzeniu<br />

rzeczywistym energia kinetyczna jest tracona na energię odkształceń, rotacji lub oscylacji. Jeżeli<br />

deformacja jest trwała to następuje strata energii a zderzenie nazywamy niesprężystym, ponieważ<br />

odpowiedź zderzeniowa czyli ruch kul po zderzeniu i związana z nią energia są mniejsze niż przed<br />

zderzeniem. Jeżeli zderzenie jest idealnie niesprężyste to obiekty po zderzeniu zazwyczaj poruszają<br />

się jako jedno ciało a prędkość ciał po zderzeniu jest średnią ważoną ich prędkości początkowych,<br />

przy czym wagami są masy tych ciał. Przykładem takiego zderzenia mogą być kule wykonane<br />

z kitu technicznego lub plasteliny. Rozpatrując czysto teoretyczne zderzenie brył sztywnych<br />

możemy rozpatrywać straty energii lub nie. Jeżeli nie rozpatrujemy strat energii to mówimy, że<br />

energia jest zachowana. Dobrym przykładem zderzenia sprężystego, lecz nie idealnie sprężystego<br />

jest zderzenie kul bilardowych lub stalowych. W takim zderzeniu w normalnych warunkach<br />

deformacje kul są nietrwałe i pomijalne małe. Zatem zderzenia rzeczywiste są czymś pośrednim<br />

pomiędzy zderzeniem idealnie sprężystym i idealnie niesprężystym. Dlatego dla ciał sztywnych<br />

wprowadzony musi zostać współczynnik, pozwalający określić stopień sprężystości zderzenia.<br />

Współczynnik taki będzie określał iloraz względnej prędkości ciał po zderzeniu do względnej<br />

prędkości tych ciał przed zderzeniem. Jeżeli w zderzeniu będą brały udział dwa ciała to<br />

współczynnik zderzenia (restytucji) możemy zapisać następująco: e=− v 1 final<br />

−v 2 final <br />

,<br />

v 1initial<br />

−v 2initial <br />

gdzie v 1 final , v 2 final prędkości po zderzeniu, natomiast v 1 initial , v 2 initial prędkości przed<br />

zderzeniem, przy czym należy zwrócić uwagę iż są to prędkości wzdłuż linii akcji zderzenia.<br />

Współczynnik taki zależy od wewnętrznej budowy, geometrii a także od materiału z jakiego są<br />

wykonane kolidujące obiekty. Dla zderzenia kul bilardowych wynosi 0,9 zaś dla zderzenia kija<br />

baseballowego i piłki wynosi zaledwie 0,43. Dla zderzeń doskonale sprężystych wynosi 1 a dla<br />

doskonale niesprężystych zero. Dla wszystkich zderzeń rzeczywistych, w których nie zachodzą<br />

reakcje egzoenergetyczne lub endoenergetyczne wartość tego współczynnika będzie przyjmować<br />

wartości z przedziału od zera do jeden.<br />

Ponadto w modelu niniejszego zderzenia przyjmuje się brak tarcia między kolidującymi obiektami,<br />

wynika z tego iż kule po zderzeniu nie będą rotować. W tego typu sytuacji linia akcji zderzenia jest<br />

prostopadła do zderzających się powierzchni. Gdy prędkości leżą wzdłuż tej prostej zderzenie nosi<br />

nazwę zderzenia na wprost, lub w szczególnym przypadku gdy prędkości dodatkowo będą mieć<br />

przeciwne zwroty to zderzenie nazywamy centralnym. Gdy prędkości nie układają się wzdłuż linii<br />

akcji to takie zderzenie nazywamy skośnym. Zazwyczaj zderzenia tego typu rozpatruje się<br />

rozkładając wektory prędkości na składowe prostopadłe i równoległe do linii akcji. Wówczas<br />

zderzenie będzie miało wpływ na składowe równoległe do linii akcji, nie będzie mieć natomiast<br />

wpływu na składowe prostopadłe do niej.<br />

Opis algorytmu<br />

Rozpoczynamy od importowania niezbędnych bibliotek, wykonania układu współrzędnych oraz<br />

zdefiniowania parametrów okna. Pierwszym fizycznym parametrem jaki wprowadzimy do<br />

symulacji jest parametr zderzenia b. Określa on odległość środka ciężkości pierwszej kuli od osi<br />

iksów na której będzie znajdować się druga kula, co przedstawione zostało schematycznie na<br />

poniższym rysunku.<br />

59


Rysunek 45: Sposób wyznaczania parametru zderzenia b.<br />

Kolejno przystępujemy do zainicjowania obiektów zderzeń czyli kul oraz nadania im atrybutów<br />

<strong>fizycznych</strong> takich jak prędkości, promienie i masy. Symulację wzbogacić należy również<br />

we współczynnik zderzenia o którym było wspomniane wcześniej. Również na samym początku<br />

programu należy zdefiniować krzywe, które będą obrazować trajektorie ruchu. Wreszcie nie<br />

możemy także zapomnieć o zdefiniowaniu kroku czasowego symulacji, który należy odpowiednio<br />

dobrać w zależności od prędkości nadanym kulom.<br />

# b - parametr zderzenia<br />

b = -1.2#-1.80#[m]<br />

ball_1 = sphere(pos=(-4.5,b,0.)) #[m]<br />

ball_2 = sphere(pos=(4.0,0.0,0.0)) #[m]<br />

ball_1.velocity = vector(4.0,0.0,0.0) #[m/s]<br />

ball_2.velocity = vector(-1.5,0.0,0.0) #[m/s]<br />

mass_1 = 15.0 #[kg]<br />

mass_2 = 5.0 #[kg]<br />

ball_1.radius=1.2 #[m]<br />

ball_2.radius=0.8 #[m]<br />

# dla coef_e=1.0 zderzenie idealnie sprezyste<br />

# dla coef_e=0.0 zderzenie calkowicie niesprezyste<br />

coef_e = 1.0 #wspolczynnik zderzenia<br />

ball_1.color=K_1<br />

ball_2.color=K_2<br />

# slad ruchu<br />

ball_1.trail = curve(color=ball_1.color,radius = 0)<br />

ball_2.trail = curve(color=ball_2.color,radius = 0)<br />

dt = 0.01 # krok czasowy symulacji<br />

Ruch obiektów zarówno przed zderzeniem jaki po nim jest ruchem jednostajnym prostoliniowym,<br />

więc by nie komplikować algorytmu do przesuwania obiektów zostanie użyta metoda Eulera. Jest to<br />

realizowane z użyciem pętli while.<br />

while 1:<br />

rate(50)<br />

#ruch kulek<br />

ball_1.pos = ball_1.pos+ball_1.velocity*dt<br />

ball_2.pos = ball_2.pos+ball_2.velocity*dt<br />

W każdym kroku czasowym algorytm uruchamia funkcję detection(), odpowiedzialną za wykrycie<br />

zderzenia między obiektami. Jej działanie jest następujące. W pierwszej kolejności tworzymy<br />

tablice pozycje=array() oraz promienie=array() w których w każdym kroku czasowym<br />

60


umieszczamy aktualne położenia i promienie obiektów. Posłużą one do wyznaczenia odległości 5<br />

między kulami. Detekcja zderzenia nastąpi w przypadku gdy odległość pomiędzy środkami kul<br />

będzie mniejsza niż suma ich promieni, będzie to znaczyć, że nastąpiła penetracja między kulami<br />

a w zmiennej KOLIZJE pojawią się niezerowe wartości. Jeżeli pojawią się niezerowe wartości to<br />

tablica KOLIZJE jest przekształcana na listę. Najistotniejszym faktem na który należy zwrócić tutaj<br />

uwagę jest przypisanie zmiennej licznik wartości równej 1. Ma to zapobiec wielokrotnym<br />

wykryciom zderzenia mogącym powstać w wyniku dobrania zbyt dużego lub małego kroku<br />

czasowego ewentualnie błędów mogących wyniknąć z zaokrąglania liczb. Mamy zatem pewność,<br />

że do detekcji zderzenia dochodzi tylko raz. Zakończenie detekcji kończy się wywołaniem funkcji<br />

cool_collision().<br />

#detekcja zderzenia<br />

poslist = [] #lista pozycji<br />

rlist = [] #lista promieni<br />

for i in range(1):<br />

ppp=ball_1.pos<br />

poslist.append((ppp))<br />

ppp=ball_2.pos<br />

poslist.append((ppp))<br />

rrr=ball_1.radius<br />

rlist.append((rrr))<br />

rrr=ball_2.radius<br />

rlist.append((rrr))<br />

pozycje=array(poslist)<br />

promienie=array(rlist)<br />

dist = pozycje-pozycje[:,NewAxis]#odleglosci miedzy kulami jako wektory<br />

dist_mag = sqrt(add.reduce(dist*dist,-1)) # przeksztalcenie tych odleglosci<br />

na skalary<br />

KOLIZJE = less_equal(dist_mag,promienie+promienie[:,NewAxis])-identity(2)<br />

KOLIZJE_LIST = sort(nonzero(KOLIZJE.flat)).tolist()<br />

for ij in KOLIZJE_LIST:<br />

print 5*"ZEDRZENIE "<br />

#rozszyfrowanie liczb i, j<br />

i, j = divmod(ij,2) # rozszyfrowanie dla kul parami<br />

#print "i",i,"j",j<br />

KOLIZJE_LIST.remove(j*2+i)<br />

licznik=1<br />

cool_collision()#wywolanie funkcji<br />

Odpowiedź zderzenia<br />

Traktując kule jak bryły sztywne wykluczamy możliwość wzajemnego przenikania się tych<br />

obiektów, dlatego odpowiedź zderzenia zaczynamy od precyzyjnego wyznaczenia położeń,<br />

w których kule zetknęły się ze sobą. W tym celu wyznaczamy czas trwania penetracji obiektów.<br />

Znamy zarówno położenie, prędkości, promienie więc zgodnie z rysunkiem b x=R−K gdzie<br />

R= R 1<br />

R 2 , K - odległość pomiędzy kulami, zatem czas penetracji deltat wyznaczymy jako<br />

deltat= R− K gdzie Dvel=∣v<br />

Dvel<br />

1<br />

− v 2<br />

∣<br />

5 Szczegółowo ta część algorytmu jest omówiona w III części modelu gazu doskonałego.<br />

61


Rysunek 46: Schemat działania algorytmu odpowiedzi zderzenia w przypadku wykrycia kolizji między<br />

obiektami.<br />

def cool_collision():<br />

"""funkcja odpowiedzialna za<br />

obsluge kolizji pomiedzy kulami"""<br />

R=ball_1.radius+ball_2.radius<br />

K=mag(ball_1.pos-ball_2.pos)<br />

DV=mag(ball_1.velocity-ball_2.velocity)<br />

deltat= (R-K)/DV<br />

#skorygowanie polozen do pozycji zetkniecia ("cofniecie czasu")<br />

ball_1.pos = ball_1.pos-(ball_1.velocity)*deltat<br />

ball_2.pos = ball_2.pos-(ball_2.velocity)*deltat<br />

Linia akcji zderzenia przebiega wzdłuż prostej przechodzącej przez środki ciężkości obu kul co<br />

umożliwia wyznaczyć jednostkowy wektor normalny [2]<br />

n = norm(ball_2.pos-ball_1.pos) # jednostkowy wektor linii akcji<br />

Wyznaczenie położeń w których kule stykają się ze sobą umożliwia wprowadzenie do symulacji<br />

pewnych funkcji, które ułatwią analizę zderzenia. Pierwszą z nich jest funkcja linia_akcji(). Jej<br />

działanie opiera się na równaniu prostej przechodzącej przez dwa różne punkty O 1<br />

x 1,<br />

y 1<br />

oraz<br />

O 2<br />

x 2,<br />

y 2<br />

, którymi są położenia kul i pozwala narysować prostą normalną do powierzchni kul<br />

w punkcie zetknięcia. Kolejna funkcja perpendicular_LINE() na podstawie wyznaczonego<br />

jednostkowego wektora normalnego rysuje prostą styczną do kul w punkcie zderzenia. Ostatnia<br />

funkcja okregi() rysuje okrąg o zadanym promieniu, kolorze w wybranym miejscu układu<br />

współrzędnych. Warto podkreślić, że działanie tych funkcji nie wpływa w żaden sposób na przebieg<br />

symulacji.<br />

#------------------------------------<br />

#wywolanie funkcji<br />

linia_akcji()<br />

perpendicular_LINE(n)<br />

okregi(ball_2.radius, ball_2.color, ball_2.pos[0],ball_2.pos[1])<br />

okregi(ball_1.radius, ball_1.color, ball_1.pos[0],ball_1.pos[1])<br />

#------------------------------------<br />

Kontynuując analizę odpowiedzi zderzenia będziemy rozkładać wektory prędkości na składowe<br />

prostopadłe i równoległe do linii akcji. Wówczas jak zostało to wcześniej podkreślone zderzenie<br />

będzie miało wpływ na składowe równoległe do linii akcji, a nie będzie mieć wpływu na składowe<br />

prostopadłe do niej. Możemy wyznaczyć prędkość względną kul wzdłuż linii akcji<br />

62


V rn<br />

=v 1init<br />

⋅n<br />

#predkosc wzgledna kul<br />

#V_rn velocity relative normal<br />

V_rn = dot((ball_1.velocity-ball_2.velocity),n)<br />

print "V_rn",V_rn<br />

Jednak istotniejszą sprawą jest wyznaczenie składowych prędkości wzdłuż linii akcji 6<br />

v 1n b c<br />

=v 1 init<br />

⋅n<br />

v 2n b c<br />

=v 2 init<br />

⋅n<br />

#skladowe normalne do powierzchni w pkt. zderzenia<br />

#czyli rownolegle do wektora n<br />

V_1n_before = dot(ball_1.velocity,n)<br />

V_2n_before = dot(ball_2.velocity,n)<br />

Stosując zasadę zachowania pędu w kierunku linii akcji, będziemy mogli wyznaczyć prędkości kul<br />

wzdłuż linii akcji po zderzeniu 7 .<br />

m 1<br />

v 1n b c<br />

m 2<br />

v 2n b c<br />

=m 1<br />

v 1n a c<br />

m 2<br />

v 2n a c<br />

z powyższego równania wyznaczamy v 1n a c<br />

restytucji<br />

e=− v 1n ac<br />

−v 2n a c <br />

v 1n bc<br />

−v 2n bc <br />

ostatecznie otrzymamy<br />

v 2n ac<br />

= e v 1n bc<br />

−v 2n bc v 1n bc<br />

m 2<br />

/m 1<br />

v 2n bc<br />

m 2<br />

/m 1<br />

1<br />

a następnie podstawiamy do wzoru na współczynnik<br />

v 1n a c<br />

=v 1n bc<br />

m 2<br />

m 1<br />

v 2n bc<br />

− m 2<br />

m 1<br />

v 2n ac<br />

#wyznaczenie skladowych normalnych predkosci po zderzeniu<br />

V_2n_after = (coef_e*(V_1n_before-V_2n_before)+<br />

V_1n_before+(mass_2/mass_1)*V_2n_before)/((mass_2/mass_1)+1)<br />

V_1n_after = V_1n_before + (mass_2/mass_1)*V_2n_before -<br />

(mass_2/mass_1)*V_2n_after<br />

W zderzeniu tym nie uwzględniamy tarcia, więc kule zachowują prędkości prostopadłe względem<br />

linii akcji.<br />

v 1n contact<br />

=v 1 init x<br />

⋅n y<br />

v 2n contact<br />

=v 2 init x<br />

⋅n y<br />

Następnie zapisujemy wektory prędkości obu kul po zderzeniu po rozkładzie na składowe styczne<br />

i normalne po czym dokonujemy przejścia do układu współrzędnych x y. Ostatecznie prędkości kul<br />

po zderzeniu zapiszemy jako:<br />

v 1 =v 1n a c cosv 1n contact sin iv 1n a c sin −v 1n contact cos j<br />

v 2 =v 2n a c cosv 2n contact sin iv 2n a c sin −v 2n contact cos j<br />

6 skrót b c (ang. before collision) – przed zderzeniem<br />

7 skrót a c (ang. after collision) – po zderzeniu<br />

63


gdzie =atan n y<br />

n x<br />

działanie funkcji cool_collision() kończy ponowne skorygowanie położeń, tym razem już z nowymi<br />

prędkościami jakie uzyskały kule po zderzeniu.<br />

# zderzenia bez tarcia, wiec ciala zachowuja predkosci prostopadle wzgledem<br />

wektora n<br />

V_1n_contact = ball_1.velocity*n[1]<br />

V_2n_contact = ball_2.velocity*n[1]<br />

alfa = atan(n[1]/n[0])<br />

alfa_deg = degrees(alfa)<br />

# przejscie do ukladu xy<br />

V_1_new_x = V_1n_after*cos(alfa) + V_1n_contact[0]*sin(alfa)<br />

V_1_new_y = V_1n_after*sin(alfa) - V_1n_contact[0]*cos(alfa)<br />

V_2_new_x = V_2n_after*cos(alfa) + V_2n_contact[0]*sin(alfa)<br />

V_2_new_y = V_2n_after*sin(alfa) - V_2n_contact[0]*cos(alfa)<br />

ball_1.velocity=vector(V_1_new_x,V_1_new_y,0.0)<br />

ball_2.velocity=vector(V_2_new_x,V_2_new_y,0.0)<br />

#skorygowanie polozen i "cofnietego czasu"<br />

ball_1.pos = ball_1.pos+(ball_1.velocity)*deltat<br />

ball_2.pos = ball_2.pos+(ball_2.velocity)*deltat<br />

return<br />

Pozostałe funkcje służą wizualizacji układu współrzędnych, układu odniesienia oraz wyświetlaniu<br />

napisów i kontrolowaniu parametrów okna.<br />

Rysunek 47: Przykładowa symulacja zderzenia kul.<br />

Wizualizacja takiego modelu może okazać się pomocna w zrozumieniu przebiegu <strong>zjawisk</strong>a<br />

zderzenia się ciał co ułatwi właściwe modelowanie podobnych lub bardziej zaawansowanych<br />

sytuacji zderzeń. Wyniki symulacji mogą być wykorzystane i porównane z wynikami <strong>fizycznych</strong><br />

eksperymentów.<br />

64


Inne sytuacje jakie mogą być symulowane przy pomocy tej techniki.<br />

Z <strong>mechaniki</strong> klasycznej wiadomo, że jeżeli na środek wahań wahadła fizycznego zadziałamy siłą<br />

impulsową na przykład skierowaną poziomo w płaszczyźnie drgań to w punkcie zawieszenia nie<br />

pojawi się siła reakcji [8],[A]. Z uwagi na tą interesującą właściwość środek wahań jest nazywany<br />

środkiem uderzenia. I tak na przykład gdy zadziałamy siłą impulsową na walec w punkcie COP 8 to<br />

w punkcie zawieszenia nie pojawi się siła reakcji.<br />

Współrzędne punktu COP dla walca wyliczamy w następujący sposób:<br />

R -promień walca, l -długość walca, M -masa walca<br />

I = MR2<br />

4 Ml 2<br />

12 Ml 2<br />

4 = MR2<br />

4 Ml 2<br />

moment bezwładności względem osi przechodzącej przez<br />

3<br />

jeden z końców walca, prostopadłej do walca<br />

L COP<br />

= I gdzie d – odległość osi obrotu od środka masy<br />

Md<br />

L COP = 1 2<br />

R 2<br />

l 2 3 l<br />

Rysunek 48: Tylko zadziałanie silą impulsową w punkcie<br />

środka uderzenia nie wywołuje siły reakcji w punkcie<br />

zawieszenia.<br />

Przyjmując założenie, że kula uderza w środek uderzenia takiego walca możemy rozpatrywać<br />

zderzenia kuli z walcem (a nawet kijem baseballowym). W tym ostatnim przypadku zakładamy, że<br />

kij może swobodnie się obracać i przesuwać wokół punktu położonego w pobliżu uchwytu oraz, że<br />

prędkość piłki i kija w chwili uderzenia jest również pozioma.<br />

Uwaga:<br />

– Wyświetlane wartości kątów należy odczytywać jako: wartość bezwzględna kąta mniejszego<br />

pomiędzy trajektorią wylotu a osią iksów.<br />

– Parametr zderzenia b jest aktualny tylko wtedy gdy igrekowa i zetowa składowa położenia kuli<br />

numer 2 jest równa zeru (to znaczy zderzenie musi odbywać się w płaszczyźnie x y).<br />

8 COP- center of percussion (ang. środek uderzenia), COG – center of gravity (ang. środek masy)<br />

65


Model gazu doskonałego.<br />

Na podstawie projektów umieszczonych na stronie Australian National University<br />

pt. „Using VPython to Simulate a Ball Bouncing in a Box” oraz<br />

„Using VPython to Simulate a Gas” które wykonali Sally Lloyd i Stephen Roberts<br />

(tłumaczenie i poprawki Marcin Maćkowicz)<br />

Wprowadzenie<br />

W tym rozdziale omówiony zostanie problem <strong>komputerowe</strong>go modelowania zachowania się<br />

gazu doskonałego w zamkniętym naczyniu.<br />

W pierwszej części zostanie przedstawione jak wymodelować odbijającą się kulkę (atom)<br />

wewnątrz sześciennego pojemnika [B], aby na podstawie tak stworzonej animacji można<br />

było wykonać symulację fizyczną zachowania się gazu doskonałego w zamkniętym<br />

naczyniu [C].<br />

Wizualizacja takiego modelu może okazać się użyteczna w przypadku kiedy będziemy<br />

chcieli uzyskać wgląd w zachowanie się gazu wewnątrz naczynia. Napisany program, może<br />

okazać się także użyteczny w bardziej skomplikowanych sytuacjach na przykład gdy<br />

będziemy chcieli, na podstawie symulacji, przewidzieć zachowanie się gazu w fizycznym<br />

eksperymencie lub co jest bardziej istotne w modelu rzeczywistym. Ewentualnie możemy<br />

użyć wyniki w celu potwierdzenia teorii dla gazów doskonałych.<br />

Symulacja odbijającej się kulki wewnątrz pudełka.<br />

• Modelowanie poszczególnych elementów<br />

W pierwszej kolejności należy wykonać ściany naczynia (obiekty typu box) oraz<br />

zainicjować obiekt, który posłuży jako atom – w naszym przypadku będzie to kula.<br />

# Import biblioteki Visual<br />

from visual import *<br />

# inicjacja sciany (lub kilku scian)<br />

wallR = box(pos=vector(6,0,0), length=0.2, height=4, width=4, color =<br />

color.red)<br />

# inicjacja kulki (kulek) reprezentujacych atom (atomy)<br />

ball = sphere(pos=vector(-5,0,0), radius=1.0, color=color.green)<br />

Następnie należy kuli przypisać atrybut – prędkość<br />

ball.velocity=vector(2,.1,0)<br />

66


• Przesuwanie obiektów<br />

Aby móc przesuwać obiekt niezbędna jest pętla, w której wielokrotnie będziemy zmieniać<br />

pozycję obiektu. Uzyskamy to poprzez zastosowanie nieskończonej pętli, która będzie się<br />

wykonywać dopóki będzie spełniony określony warunek. Ruch ten powinien być<br />

skoordynowany z prędkością jaką nadaliśmy obiektowi.<br />

• Wielokrotne odbicia piłki<br />

Po uruchomieniu powyższego programu zauważamy, że piłka idzie prosto przez ścianę. Aby<br />

mogło dojść do odbicia piłki od ściany musimy (wykonać) zaimplementować algorytm,<br />

który pozwoli na detekcję zderzenia atomu ze ścianką naczynia. W najprostszym przypadku<br />

detekcję zderzenia uzyskamy poprzez porównanie iksowej składowej położenia atomu i tej<br />

samej składowej ścianki naczynia. W przypadku gdy atom przesunął się poza wyznaczoną<br />

granicę, którą jest ściana, będzie musiało dojść do odwrócenia składowej iksowej prędkości.<br />

Tym sposobem zrealizujemy odbicie doskonale sprężyste.<br />

#sprawdzenie kolizji ze sciana<br />

#prawa sciana<br />

if ball.x > wallR.x:<br />

ball.velocity.x = -ball.velocity.x<br />

Po uruchomieniu programu zobaczyć można ruch kulki w prawo, odbicie od ściany a<br />

następnie ruch w lewo kontynuowany w nieskończoność. Należy podkreślić, że ten test<br />

logiczny nie jest zbyt wyrafinowany. Współrzędna ball.x jest środkiem atomu i współrzędna<br />

wallR.x jest środkiem grubości ściany, więc następuje penetracja pomiędzy ścianą i kulą<br />

zanim dojdzie do odbicia.<br />

Poprawimy znacznie naszą animację modyfikując algorytm w taki sposób aby odwrócenie<br />

wartości składowej prędkości nastąpiło gdy brzeg atomu sięgnie prawego brzegu ściany<br />

(ball.x > wallR.x-wallR.size.x/2- ball.radius)<br />

W następnej kolejności należy dodać kolejne ściany, a następnie zaimplementować dla kuli<br />

detekcję zderzenia z nimi.<br />

Częsty dostęp do niektórych parametrów<br />

obiektów wymusza zastosowanie nowych<br />

zmiennych (side, thk, ball_radius, maxpos)<br />

dzięki którym w łatwy i przystępny sposób<br />

parametryzujemy ściany, atom i jego<br />

pozycję.<br />

Po dodaniu wszystkich ścian niezbędne<br />

będzie ustawienie parametru visible na zero<br />

dla ściany przedniej. Zabieg ten<br />

wykonujemy, aby móc zobaczyć co dzieje<br />

się wewnątrz zbiornika.<br />

(patrz plik 01_kulka_w_zbiorniku.py)<br />

Rysunek 49: Kula wewnątrz sześciennego naczynia.<br />

67


Symulacja zachowania się gazu doskonałego w zamkniętym naczyniu.<br />

• Opis symulacji - ruch atomów<br />

Teraz na podstawie animacji obrazującej ruchu kulki wewnątrz zamkniętego naczynia<br />

zasymulujemy ruch atomów w gazie doskonałym [C]. Do tego celu będziemy potrzebować<br />

wiele atomów, które będą zderzać się idealnie sprężyście ze ściankami naczynia. Będziemy<br />

także musieli nadać pozycje i prędkości początkowe dla wszystkich atomów, ale przy<br />

założeniu, że wartości te będą mogły ewoluować w późniejszej fazie symulacji. W kolejnej<br />

fazie symulacji będziemy chcieli porównać rozkład prędkości cząsteczek gazu z rozkładem<br />

prędkości Maxwell'a.<br />

• Liczby losowe.<br />

Możemy uzyskać różne prędkości atomów, przy każdym uruchomieniu programu, używając<br />

do tego celu generator liczb losowych. Generator liczb losowych (a dokładniej<br />

pseudolosowych) nie jest częścią standardowego języka Python i musi by importowany<br />

z modułu o nazwie random. Moduł liczb losowych zawiera generatory dla kilku różnych<br />

rozkładów. Możemy importować tylko jednolity rozkład dla liczb losowych wydając<br />

polecenie:<br />

from random import uniform<br />

umieszczając je na początku programu w sekcji gdzie importujemy niezbędne biblioteki.<br />

Funkcja uniform(-1,1) będzie zwracać liczby losowe zawierające się w przedziale<br />

pomiędzy -1 i 1. Ustawienie poszczególnych składowych prędkości początkowych<br />

w zakresie od -2 do 2 (w jednostkach dowolnych, czyli na przykład w m/s) można wykonać<br />

w następujący sposób [2],[4]:<br />

ball.velocity=maxv*vector(uniform(-1,1),uniform(-1,1),uniform(-1,1))<br />

gdzie maxv jest zmienną określającą maksymalną wartość poszczególnej składowej - w<br />

naszym przypadku maxv=2<br />

• Kontrola nad wieloma atomami<br />

Aby móc symulować zachowanie się gazu w zbiorniku musimy mieć wiele atomów<br />

wewnątrz naczynia. Jedną z prostych dróg prowadzących do łatwej i wygodnej kontroli<br />

parametrów dużej liczby obiektów jest umieszczenie ich w liście.<br />

Aby to zrobić należy zmodyfikować inicjowanie atomów do postaci:<br />

# inicjowanie atomow<br />

no_particles=10<br />

ball_radius=0.4<br />

maxpos=side-.5*thk-ball_radius<br />

maxv=1.0<br />

ball_list=[]<br />

for i in range(no_particles):<br />

ball=sphere(color=color.green,radius=ball_radius)<br />

ball.pos=maxpos*vector(uniform(-1,1),uniform(-1,1),uniform(-1,1))<br />

ball.velocity=maxv*vector(uniform(-1,1),uniform(-1,1),uniform(-1,1))<br />

68


all_list.append(ball)<br />

• Konstrukcja pętli for w Python jest odrobinę inna niż w wielu innych językach<br />

programowania [1]. Python wykonuje powtarzanie przez listę. Zatem aby otrzymać pętlę<br />

przechodzącą przez dany zakres liczb musimy utworzyć listę (range) liczb całkowitych<br />

pomiędzy 0 i liczbą atomów.<br />

W ten sposób otrzymamy listę nazwaną ball_list zawierającą zadaną liczbę atomów.<br />

Dowolny typ obiektu może by umieszczony w liście. Jeżeli uruchomimy powyższy skrypt<br />

zobaczymy 10 kulek wewnątrz naczynia, lecz tylko jedna z kulek (ostatnia zainicjowana<br />

przez pętlę) będzie się poruszać (patrz plik 02_02_gaz.py).<br />

Aby móc animować wszystkie atomy umieszczone w liście musimy użyć kolejnej pętli,<br />

która będzie przechodzić przez wszystkie elementy listy, za każdym razem aktualizując<br />

położenie obiektu (patrz plik 02_03_gaz.py). Pętla będzie zatem wyglądać następująco:<br />

while (1==1):<br />

# Set number of times loop is repeated per second<br />

rate(100)<br />

# Loop over list of particles<br />

# Move and check wall collisions<br />

for ball in ball_list:<br />

# Move ball(s)<br />

ball.pos = ball.pos + ball.velocity*timestep<br />

#check for collisions with the walls<br />

• Zderzenia pomiędzy między atomami.<br />

Można poeksperymentować z większą liczbą atomów, zmienianiem ich promieni oraz<br />

koloru etc. W tym momencie program pozwala kontrolować pozycję każdego z atomów<br />

oraz obsługuje kolizję każdego z nich w przypadku zderzenia z dowolną ścianką naczynia.<br />

Model ten jednakże posiada jeszcze jedną dość istotną wadę, ponieważ atomy<br />

przemieszczając się wewnątrz naczynia przelatują przez siebie na wzajem. Aby model ten<br />

stał się bardziej realistyczny musimy uwzględnić wzajemne zderzenia pomiędzy atomami.<br />

Różne rodzaje oddziaływań mogą być zaimplementowane w zależności od tego jaki typ<br />

gazu chcemy symulować. Dla gazu doskonałego zderzenia są doskonale sprężyste to znaczy<br />

takiego samego typu jak w przypadku zderzenia atomu ze ścianą naczynia.<br />

W każdym kroku pętli musimy sprawdzać czy nie nastąpiła kolizja pomiędzy atomami. Dla<br />

każdej pary atomów musimy sprawdzać czy odległość pomiędzy nimi jest mniejsza niż<br />

suma ich promieni. I tak na przykład umieszczając, na samym końcu programu czyli po<br />

części odpowiedzialnej za aktualizację położeń atomów, poniższy skrypt, otrzymamy<br />

algorytm, który będzie wykrywał kolizje pomiędzy atomami. Wcięcie dla tego skryptu<br />

powinno być równe z wcięciem dla funkcji rate().<br />

# Ball Collision Detection<br />

for ball in ball_list:<br />

for otherball in ball_list:<br />

if not ball == otherball:<br />

distance=mag(otherball.pos-ball.pos)<br />

if distance


Działanie tej części skryptu jest następujące. Dla każdego atomu umieszczonego na liście<br />

jest sprawdzana odległość pomiędzy danym atomem i wszystkimi innymi atomami<br />

sąsiednimi. Jeżeli ta odległość jest mniejsza niż suma dwóch promieni algorytm wypisze na<br />

ekranie słowo „collision” (ang. zderzenie) oraz poda odległość pomiędzy atomami, między<br />

którymi doszło do kolizji. Nie sprawdza natomiast czy doszło do zderzenia atomu samego<br />

ze sobą gdyż to niemiałoby sensu. Funkcja mag zwraca długość wektora (skalar) pomiędzy<br />

atomami. (patrz plik 02_04_gaz.py)<br />

Po uruchomieniu programu zauważyć można że słowo „kolizja” jest wypisywane dwa razy<br />

dla każdej pary atomów. Dzieje się tak dlatego, że w pętli sprawdzana jest detekcja danego<br />

atomu z listy każdym atomem sąsiednim. Aby sprawdzanie dla danej pary atomów nastąpiło<br />

tylko raz niezbędna jest modyfikacja pętli. W poprawionej wersji sprawdzanie kolizji<br />

następuje dla atomu o numerze indeksu „i” z atomami o numerach indeksu wyższym niż „i”.<br />

# Ball Collision Detection<br />

for i in range(no_particles):<br />

for j in range(i+1,no_particles):<br />

distance=mag(ball_list[i].pos-ball_list[j].pos)<br />

if distance


exchange=vj-vi<br />

#exchange momentum<br />

ball_list[i].velocity=ball_list[i].velocity +<br />

exchange*direction<br />

ball_list[j].velocity=ball_list[j].velocity -<br />

exchange*direction<br />

Funkcja norm() zwraca wektor o tym samym kierunku jak kierunek wektora wejściowego,<br />

ale o długości znormalizowanej do jedności.<br />

Zatem mając wektor a o współrzędnych x , y , z , możemy obliczyć wektor<br />

jednostkowy b za pomocą wzoru: b= ∣a∣ x<br />

∣a∣ i y<br />

∣a∣ j z k<br />

I tak na przykład pisząc:<br />

a=vector(3.0,4.0,0.0)<br />

b=norm(a)<br />

print b<br />

otrzymamy wektor jednostkowy o składowych: [0.6 0.8 0]<br />

Funkcja dot() zwraca iloczyn skalarny dwóch wektorów, w naszym przypadku jest to<br />

iloczyn skalarny pomiędzy prędkością i-tego (lub j-tego) atomu i kierunkiem linii akcji na<br />

której odbywa się zderzenie.<br />

Rysunek 50: Linia akcji zderzających się kul.<br />

Rozpatrzmy działanie tego skryptu na prostym przykładzie. Załóżmy, że promienie obu<br />

atomów są jednakowe R 1<br />

=R 2<br />

=1 , prędkość pierwszego atomu v 1<br />

=0.0 ,0 .0,0 .0 a<br />

drugiego v 2<br />

=−5.0 ,0.0,0 .0 , detekcja zderzenia następuje w momencie gdy atom i<br />

znajduje się w punkcie p 1<br />

=−1.34 ,−1.34 ,0 .0 a atom j w punkcie p 2<br />

=0.0 , 0.0 ,0.0<br />

odległość między atomami wynosi 1.895 , direction=−0.707 ,−0.707 ,0. ,<br />

v i<br />

=0.0 , v j<br />

=3.536 , parametr exchange=3.536 . Nowe prędkości wyniosą<br />

odpowiednio: v 1<br />

=−2.5 ,−2.5 , 0 oraz v 2<br />

=−2.5 ,2.5 , 0 .<br />

Przeanalizowanie tego zagadnienia może stać się bardziej przejrzyste gdy rozważymy<br />

zderzenie centralne na wprost.<br />

71


Napisany program umożliwia wizualizację zachowania się 10 atomów, wewnątrz<br />

zamkniętego zbiornika. Nie mniej jednak nie jest to końcowa wersja programu gdyż<br />

modyfikacji jakich możemy dokonać jest bardzo wiele. Możemy na przykład zmieniać kolor<br />

atomów, które właśnie kolidują ze sobą na wzajem lub ze ścianką zbiornika (plik<br />

02_06_gaz_blyski.py). Należy przy tym pamiętać o zmniejszeniu kroku czasowego by<br />

wizualizacja dla kul, o innym kolorze trwała dłużej. Możemy także dokonywać przerwy w<br />

symulacji w momencie gdy następuje detekcja zderzenia i uruchamiać ponownie na<br />

przykład po naciśnięciu dowolnego klawisza itp.<br />

Warto jednak skupić się bardziej na precyzji wykonywania symulacji, gdyż posiada ona<br />

jeszcze jedną dość znaczącą wadę. Detekcja kolizji między atomami następuje w momencie<br />

gdy odległość między atomami jest mniejsza niż suma ich promieni co powoduje, że w<br />

wyniku błędów numerycznych (lub w wyniku inicjacji położeń atomów, która opiera się<br />

o algorytm losowego dobierania położeń) może dochodzić do sklejenia się atomów między<br />

sobą. Aby wyeliminować ten niekorzystny efekt musimy podczas każdego zderzenia<br />

kontrolować odległość pomiędzy atomami: (patrz plik 02_07_gaz.py)<br />

#adjust position<br />

overlap=2*ball_radius-distance<br />

ball_list[i].pos=ball_list[i].pos - overlap*direction<br />

ball_list[j].pos=ball_list[j].pos + overlap*direction<br />

• Szybkość wykonywania programu.<br />

Program, który został napisany ilustruje prosty model zachowania się atomów w gazie<br />

doskonałym. Możemy eksperymentować zmieniając liczbę atomów ich rozmiar a także<br />

średnią prędkość. Kiedy dodamy więcej atomów zauważymy natychmiast, że szybkość<br />

wykonywania programu znacznie spadła. Zwiększenie dwukrotnie liczby atomów powoduje<br />

około czterokrotne zmniejszenie szybkości wykonywania programu. Dzieje się tak dla tego,<br />

że liczba kroków niezbędna do sprawdzenia kolizji wzrasta z kwadratem liczby atomów.<br />

Wiele symulacji komputerowych opiera się o algorytmy obliczeniowe mające złożoność<br />

obliczeniową typu N 2 . Tego typu symulacje zawierające dużą liczbę atomów wymagają<br />

komputerów o dużych możliwościach obliczeniowych.<br />

Zwiększanie szybkości wykonywania programu może zająć większą część czasu jaki<br />

poświęca się na napisanie programu, dlatego nie należy lekceważyć tego problemu.<br />

Taktyka dla ulepszania efektywności wykonywania programu obejmuje między innymi:<br />

- usuwanie niepotrzebnych kalkulacji (obliczanie odległości pomiędzy atomami<br />

wymagające znalezienia pierwiastka kwadratowego jest bardzo pracochłonne, co znacznie<br />

wydłuża wykonywanie algorytmu. Sprawdzanie odległości pomiędzy atomami w celu<br />

wykrycia kolizji może być zrealizowane w prostszy sposób poprzez porównanie kwadratu<br />

odległości pomiędzy atomami co jest znacznie skróci czas wykonywania algorytmu)<br />

- zapisywanie wyników obliczeń zamiast wielokrotnego powtarzania ich<br />

- używanie bardziej wydajnych konstrukcji dla przechowywania danych (na przykład<br />

elementy list w pythonie nie są konieczne dla przechowywania wszystkich typów danych.<br />

Użycie tablic powoduje, że program staje się bardziej elastyczny w różnych zastosowaniach,<br />

lecz dostęp do tych danych będzie zajmował więcej czasu. Niemniej jednak tablice języka<br />

Python posiadają każdy element tego samego typu co powoduje, że niektóre operacje<br />

wykonywane na tablicach są szybsze)<br />

- stosowanie ulepszonych matematycznie algorytmów pozwalających używać większych<br />

72


kroków czasowych przy zachowaniu dużej precyzji obliczeniowej<br />

- minimalizację liczby atomów niezbędnych dla danego modelu<br />

- implementację tylko najważniejszych oddziaływań<br />

• Symulacja zachowania się gazu doskonałego w zamkniętym naczyniu - algorytm oparty o<br />

tablice przeznaczone dla szybkich obliczeń<br />

Najistotniejszą różnicą pomiędzy algorytmem poprzednim i obecnym jest to, że w obecnej<br />

wersji dane dostarczane w postaci tablic a nie jak to było wcześniej w postaci list. Jeżeli<br />

umieścimy je w tablicy, typ danych będzie rozpoznawany tylko raz a nie jak jest to w<br />

przypadku listy, za każdym razem gdy jest czytany nowy element.<br />

Przed pętlą odpowiedzialną za ruch atomów inicjujemy tablicę ltriang. Określa ona<br />

między którymi atomami będziemy musieli wyliczać kwadraty odległości.<br />

ltriang=fromfunction(lambda<br />

i,j: less_equal(j,i),[no_particles,no_particles])<br />

Przesuwanie atomów odbywa się przy pomocy tej samej techniki co poprzednio (metoda<br />

Eulera) z tym, że obecnie wykonywane jest ono z użyciem tablicy [2].<br />

pos_array=pos_array+p_array*dt<br />

Sprawdzanie czy nie doszło do kolizji atomu ze ścianką polega na nałożeniu maski na<br />

tablicę położeń. Zadaniem tej maski w pierwszym przypadku jest sprawdzanie czy pozycja<br />

(a bardziej precyzyjnie - którakolwiek współrzędna) atomu jest większa niż dozwolona<br />

odległość jaką dopuszcza zmienna maxpos i jeżeli odległość ta jest większa dla jakiegoś<br />

atomu, to następuje zmiana odpowiedniej składowej wektora prędkości na przeciwny oraz<br />

skorygowanie pozycji atomu. W ten sposób ograniczamy pozycję atomu jednocześnie w<br />

trzech płaszczyznach (x, y oraz z) znajdujących się w odległości maxpos od początku<br />

układu współrzędnych.<br />

# Check wall collisions<br />

putmask(p_array,greater(pos_array,maxpos),-p_array)<br />

putmask(pos_array,greater(pos_array,maxpos),2*maxpos-pos_array)<br />

Dopełnieniem powyższego warunku jest sprawdzanie czy poszczególne składowe położeń<br />

atomu nie są mniejsze niż dopuszczalna wartość -maxpos. Jest to realizowane w sposób<br />

analogiczny do powyższego<br />

putmask(p_array,less_equal(pos_array,-maxpos),-p_array)<br />

putmask(pos_array,less_equal(pos_array,-maxpos),-2*maxpos-pos_array)<br />

Detekcja zderzeń między atomami odbywa się w prostszy sposób (pod względem obliczeń)<br />

niż w algorytmie poprzednim. W pierwszej kolejności wyznaczamy wzajemne odległości<br />

pomiędzy atomami wyrażając je jako wektory<br />

# Check particle collisions<br />

separation=pos_array-pos_array[:,NewAxis]<br />

a następnie wyznaczamy te odległości w formie skalarów<br />

sepmag2=add.reduce(separation*separation,-1)<br />

73


#to samo możemy napisać jako:<br />

#sepmag2=sum(separation*separation,-1)<br />

Nałożenie maski na tablicę określającą, między którymi atomami należy obliczać kwadraty<br />

odległości<br />

putmask(sepmag2,ltriang,4*D2)<br />

Kolejnym krokiem jest wykrycie zderzenia<br />

hit=less_equal(sepmag2,D2)<br />

i dokonanie zamiany tablicy na listę, z której usuwamy zera, po czym sortujemy.<br />

hit_list=sort(nonzero(hit.flat))<br />

Dla każdego zderzenia otrzymujemy liczbę, która po rozkodowaniu umożliwia dostęp od<br />

pozycji i prędkości atomów. Kolejne linie dotyczą wymiany składowych prędkości wzdłuż<br />

linii działania oraz modyfikacji położeń pomiędzy atomami, które zderzyły się ze sobą.<br />

for ij in hit_list:<br />

i, j = divmod(ij,no_particles)<br />

#print "i",i,"j",j<br />

sepmag=sqrt(sepmag2[i,j])<br />

#print "sepmag",sepmag<br />

direction=separation[i,j]/sepmag<br />

pi=dot(p_array[i],direction)<br />

pj=dot(p_array[j],direction)<br />

exchange=pj-pi<br />

p_array[i]=p_array[i]+exchange*direction<br />

p_array[j]=p_array[j]-exchange*direction<br />

overlap=2*ball_radius-sepmag<br />

pos_array[i]=pos_array[i]-overlap*direction<br />

pos_array[j]=pos_array[j]+overlap*direction<br />

W końcu by zobaczyć działanie algorytmu musimy dokonać aktualizacji położeń<br />

i prędkości. (patrz plik 02_08_gaz_quick_array_full.py)<br />

# Update position and velocity of balls<br />

for i in arange(len(ball_list)):<br />

ball_list[i].pos=vector(pos_array[i])<br />

ball_list[i].velocity=vector(p_array[i])<br />

• Rozkład prędkości atomów w gazie.<br />

Używając obojętnie którego z tych algorytmów symulujących zachowanie się gazu<br />

doskonałego, będziemy potrzebowali wyciągnąć informacje jakie zwykle podajemy przy<br />

opisie gazu, bez konieczności przeprowadzania ciągłej obserwacji atomów odbijających się<br />

wewnątrz naczynia.<br />

Na samym początku nadaliśmy wszystkim atomom prędkości w każdym kierunku według<br />

jednolitego rozkładu jaki zapewnia nam funkcja uniform(). Analiza statystyczna<br />

podpowiada, iż bardziej prawdopodobny jest rozkład statystyczny, w którym będziemy mieli<br />

więcej atomów o niższej prędkości i „długi ogon” dla atomów o wyższej prędkości.<br />

74


Możemy zatem się spodziewać, że gaz będzie ewoluował do takiego rozkładu prędkości.<br />

Do wizualizacji niezbędny będzie wykres typu histogram, aby pokazać jak wiele atomów<br />

znajduje się w zadanym przedziale prędkości. Wykres należy zainicjować przed główną<br />

pętlą odpowiedzialną za aktualizację położeń atomów.<br />

graphwindow=gdisplay(xtitle='v_x',ytitle='N',ymax=no_particles/2)<br />

velocity_dist=ghistogram(bins=arange(0,2*maxv,maxv/5))<br />

Zmienna velocity_dist przechowuje parametry histogramu. W tym przypadku będzie on<br />

posiadał dziesięć przedziałów do których zastaną przyporządkowane ilości atomów<br />

o zadanej przez dany przedział prędkości. Można oczywiście zmienić ilość atomów w<br />

zbiorniku. Wykres będzie wyświetlony w nowym oknie. Aby narysować rozkład<br />

składowych x-owych prędkości należy stworzyć listę zawierającą owe składowe prędkości.<br />

Zawarte w niej dane należy przekazać do histogramu przy pomocy funkcji plot().<br />

v_list=[]<br />

for ball in ball_list:<br />

v_list.append(abs(ball.velocity.x))<br />

velocity_dist.plot(data=v_list)<br />

Umieszczając powyższe linie kodu wewnątrz głównej pętli zobaczymy jak rozkład<br />

prędkości ewoluuje w czasie. Jeżeli nie symulujemy ruchu dla wielu atomów rozkład<br />

prędkości będzie bardzo dynamicznie się zmieniał (patrz plik 02_09_gaz_hist_a.py). Aby<br />

zredukować ten mankament musimy uśrednić rozkład w czasie. Zmiana polega na<br />

zmodyfikowaniu parametrów wykresu a tym samym sposobu wyznaczania zmiennej<br />

velocity_dist. W nowej wersji powinna mieć ona postać (plik 02_10_gaz_hist_b.py):<br />

velocity_dist=ghistogram(bins=arange(0,2*maxv,maxv/5), accumulate=1,<br />

average=1)<br />

Pierwszy z dodanych parametrów (accumulate=1) jest odpowiedzialny za sumowanie<br />

danych dostarczanych w każdym kroku czasowy, natomiast drugi (average=1) wyznacza ich<br />

średnią arytmetyczną. Dla bardzo dużych ilości atomów fluktuacje będą nieznaczne<br />

i modyfikacja nie będzie konieczna.<br />

Jeżeli używamy szybkiej wersji programu symulującego gaz doskonały prędkości są<br />

przechowywane w tablicy p_array i mogą być przekształcone w histogram w następujący<br />

sposób:<br />

velocity_dist.plot(data=abs(p_array[:,0]))<br />

Tabela. Przykładowe iksowe składowe prędkości, jakie miały atomy w jednym z kolejnych<br />

kroków czasowych.<br />

Lp. atomu<br />

iksowa składowa prędkości<br />

1 -0.812<br />

2 -0.0634<br />

3 0.064<br />

4 -0.972<br />

5 -0.774<br />

6 0.132<br />

7 0.162<br />

8 -0.559<br />

9 0.787<br />

10 -0.263<br />

75


Otrzymany rozkład składowych prędkości będzie bardzo specyficzny ponieważ należy<br />

pamiętać o tym, że na histogramach tworzonych w języku Python nie mamy możliwości<br />

zobrazowania danych posiadających ujemne wartości. Niedogodność tą jednak można<br />

w prosty sposób zniwelować. Niemniej jednak chcemy tylko zobaczyć czy nadane składowe<br />

prędkości uzyskane z jednolitego rozkładu, w jakiś sposób ewoluowały. Pod uwagę<br />

bierzemy atomy posiadające składowe iksowe zarówno dodatnie jak i ujemne i wyliczamy<br />

dla każdej z nich jej wartość bezwzględną.<br />

• Porównanie z oczekiwanym wynikiem<br />

Wynik teoretyczny może być naniesiony na wykres w postaci krzywej.<br />

expected_distribution=gcurve(color=color.green)<br />

for vx in arange(0,2*maxv,maxv/20):<br />

expected_distribution.plot(pos = (vx,.27*no_particles*exp(-<br />

vx**2/maxv**2*3/2)))<br />

Powyższą część skryptu należy umieścić tuż przed główną pętla, gdyż zależność ta będzie<br />

nanoszona na wykres tylko raz. W przypadku gdy zmienimy liczbę przedziałów prędkości<br />

zależność tę będziemy musieli przeskalować.<br />

Rysunek 51: Porównanie wyników symulacji (niebieskie słupki) z wartością<br />

teoretyczną (zielona linia).<br />

Jak widać z powyższej ilustracji rozkład składowych prędkości uległ zmianie. Gdyby tak nie<br />

było, ilość atomów odpowiadająca danemu przedziałowi prędkości była by jednakowa. Już<br />

w modelu o tak niskim stopniu zaawansowania można zauważyć, że prędkości<br />

scharakteryzowane są przez pewien rozkład. Zmodyfikujmy program i w każdym kroku<br />

czasowym wyliczmy prędkość atomu.<br />

76


# Plot the velocity histogram<br />

v_list=[]<br />

for ball in ball_list:<br />

#v_list.append(abs(ball.velocity.x))<br />

v_list.append((sqrt(ball.velocity.x*ball.velocity.x+ball.velocity.<br />

y*ball.velocity.y+ball.velocity.z*ball.velocity.z)))<br />

velocity_dist.plot(data=v_list)<br />

Może to nieznacznie wpłynąć na szybkość wykonywania programu, lecz uzyskamy w ten<br />

sposób wykres, który w bardziej czytelny sposób pozwoli zorientować się jak kształtuje się<br />

rozkład prędkości w gazie. (plik 02_11_gaz_hist_c.py)<br />

Rysunek 52: Rozkład prędkości atomów przybiera charakterystyczny kształt krzywej Gaussa.<br />

• Bardziej zaawansowane symulacje.<br />

Możemy rozbudować program w celu symulacji bardziej złożonych problemów na<br />

przykład:<br />

- symulacji <strong>zjawisk</strong>a dyfuzji dwóch gazów idealnych (patrz skrypt quicktwogas.py)<br />

- rozkładu prędkości w sytuacji gdy gaz składa się z atomów o różnych masach<br />

- symulacji zachowania się gazu w zbiorniku z ruchomym tłokiem (patrz skrypt<br />

quickbounce2.py)<br />

Inne sytuacje jakie mogą być modelowane przy użyciu podobnej techniki<br />

Technika śledzenia pojedynczych punktów materialnych, obliczania przyspieszeń tych<br />

punktów i przewidywania późniejszego położenia względem innych punktów znajdujących<br />

się w ruchu może być użyta w szerokiej gamie sytuacji:<br />

- dynamika molekuł i obliczenia z dziedziny chemii mogą tym sposobem ustalać kształt,<br />

częstości rezonansowe i energię wiązania molekuł. Najtrudniejszą sprawą w takich<br />

przypadkach jest ustalenie oddziaływań, które zależą bardzo często od względnych pozycji<br />

więcej niż dwóch atomów, a także od kątów między nimi<br />

- oddziaływania grawitacyjne wyznaczają orbity planet na przykład w układzie słonecznym.<br />

Precyzyjne wyznaczenie tych orbit może odpowiedzieć na pytanie czy układ planetarny jest<br />

stabilny czy chaotyczny, czy orbity planet mogą drastycznie się zmienić w przyszłości jeżeli<br />

do danego układu planetarnego wpadnie asteroida lub planetoida itp.<br />

77


- Zachowanie zwierząt. Zwierzęta kontrolują pozycję innych osobników i obiektów wokół<br />

siebie i na tej podstawie decydują o swoim zachowaniu. Zachowanie to może dotyczyć<br />

zdobywania jedzenia, wypatrywania drapieżników, utrzymywania się w zwartych stadach<br />

gdzie nie może dochodzić do wzajemnych kolizji.<br />

- Tego typu symulacje mogą też służyć do kierowania przepływem ruchu w przypadku<br />

projektowania wyjść ewakuacyjnych z budynków, projektowania systemów ulicznych w<br />

celu minimalizowania zatorów, szkolenia kierowców w technikach unikania kolizji na<br />

drodze itp.<br />

78


Kinetyczny model gazu doskonałego.<br />

Inspiracją do powstania tego modelu stał się standardowy skrypt VPython'a, który można znaleźć w<br />

katalogu C:\Program Files\Python23\Lib\site-packages\visual\demos\gas.py<br />

Stanowi on bardziej złożoną (pod względem ilości parametrów <strong>fizycznych</strong>) i udoskonaloną (pod<br />

względem detekcji i odpowiedzi zderzenia) wersję programu symulującego zachowanie się gazu<br />

doskonałego.<br />

1. Wprowadzenie<br />

Poprzednie modele gazu były czysto mechaniczne. Jednakże przy ich pomocy można było<br />

zobaczyć, że prędkości poszczególnych atomów w gazie nie są jednakowe. Kolejny model<br />

jaki zostanie przedstawiony będzie uwzględniał więcej <strong>fizycznych</strong> parametrów takich jak<br />

masa atomów, temperatura, prędkości poszczególnych atomów będą zbliżone do<br />

rzeczywistych. Jako przykład rozważymy zbiornik zawierający hel albo jakiś inny gaz<br />

szlachetny, którego cząsteczki są pojedynczymi atomami bez żadnych ruchów<br />

wewnątrzcząsteczkowych. Będą to mogły być również pary jakiegoś gazu na przykład rtęci<br />

przy założeniu, że temperatura będzie dostatecznie wysoka aby dany pierwiastek mógł<br />

występować w stanie lotnym. Jeśli cząsteczka jest złożona, to mogą w niej występować<br />

ruchy wewnętrzne, na przykład drgania lub rotacje. W rozpatrywanym modelu nie będą<br />

uwzględniane takie przypadki a energia kinetyczna ruchu środka masy atomu będzie<br />

stanowić jego całkowitą energię. Dla gazu jednoatomowego energia całkowita jest więc jego<br />

energią kinetyczną. Porównany zostanie też rozkład prędkości cząsteczek w symulowanym<br />

modelu z rozkładem Maxwell'a.<br />

2. Zainicjowanie obiektów i nadanie im parametrów<br />

Jak zwykle zaczynamy 9 od umieszczenia na samym początku programu procedur<br />

importujących niezbędne moduły, tworzących okno 3D oraz zbiornik, w którym umieścimy<br />

gaz (plik 03_gas_adaptacja10.py)<br />

from visual import *<br />

from visual.graph import *<br />

from random import random<br />

from random import uniform<br />

#---------------------------------------<br />

# wykonanie zbiornika<br />

#---------------------------------------<br />

# parametry mogace podlegac zmianom<br />

d_sc = 0.05<br />

# grubosc scianki<br />

half_L = 4.0<br />

# polowa dlugosci zbiornika<br />

d_b = half_L - d_sc<br />

# dlugosc scianek bocznych<br />

d_g = half_L + d_sc<br />

# dlugosc scianek gornej i dolnej<br />

kolor_zbiornika=(0.0,0.7,0.7)<br />

kolor_zbiornika2=(1.0,0.6,0.2)<br />

#----------------------------------------<br />

scianka_prawa = box (pos=vector( half_L, half_L/2.,<br />

9 Uwaga: niektóre części skryptu zostały pominięte. Kompletny działający kod znajduje się na dołączonej płycie CD<br />

79


half_L/2.),length=d_sc, height=d_b, width=d_g,color = kolor_zbiornika)<br />

scianka_lewa = box (pos=vector(0, half_L/2., half_L/2.),length=d_sc,<br />

height=d_b,width=d_g,color = kolor_zbiornika)<br />

scianka_dolna = box (pos=vector(half_L/2., 0, half_L/2.),length=d_g,<br />

height=d_sc, width=d_g,color = kolor_zbiornika2)<br />

scianka_gorna = box (pos=vector(half_L/2., half_L, half_L/2.),length=d_g,<br />

height=d_sc, width=d_g,color = kolor_zbiornika2)<br />

scianka_tylna = box(pos=vector(half_L/2., half_L/2., 0),length=d_b,<br />

height=d_b, width=d_sc,color = kolor_zbiornika2)<br />

Następnie przystępujemy do zdefiniowania parametrów atomów i niezbędnych stałych<br />

<strong>fizycznych</strong><br />

# parametry mogace podlegac zmianom<br />

Nb_of_atoms = 30<br />

# zmien aby otrzymac wiecej lub mniej atomow<br />

Masa_atom = 4E-3/6E23 # masa atomu helu<br />

R_atom = 0.3<br />

# widoczny rozmiar atomu<br />

k = 1.4E-23<br />

# stala Boltzmana<br />

T = 300.0<br />

# [K] temperatura gazu<br />

max_speed = 6000.0<br />

# przewidywalna maksymalna predkosc atomow<br />

dt = 1E-5<br />

# krok czasowy symulacji<br />

Kolor_atomu = (0.0,0.4,1.0)<br />

maxpos=half_L-0.5*d_sc-R_atom #maksymalna pozycja atomu (przy losowaniu)<br />

Do przechowywania danych będziemy potrzebować następujące tablice<br />

#puste listy do przechowywania obiektów i zmiennych<br />

Atoms = [] #obiekty - atomy<br />

poslist = [] #lista położeń<br />

plist = [] #lista pędów atomów<br />

mlist = [] #lista mas<br />

rlist = [] #lista promieni<br />

#inicjacja atomow i nadanie im atrybutow<br />

for i in range(Nb_of_atoms):<br />

#global Masa_atom,R_atom<br />

ppp=maxpos*vector(uniform(0.5*d_sc+R_atom,1),uniform(0.5*d_sc+R_atom,1<br />

),uniform(0.5*d_sc+R_atom,1))<br />

Atoms = Atoms+[sphere(pos=(ppp), radius=R_atom, color=Kolor_atomu)]<br />

#srednia energia kinetyczna p**2/(2mass) = (3/2)kT<br />

#zatem ped bedzie dany wzorem<br />

ped_sredni = sqrt(2.*Masa_atom*1.5*k*T) # average kinetic energy<br />

p**2/(2mass) = (3/2)kT<br />

theta = pi*random()<br />

phi = 2*pi*random()<br />

#wsp sferyczne<br />

px = ped_sredni*sin(theta)*cos(phi)<br />

py = ped_sredni*sin(theta)*sin(phi)<br />

pz = ped_sredni*cos(theta)<br />

poslist.append((ppp))<br />

plist.append((px,py,pz))<br />

mlist.append(Masa_atom)<br />

rlist.append(R_atom)<br />

# by byly mozliwe operacje na tablicach dokonujemy ich skopiowania<br />

80


pos = array(poslist)<br />

p = array(plist)<br />

m = array(mlist)<br />

#zamiana z wiersza (n-atomow) na kolumne o tylu wierszach<br />

m.shape = (Nb_of_atoms,1) # Numeric Python: (1 by Natoms) vs. (Natoms by<br />

1)<br />

radius = array(rlist)<br />

t = 0.0<br />

pos = pos+(p/m)*(dt/2.)<br />

# wyzerowanie czasu<br />

# initial half-step<br />

3. Wizualizacja danych w postaci histogramu<br />

Prawo rozkładu prędkości atomów w gazie dotyczy najbardziej prawdopodobnego rozkładu<br />

prędkości dużej liczby atomów gazu [8]. Dla próbki zawierającej N cząsteczek gazu<br />

3<br />

m<br />

przyjmuje ono postać: N v=4 N<br />

2 k T <br />

2<br />

2 v 2 e −mV 2kT <br />

gdzie N v jest liczbą<br />

cząsteczek w próbce posiadających prędkości zawarte w przedziale v i vdv . T -<br />

temperatura bezwzględna, k - stała Boltzmanna.<br />

Aby zobaczyć jak kształtuje się rozkład prędkości cząsteczek w prezentowanym modelu<br />

posłużymy się funkcją ghistogram(), która częściowo automatyzuje proces tworzenia<br />

histogramu. Prędkości atomów przed wizualizacją muszą być sortowane, oraz musi być<br />

znana liczba atomów mieszcząca się w danym przedziale prędkości, przy czym obie<br />

czynności należy wykonywać w każdym kroku czasowym. Dodatkowo chcemy aby dane<br />

uzyskane w jednym kroku czasowym były dodawane do danych z kolejnego kroku, a wynik<br />

wyświetlany był w postaci średniej tych wartości. Wszystkie omówione czynności funkcja<br />

ghistogram() może wykonywać automatycznie. W przypadku krzywej teoretycznej musimy<br />

jednak pamiętać, że wartości umieszczane na osi igreków należy dodatkowo pomnożyć<br />

przez wartość przedziału prędkości czyli zmienną deltav. Uzyskamy w ten sposób na osi<br />

igreków liczbę atomów zawartych w danym przedziale prędkości. Jest to jedyna różnica<br />

o której musimy pamiętać gdy będziemy chcieli obliczyć całkę N =∫<br />

0<br />

∞<br />

N vdv<br />

poznać jaka jest całkowita liczba atomów, wtedy wartości deltav nie należy brać pod uwagę.<br />

# histogram<br />

czyli<br />

#przedzialy predkosci co deltav<br />

deltav = 100.<br />

vdist = gdisplay(x=0, y=frm, ymax = Nb_of_atoms*deltav/1000.,<br />

width=800, height=700-frm, xtitle='v',<br />

ytitle='dN',foreground=pierwszoplanowy, background=drugoplanowy)<br />

theory = gcurve(color=color.red)<br />

observation = ghistogram(bins=arange(0.,max_speed,deltav),<br />

accumulate=1, average=1,<br />

color=Kolor_atomu)<br />

dv = 10.<br />

for v in arange(0.,max_speed+1+dv,dv): # theoretical prediction<br />

theory.plot(pos=(v,(deltav)*Nb_of_atoms*4.*pi*((Masa_atom/(2.*pi*k*T))<br />

**1.5)<br />

*exp((-0.5*Masa_atom*v**2)/(k*T))*v**2))<br />

81


4. Główna pętla.<br />

Zajmijmy się teraz konkretami czyli pętlą odpowiedzialną za przesuwanie atomów, detekcję<br />

wzajemnych zderzeń między atomami i ze ściankami naczynia.<br />

Pętlę rozpoczynamy od opcjonalnej funkcji ograniczającej szybkość wykonywania<br />

algorytmu. Umieszczona tu została w linii komentarza ponieważ obecny algorytm jest<br />

o wiele bardziej złożony od poprzednich, lecz w przypadku gdy będziemy symulować<br />

zachowanie się niewielkiej liczby atomów musimy pamiętać o tym, iż może okazać się<br />

niezbędny do ograniczenia tempa. Kolejna linia kodu odpowiedzialna jest za obliczenie<br />

wartości prędkości każdego z atomów, przekształcenie tych danych na listę i przekazanie<br />

tych informacji do histogramu, gdzie są on sortowane uśredniane a następnie wyświetlane.<br />

Następna linia aktualizuje tablicę przechowującą dane z położeniami atomów.<br />

while 1:<br />

#rate(30)<br />

observation.plot(data=mag(p/m))<br />

# Uaktualnienie tablicy zawierajacej polozenia<br />

pos += (p/m)*dt<br />

#czyli x = x_0 + v*t<br />

W każdym kroku pętli będziemy sprawdzać czy nie doszło do kolizji pomiędzy atomami.<br />

W tym celu wyznaczamy wzajemne odległości między atomami najpierw w formie<br />

wektorów, a później wyliczamy ich długość (wielkość skalarną).<br />

r = pos-pos[:,NewAxis] # wyznaczenie odległości pomiędzy atomami jako<br />

wektorów<br />

rmag = sqrt(add.reduce(r*r,-1)) # przekształcenie tych odległości na<br />

skalary<br />

hit = less_equal(rmag,radius+radius[:,NewAxis])-identity(Nb_of_atoms)<br />

Jeżeli odległość między dwoma atomami będzie mniejsza niż suma dwóch promieni to<br />

znaczy, że nastąpiła penetracja między atomami i w zmiennej hit pojawią się niezerowe<br />

wartości na przykład:<br />

#przykładowe dane pochodzące ze zderzenia atomów w gazie składającym się z<br />

3 atomów<br />

R_atom=0.3 #promień atomu<br />

rmag #odległości (skalarnie) pomiędzy atomami<br />

[[ 0. 1.49881131 1.72266461]<br />

[ 1.49881131 0. 0.59806834]<br />

[ 1.72266461 0.59806834 0. ]]<br />

#(na czerwono zostały oznaczone odległości mniejsze niż 2r)<br />

hit=<br />

[[0 0 0]<br />

[0 0 1]<br />

[0 1 0]]<br />

Będzie to oznaczało, że zadziała pierwsza część funkcji zderzenie(). Nim omówiona<br />

zostanie ta funkcja musimy otrzymać dostęp do parametrów kolidujących atomów a tym<br />

samym dowiedzieć się dokładnie, między którymi atomami doszło do zderzenia. W tym<br />

celu przystępujemy do przekształcenia tablicy hit na listę w następujący sposób<br />

82


hitlist = sort(nonzero(hit.flat)).tolist()<br />

atom o numerze „i” kolidujący z atomem o numerze „j” będzie umieszczony w tej liście<br />

w postaci liczby wynoszącej i⋅Nb_of_atoms j na przykład:<br />

hitlist=[5, 7]<br />

przy czym druga liczba to j⋅Nb_of_atomsi (ponieważ tablica rmag zawiera odległości<br />

każdego atomu z każdym – jest symetryczna, więc tablica hit również jest symetryczna a to<br />

powoduje, że hitlist zawierać będzie parę takich liczb).<br />

Nadeszła teraz pora na omówienie działania funkcji zderzenie(), która wprowadzi nieco<br />

porządku. Pierwsza jej część operuje na zmiennej o nazwie bounce, którą w naszym<br />

przypadku jest tablica hitlist. Zatem na każdym elemencie z tablicy wykonywane jest<br />

dzielenie z resztą elementu tablicy przez liczbę atomów, w wyniku czego otrzymujemy parę<br />

liczb „i” oraz „j”. Będą one stanowiły wskaźniki odpowiednio wiersza i kolumny w tablicy<br />

hit ( liczone od zera).<br />

def zderzenie(bounce):<br />

"""funkcja odpowiedzialna za<br />

obsługę<br />

zderzeń miedzy atomami i atomów<br />

ze ściankami naczynia"""<br />

global p<br />

for ij in bounce:<br />

#rozszyfrowanie miedzy<br />

którą para atomów doszło do<br />

zderzenia<br />

i, j =<br />

divmod(ij,Nb_of_atoms) #<br />

rozszyfrowanie dla atomów parami<br />

Jeżeli zmienna hitlist zawiera na przykład<br />

elementy [5, 7] to w wyniku pierwszego<br />

dzielenia otrzymamy parę liczb i=1<br />

oraz j=2 co daje nam odpowiedź na<br />

pytanie, między którą parą atomów doszło<br />

do kolizji. Kolejna liczba z listy hitlist nie<br />

będzie już zatem potrzebna więc po prostu<br />

jest z niej usuwana<br />

hitlist.remove(j*Nb_of_atoms+i)<br />

# usuniecie symetrycznej pary j, i<br />

z listy<br />

Znalezienie pary atomów kolidujących ze<br />

sobą nie oznacza zakończenia procesu<br />

detekcji zderzenia. Gdybyśmy teraz przeszli<br />

do odpowiedzi zderzenia, popełnilibyśmy<br />

błąd. Zgodnie ze wcześniej poczynionymi<br />

założeniami atomy traktujemy jak bryły<br />

sztywne, co wyklucza możliwość<br />

wzajemnego przenikania się tych obiektów.<br />

Rysunek 53: Schemat działania algorytmu detekcji<br />

Dlatego aby móc przejść do odpowiedzi<br />

zderzenia.<br />

83


zderzenia musimy znaleźć położenia atomów w chwili gdy stykały się ze sobą, ponieważ<br />

wtedy właśnie powinna nastąpić detekcja zderzenia.<br />

Na rysunku przedstawiono przykładowe wzajemne położenie atomów w chwili t=0 (rys A)<br />

oraz w następnym kroku czasowym 10 t=dt (rys B) to jest chwili, w której następuje detekcja<br />

zderzenia. Skorygowanie położeń i odpowiedź zderzenia prześledźmy na przykładzie.<br />

Prędkość pierwszego atomu wynosi v i<br />

=5.0,0 .0 ,0 .0 a drugiego v j<br />

= 0.0,0 .0 ,0 .0 .<br />

Zgodzie z rysunkiem (B) przyjmujemy, że w chwili t=dt atom pierwszy znajdował się w<br />

punkcie O i ,t =dt<br />

=−1.8,0 .0 ,0.0 a drugi w początku układu współrzędnych<br />

O j ,t =dt<br />

=0.0 ,0 .0 ,0.0 . Aby dowiedzieć się jakie były położenia atomów w chwili<br />

zetknięcia się ich powierzchni musimy obliczyć czas trwania penetracji obiektów. Znamy<br />

zarówno położenie, prędkości a promienie są równe i wynoszą na przykład r 1<br />

=r 2<br />

=1 .<br />

Zgodnie z rysunkiem B<br />

x=R−K gdzie R=r 1<br />

r 2 , K =∣O i ,t =dt<br />

− O j ,t= dt<br />

∣<br />

więc czas penetracji deltat<br />

deltat= R− K gdzie Dvel=∣v<br />

Dvel<br />

i<br />

− v j<br />

∣<br />

w naszym przykładzie deltat=0.04<br />

położenia atomów muszą być zatem skorygowane<br />

O i ,t =dt<br />

= O i , t=dt<br />

−v i<br />

⋅deltat<br />

O j ,t =dt<br />

= O j ,t= dt<br />

−v j<br />

⋅deltat<br />

i dla chwili zetknięcia wynosić będą one<br />

O i ,t =dt<br />

=2.0 ,0 .0 ,0 .0<br />

O j ,t =dt<br />

=0.0 ,0 .0 ,0.0<br />

część algorytmu wykonująca poprawienie położeń atomów wygląda następująco:<br />

mi = m[i,0]<br />

mj = m[j,0]<br />

R = Atoms[i].radius+Atoms[j].radius<br />

K = mag(pos[i]-pos[j])<br />

Dvel = mag((p[i]/mi)-(p[j]/mj))<br />

deltat=(R-K)/Dvel<br />

#skorygowanie położeń do pozycji zetknięcia czyli cofnięcie o czas deltat<br />

pos[i] = pos[i]-(p[i]/mi)*deltat<br />

pos[j] = pos[j]-(p[j]/mj)*deltat<br />

Można powiedzieć, że „cofnęliśmy się w czasie” o deltat czyli do momentu w którym<br />

obiekty stykają się ze sobą (rys C). Odpowiedź zderzenia to problem fizyczny ruchu<br />

atomów po zderzeniu. W odpowiedzi zderzenia będziemy korzystali z zasady zachowania<br />

pędu. Jak założyliśmy wcześniej zderzenia są doskonale sprężyste, a całkowity pęd<br />

zderzających się atomów nie ulega zmianie.<br />

Zdecydowanie prostsze w obliczeniach jest rozpatrywanie zderzenia dwóch kul w układzie<br />

środka masy (CM Centre of Mass). Jeżeli oznaczymy przez v CM i prędkość pierwszego<br />

atomu w układzie środka masy, v śr M prędkość środka masy, to prędkość atomu<br />

w układzie LAB v i możemy napisać jako<br />

v i<br />

=v CM i<br />

v śr M<br />

Należy jeszcze znaleźć pędy poszczególnych atomów w układzie CM.<br />

p tot<br />

=p i<br />

p j<br />

M =m i<br />

m j<br />

10 Nie należy mylić dt z różniczką, ponieważ w tym przypadku dt oznacza krok czasowy (dyskretny przyrost czasu w<br />

każdym kolejnym kroku pętli). Jest wartością dobraną empirycznie i dla tej symulacji wynosi 1E-5s<br />

84


v CM i<br />

M v śr M<br />

M =v i<br />

M<br />

m i<br />

m i<br />

v CM i<br />

M v śr M<br />

M = m i<br />

m i<br />

v i<br />

M<br />

p CM i<br />

M<br />

m i<br />

p tot<br />

=p i<br />

M<br />

m i<br />

p CM i<br />

p tot<br />

m i<br />

M =p i<br />

m<br />

p CM i<br />

=p i<br />

−p<br />

i<br />

tot<br />

otrzymaliśmy w ten sposób pęd atomu pierwszego w układzie środka<br />

M<br />

masy. Postępując analogicznie w przypadku atomu drugiego v j<br />

=v CM j<br />

v śr M otrzymamy<br />

m<br />

p CM j<br />

= p j<br />

−p<br />

j<br />

tot<br />

M<br />

Są to równania pozwalające transformować pędy poszczególnych atomów z układu LAB do<br />

układu środka masy.<br />

Kolejnym krokiem jest wyznaczenie pędów atomów po zderzeniu. Linię akcji na której<br />

dochodzi do zderzenia oznaczamy przez r<br />

rel a pędy po zderzeniu będą wyrażały się<br />

w sposób następujący:<br />

p CM i<br />

=p CM i<br />

−2 p CM i<br />

⋅r<br />

rel r<br />

rel<br />

p CM j<br />

= p CM j<br />

−2p CM j<br />

⋅r rel r<br />

rel<br />

gdzie p CM i<br />

⋅r<br />

rel , p CM j<br />

⋅r<br />

rel to wartości pędu w układzie CM na linii akcji, więc<br />

w efekcie otrzymamy pędy (jako wielkości wektorowe) po zderzeniu. Następnie powracamy<br />

do układu LAB<br />

p i<br />

=p CM i<br />

p tot<br />

m i<br />

M<br />

p j<br />

= p CM j<br />

p tot<br />

m j<br />

M<br />

Została jeszcze kwestia „cofniętego czasu”. Atomy po zderzeniu kontynuują ruch z nowymi<br />

prędkościami, więc uwzględniając ten fakt musimy wyznaczyć nowe przemieszczenie<br />

w czasie deltat (rys D).<br />

pos i<br />

= pos i<br />

p i<br />

deltat<br />

m i<br />

pos j<br />

= pos j<br />

p j<br />

deltat<br />

m j<br />

część skryptu realizująca wyżej opisane działania wygląda następująco:<br />

ptot = p[i]+p[j] #suma geometryczna<br />

mtot = mi+mj<br />

# przejscie do ukladu CM<br />

pcmi = p[i]-ptot*mi/mtot<br />

pcmj = p[j]-ptot*mj/mtot<br />

# jednostkowy wektor linii akcji<br />

rrel = norm(pos[j]-pos[i])<br />

# zderzenie w ukladzie CM<br />

pcmi = pcmi-2*dot(pcmi,rrel)*rrel<br />

pcmj = pcmj-2*dot(pcmj,rrel)*rrel<br />

# transformacja do ukladu LAB<br />

p[i] = pcmi+ptot*mi/mtot<br />

p[j] = pcmj+ptot*mj/mtot<br />

# kontynuacja ruchu w czasie deltat z nowymi prędkościami<br />

pos[i] = pos[i]+(p[i]/mi)*deltat<br />

85


pos[j] = pos[j]+(p[j]/mj)*deltat<br />

To jeszcze jednak nie wszystko. Druga część funkcji zderzenie() jest odpowiedzialna za<br />

detekcję i obsługę kolizji atomów ze ściankami naczynia. Najprościej rzecz ujmując jej<br />

działanie opiera się na prawie odbicia, lub jeżeli sprawę opisujemy w sposób wektorowy, na<br />

zmianie znaku odpowiedniej składowej pędu atomu w przypadku przekroczenia dozwolonej<br />

pozycji jaką stanowią ścianki pojemnika. Jest to realizowane w sposób następujący. Tak jak<br />

w poprzednich przypadkach dotyczących detekcji zderzenia tu również mamy dwa warunki<br />

sprawdzające pozycję atomów. Pierwszy z nich sprawdza czy pozycje poszczególnych<br />

atomów nie są mniejsze niż promień atomu. Wynika stąd specyficzne usytuowanie<br />

zbiornika, wymagające aby jeden z wierzchołków zbiornika znajdował się w początku<br />

układu współrzędnych. Jeżeli się tak zdarzy, że składowa położenia któregokolwiek<br />

z atomów będzie będzie mniejsza niż promień atomu to w tablicy outside pojawi (lub<br />

pojawią się) się niezerowa wartość. Tą wartością będzie jeden, ale niesie ona ze sobą jeszcze<br />

jedną istotną informację a mianowicie wskazuje konkretnie, która składowa ma ulec zmianie<br />

na przeciwną, gdyż wiersze tej tablicy oznaczają atomy, natomiast kolumny oznaczają<br />

składowe odpowiednio x, y i z. Zatem zmienna p1 będzie zawierała składową (lub<br />

składowe) pędu, której należy zmienić znak na przeciwny. Drugi warunek sprawdza czy<br />

czy pozycje poszczególnych atomów nie są większe niż zmienna maxpos i działa zupełnie<br />

analogicznie jak warunek pierwszy. Ostatnim zadaniem funkcji zderzenie() jest aktualizacja<br />

położeń wszystkich atomów.<br />

#część odpowiedzialna za obsługę<br />

#zderzeń atomów ze ściankami naczynia<br />

outside = less_equal(pos,R_atom)<br />

p1 = p*outside<br />

p = p-p1+abs(p1) # (odwrócenie składowej pędu do środka zbiornika)<br />

outside = greater_equal(pos,maxpos)<br />

p1 = p*outside<br />

p = p-p1-abs(p1) # (odwrócenie składowej pędu do środka zbiornika)<br />

# Uaktualnienie pozycji wyswietlanych obiektow<br />

for i in range(Nb_of_atoms):<br />

Atoms[i].pos = pos[i]<br />

return<br />

86


Rysunek 54: Rozkłady prędkości Maxwell'a dla 100 atomów helu w trzech różnych temperaturach.<br />

87


W przypadku krzywej teoretycznej liczba atomów o prędkościach leżących w określonym<br />

przedziale jest równa polu powierzchni pod odpowiednim odcinkiem krzywej.<br />

m<br />

v- prędkość w<br />

s<br />

liczbacząsteczek<br />

dN – liczba atomów zawartych w jednostkowych przedziałach prędkości w m<br />

s<br />

Możemy również uzyskać informacje o rozkładzie prędkości w danym kroku czasowym.<br />

Wystarczy tylko zmodyfikować sposób rysowania wykresu usuwając zmienne<br />

odpowiedzialne za sumowanie i uśrednianie danych. Dane uzyskane w ten sposób są<br />

widoczne na ilustracji poniżej. Ilość atomów mieszcząca się w danym przedziale prędkości<br />

jest wówczas liczbą całkowitą. (plik 03_gas_adaptacja10_verb.py)<br />

observation = ghistogram(bins=arange(0.,max_speed,deltav),<br />

#accumulate=1, average=1,<br />

color=Kolor_atomu)<br />

Rysunek 55: Rozkład prędkości atomów w jednym z kolejnych kroków czasowych.<br />

88


9. Zakończenie<br />

Eksperyment komputerowy nie jest procesem jednorazowym. Wymaga on zazwyczaj wielu<br />

prób i testów na etapie budowy zarówno modelu fizycznego jak i algorytmu numerycznego.<br />

Zazwyczaj pierwsze poprawne wyniki ukazują niedoskonałości opisu modelu matematycznofizycznego<br />

i modeli przy pomocy których ilustrujemy dane <strong>zjawisk</strong>o. Rozbudowywanie<br />

i poprawianie modelu należy przeprowadzać aż do uzyskania zadowalających wyników. W takich<br />

przypadkach ważne jest, aby kody programów były przystosowane na wprowadzanie zmian. Szybki<br />

postęp wiedzy powoduje, że zmiany takie są praktycznie nieuchronne, a doskonalenie modelu<br />

i kodu staje się procesem ciągłym.<br />

Również fizyka komputerowa nie daje także jednoznacznych odpowiedzi które algorytmy<br />

będą najlepsze do rozwiązania danego problemu. Zazwyczaj o wyborze algorytmu decyduje<br />

doświadczenie i intuicja badacza, który musi niejednokrotnie porównywać informacje<br />

zamieszczone w pracach badawczych z wynikami symulacji, gdyż całościowe rozwiązanie danego<br />

problemu zależy od wielu kwestii <strong>fizycznych</strong>, matematycznych i programowych. Należy także<br />

podkreślić, że istnieje jeszcze wiele technik, mniej lub bardziej wydajnych, przy pomocy których<br />

można przedstawić opisane <strong>zjawisk</strong>a.<br />

Zdaję sobie sprawę, że przedstawione <strong>zjawisk</strong>a i metody nie wyczerpują w żadnym<br />

wypadku tematu. Sama praca stanowi raczej wprowadzenie do tego typu zagadnień i mam nadzieję,<br />

że przedstawione rezultaty zachęcą Czytelnika do samodzielnego studiowania podobnych<br />

zagadnień <strong>fizycznych</strong>.<br />

89


10. Bibliografia<br />

1. Chris Fehily, Po prostu Python, Wydawnictwo Helion, Gliwice 2002<br />

2. D. Ascher, P. F. Dubois, K. Hinsen, J. Hugunin, T. Oliphant , Numerical Python , Lawrence<br />

Livermore National Laboratory, Livermore 2001 (http://www.scipy.org/NumPy_Tutorial)<br />

3. D. M. Bourg, Fizyka dla programistów gier, Wydawnictwo Helion, Gliwice 2003<br />

4. Guido van Rossum, Fred L. Drake, Podręcznik programisty Pythona,<br />

(http://docs.python.org/ref/ref.html)<br />

5. Pod red. K. Jacha, Komputerowe modelowanie dynamicznych oddziaływań ciał metodą<br />

punktów swobodnych, Wydawnictwo PWN, Warszawa 2001<br />

6. K. Stefański, Wstęp do <strong>mechaniki</strong> klasycznej, Wydawnictwo PWN, Warszawa 1999<br />

7. R. P. Feynman, R. B. Leighton, M. Sands, Feynmana wykłady z fizyki, tom 1.1 oraz 1.2,<br />

Wydawnictwo PWN, Warszawa 2004<br />

8. R. Resnic, D. Halliday, Fizyka, tom 1, Wydawnictwo PWN, Warszawa 2001<br />

9. W. Rubinowicz, W. Królikowski, Mechanika teoretyczna, Wydawnictwo PWN, Warszawa<br />

1980<br />

10. E.Kalinowska, K.Kalinowski, Metody numeryczne, Wydawnictwo Pracowni Komputerowej<br />

Jacka Skalmierskiego, Bielsko-Biała 2003<br />

11. J. Krupka, Z. Morawski, J. Opalski, Wstęp do metod numerycznych dla studentów<br />

elektroniki i technik informacyjnych, Oficyna Wydawnicza Politechniki Warszawskiej<br />

,Warszawa 1999<br />

12. J. R. Taylor , Wstęp do analizy błędu pomiarowego, Wydawnictwo PWN, Warszawa 1999<br />

Witryny internetowe:<br />

A) Harvard University - Natural Science, Lecture Demonstrations - Center of Percussion,<br />

http://www.fas.harvard.edu/~scdiroff/lds/NewtonianMechanics/CenterofPercussion/CenterofPercussion.html<br />

B) Sally Lloyd i Stephen Roberts, Using VPython to Simulate a Ball Bouncing in a Box,<br />

http://www.maths.anu.edu.au/comptlsci/Tutorial-Gas/tute-bounce.html<br />

C) Sally Lloyd i Stephen Roberts, Using VPython to Simulate a Gas,<br />

http://www.maths.anu.edu.au/comptlsci/Tutorial-Gas/tute-gas.html<br />

D) L. Urbano and J. Houghton, An Interactive Computer Model for Coriolis Demonstrations,<br />

Journal of Geoscience Education 2006,<br />

http://lurbano-5.memphis.edu/GeoMod/index.php/Main_Page<br />

E) M. Matyka, <strong>Symulacje</strong> komputrowe w fizyce,<br />

http://panoramix.ift.uni.wroc.pl/~maq/pl/<br />

F) R. Salgado, VPython applications for Teaching Physics,<br />

http://physics.syr.edu/~salgado/software/vpython/<br />

G) R. Chabay, D. Scherer, and B. Sherwood The Visual Module of VPython,<br />

http://vpython.org/webdoc/visual/index.html<br />

90

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!