Symulacje komputerowe zjawisk fizycznych z zakresu mechaniki
Symulacje komputerowe zjawisk fizycznych z zakresu mechaniki
Symulacje komputerowe zjawisk fizycznych z zakresu mechaniki
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