Symulacje i modele
W tym artykule napiszemy symulację zachowania wielu oddziaływujących na siebie obiektów. Zostanie zdefiniowana klasa
opisująca zachowanie i właściwości tych obiektów. Pokażemy jak nimi sterować przy pomocy stopera i sygnałów oraz jak
przekazać wartości zmiennych pomiędzy obiektami.
Jest to średnio zaawansowany artykuł. Przed przeczytaniem warto zajrzeć do podręcznika, do
rozdziału o klasach w POOL.
1. Pierwszy model.
Na początek napiszemy bardzo prostą klasę, którą rozwiniemy w następnych rozdziałach.
Klasa w POOL jest zdefiniowana identycznie jak funkcja. W rzeczy samej - nowy obiekt (żółw) danej klasy wykonuje
opisującą go funkcję podczas utworzenia. Wszystkie funkcje i zmienne utworzone w tym czasie stają się składowymi obiektu.
Także parametry funkcji tworzącej zostają zachowane na czas życia obiektu jako zmienne lokalne.
W pierwszej wersji programu klasa model
pozwala ustawić proste właściwości żółwia:
początkową pozycję i zwrot oraz prędkość, z jaką będzie się poruszał. Wartości te można przekazać jako argumenty
funkcji tworzącej żółwia - nazwijmy ją konstruktorem. Konstruktor ustawia także kolor pisaka i promień
żółwia (używany teraz przy odbiciu od ściany). Na koniec konstruktor wypisuje w oknie tekstowym informacje o
parametrach nowego żółwia.
Klasa model
zawiera jedną funkcję składową: krok dt
.
Funkcja ta pozwala przesunąć żółwia do przodu o odległość wynikającą z jego prędkości (podanej jako parametr
konstruktora) i przedziału czasu dt
podanego jako argument funkcji.
Program głównego żółwia (#first
) tworzy dwa żółwie klasy model
.
Opis użytej do tego instrukcji newturtle
znajdziesz w podręczniku.
Aby poruszać żółwiami, pętla repeat
wywołuje funkcję krok
dla
każdego z nich. Funkcja, którą mają wykonać obiekty wywoływana jest w POOL przy pomocy operatora @
.
Opóźnienie w pętli pozwala zachować stałe tempo wykonania programu, odpowiednie do ustawień prędkości żółwi.
to model :p :h [:v 60]
setpos :p
setheading :h
setpencolor random 1000
pendown
showturtle
setradius 10
to krok :dt
right (runif -20 20)
forward 0.001 * :dt * :v
end
(print who "|p =| :p "|h =| :h "|v =| :v)
end
bounce
hideturtle penup
"m1 := (newturtle $model {-50 0} 30 15)
"m2 := (newturtle $model {50 0} (-50))
"dt := 100
repeat 500 [
sync :dt
(krok :dt) @ :m1
(krok :dt) @ :m2
]
2. Więcej samodzielności.
W poprzednim rozdziale żółw #first
sterował żółwiami :m1
i
:m2
wydając im polecenia w pętli. Lepszym sposobem regularnego wywoływania instrukcji jest
użycie stopera. Stoper nie wymaga programowania opóźnień (sync
lub wait
)
i nie blokuje dostępu do żółwia #first
z linii poleceń. W kolejnej wersji programu skorzystamy
ze stopera.
W podręczniku znajdziesz opis użycia stopera. Teraz skorzystamy z wariantu, w którym
stoper regularnie wysyła sygnał do wszystkich żółwi. W klasie opisującej nasz model możemy odebrać taki sygnał przy
pomocy odpowiedniej funkcji obsługi - zastąpi ona funkcję krok
z poprzedniego rozdziału.
Wartość czasu potrzebną do obliczenia długości kroku żółwia odczytamy z informacji, które zawiera sygnał wysyłany
przez stoper.
Na tym etapie zmieniamy także sposób tworzenia nowych żółwi. Do programu żółwia #first
dodajemy funkcję obsługi zdarzenia - kliknięcia myszą (zobacz opis w podręczniku).
Teraz przy każdym kliknięciu w oknie grafiki pojawi się nowy żółw, który od razu przystępuje do obsługi sygnałów
wysyłanych przez stoper.
Po uruchomieniu programu i utworzeniu kilku żółwi przełącz się do linii poleceń. Możesz wydawać instrukcje - żółw
#first
jest zajęty wykonywaniem funkcji stopera tylko przez ułamek milisekundy. Możesz np.
sprawdzić, ile żółwi zostało już utworzonych: print count children
.
to model :p :h [:v 60]
setpos :p
setheading :h
(setpencolor random 1000 75)
pendown
showturtle
setradius 10
to onsignalkrok :turtle :data
right (runif -20 20)
let "dt :data,2
forward 0.001 * :dt * :v
end
(print who "|p =| :p "|h =| :h "|v =| :v)
end
to onmouseclick :mousepos
let "m (newt $model :mousepos random 360 20 + random 100)
end
bounce ht pu
"t := timer "krok 500
3. Więcej współpracy.
W poprzednich programach żółwie chodziły własnymi ścieżkami, nie reagując na siebie nawzajem. W tym rozdziale
umożliwimy im komunikację. Zamiast wykonywać losowe skręty obliczymy w każdym kroku nowy kierunek żółwia - nieco
w stronę pozostałych żółwi.
Zmianie ulega funkcja obsługi onsignalkrok
- zamiast obrotu right (runif -20 20)
użyjemy instrukcji ustawiającej żółwia zgodnie z podanym wektorem: setheaddir zwrot
;
zwrot
jest nową funkcją składową klasy model
. Zawiera ona kilka
nowych elementów:
all
- instrukcja zwracająca listę wszystkich żółwi istniejących w programie;
this
- instrukcja zwracająca żółwia, który ją wywołuje; w tym przypadku
umożliwia ona uniknięcie obliczania kierunku "do samego siebie": :m <> this
;
pos @ :m
- odczytanie pozycji kolejnych żółwi.
to model :p :h [:v 60]
setpos :p
setheading :h
(setpencolor random 1000 75)
pendown
showturtle
setradius 10
to zwrot
if (count all) > 2 [
let "sx 0 let "sy 0
foreach "m all [
if and (:m <> this) (:m <> #first) [
let "pm pos @ :m
let "dx :pm,1 - xcor
let "dy :pm,2 - ycor
let "norm sqrt :dx^2 + :dy^2
"sx := :sx + :dx / :norm
"sy := :sy + :dy / :norm
]
]
"sx := 0.1 * :sx + 0.9 * headdir,1
"sy := 0.1 * :sy + 0.9 * headdir,2
output array :sx :sy
]
output headdir
end
to onsignalkrok :turtle :data
setheaddir zwrot
fd 0.001 * :data,2 * :v
end
(print who "|p =| :p "|h =| :h "|v =| :v)
end
to onmouseclick :mousepos
let "m (newt $model :mousepos random 360 50 + random 100)
end
bounce ht pu
setpalette "hot
"t := timer "krok 50
Powyższy program wygląda rozsądnie i działa jak tego oczekujemy. Jednak do przygotowania ciekawszej i bardziej
rozbudowanej symulacji potrzebujemy jeszcze większego porządku - podziału na dwa etapy:
obliczenie zmian - przygotowanie nowych wartości kierunku, prędkości, itd.; stan wszystkich obiektów jest
"zamrożony" i obiekty mogą go wzajemnie odczytywać;
wykonanie zmian - stan obiektów zmienia się zgodnie z wartościami obliczonymi w poprzednim etapie; obiekty
nie komunikują się ze sobą.
Dzięki temu podziałowi obliczenia w każdym etapie mogą być wykonywane równolegle, a wyniki są niezależne od
kolejności, w jakiej zostały uruchomione obliczenia. Aby zrealizować ten algorytm trzeba zmodyfikować działanie
stopera, tak by przy każdym "cyknięciu" wysłał dwa sygnały: sygnał "zwrot", którego funkcja obsługi w klasie
model
obliczy nowy kierunek żółwia na podstawie położenia innych żółwi oraz sygnał
"krok", który wykona obrót i krok do przodu każdego żółwia. W nowym programie stopera sygnały są "blokujące"
(zobacz opis w podręczniku), tzn. kolejne instrukcje w programie wysyłającym
sygnał są wstrzymane do czasu zakończenia obsługi wysłanego sygnału przez wszystkie oczekujące go obiekty.
Kolejnym usprawnieniem w nowej wersji programu jest zapamiętanie tworzonych żółwi klasy model
w zmiennej współdzielonej (jedna kopia zmiennej dostępna dla wszystkich obiektów): shared "żółwie
.
Upraszcza to przeszukiwanie listy żółwi podczas obliczenia nowego kierunku.
shared "żółwie
"żółwie := []
to model :p :h [:v 60]
setpos :p
setheading :h
(setpencolor random 1000 75)
pendown
showturtle
setradius 10
let "kierunek headdir
to onsignalzwrot
if (count :żółwie) < 2 [
"kierunek := headdir stop
]
let "sx 0 let "sy 0
foreach "m :żółwie [
if :m <> this [
let "pm pos @ :m
let "dx :pm,1 - xcor
let "dy :pm,2 - ycor
let "norm sqrt :dx^2 + :dy^2
"sx := :sx + :dx / :norm
"sy := :sy + :dy / :norm
]
]
"sx := 0.1 * :sx + 0.9 * headdir,1
"sy := 0.1 * :sy + 0.9 * headdir,2
"kierunek := array :sx :sy
end
to onsignalkrok :turtle :data
setheaddir :kierunek
fd 0.001 * :data * :v
end
(print who "|p =| :p "|h =| :h "|v =| :v)
end
to onmouseclick :mousepos
queue :żółwie (newt $model :mousepos random 360 50 + random 100)
end
bounce ht pu
setpalette "blue2red
"t := timer [
signalw "zwrot
(signalw "krok :dt)
] 50
4. Pełna symulacja.
Mamy już wszystkie elementy potrzebne do zaprogramowania symulacji. Będzie to model kulek-cząstek posiadających
masę nadającą im bezwładność i ładunek, dzięki któremu mogą się przyciągać lub odpychać. Suwakami możesz zmieniać
stałą oddziaływania (suwak "siła") i proporcję mas (suwak "asymetria").
shared "cząstki
"cząstki := []
to model :q :masa [:v 0] [:h 0] [:ft 0.001] [:c 90]
to ustaw_siłę :g
"c := -(:q * :g)
end
to ustaw_masę :m
setr 10 * sqrt :m
"masa := :m
end
"ładunek := :q
ustaw_masę :masa
ustaw_siłę :c
setpos pos @ parent
seth :h
st
let "fx 0
let "fy 0
to onsignalf
"fx := 0 "fy := 0
foreach "m :cząstki [
if :m <> this [
let "p pos @ :m
let "r radius + radius @ :m
let "d distance :p
ifelse :d > :r
[let "fm (:c * :ładunek @ :m) / :d^3]
[let "fm (:c * :ładunek @ :m) / :r^3]
"fx += :fm * (:p,1 - xcor)
"fy += :fm * (:p,2 - ycor)
]
]
end
to onsignalkrok :turtle :data
let "dt 0.01 * :data
if :dt > 0.8 ["dt := 0.8]
let "hdir headdir
:hdir,1 := :v * :hdir,1 + :fx * :dt / :masa
:hdir,2 := :v * :hdir,2 + :fy * :dt / :masa
"v := sqrt :hdir,1^2 + :hdir,2^2
if :v > 30 ["v := 30]
setheaddir :hdir
let "ds :v * :dt
"v -= :ds * :ft / :masa
if :v < 0 ["v := 0]
fd :ds
end
end
bounce ht pu
repeat 30 [
setxy (rnorm -30 30) (rnorm -10 30)
"m := (newt $model 1 0.7)
(setturtleimg "red_light_small) @ :m
queue :cząstki :m
]
repeat 30 [
setxy (rnorm 30 30) (rnorm -10 30)
"m := (newt $model (-1) 0.3)
(setturtleimg "blue_light_small) @ :m
queue :cząstki :m
]
home
"m := (newt $model 3 2 4 45 0)
(setturtleimg "yellow_light) @ :m
queue :cząstki :m
"gs := (slider "siła {10 4} {10 100 5} 90)
setonchange :gs [
foreach "m :cząstki [(ustaw_siłę getvalue :gs) @ :m]
]
"sym := (slider "asymetria {10 60} {-0.9 0.9 0.1} 0.4)
setonchange :sym [
let "s 0.5 * (1 + getvalue :sym)
foreach "m :cząstki [
if :ładunek @ :m = 1 [(ustaw_masę :s) @ :m]
if :ładunek @ :m = -1 [(ustaw_masę 1 - :s) @ :m]
]
]
"t := timer [
signalw "f
(signalw "krok :dt)
] 25
5. Podsumowanie.
Artykuł ilustruje najbardziej podstawowe techniki przygotowania symulacji złożonej z wielu komunikujących się
ze sobą obiektów: opis właściwości i zachowania obiektu przy pomocy klasy, użycie stopera do regularnego wykonania
kroków symulacji i użycie sygnałów do wyzwalania równoległych zadań.
Symulacja, jaką przygotowaliśmy opiera się o bardzo proste zasady - bezwładność i siłę podobną do elektrostatycznej.
Nawet tak prosty opis pozwala zaobserwować ciekawe zachowania grupy obiektów. Program może być podstawą do zbudowania
bardziej realistycznego opisu, np. uzupełnionego o inne oddziaływania lub o inne modele obiektów. Warto też dodać
interaktywne elementy, reagujące na działania użytkownika.