czwartek, 15 czerwca 2017

Histogram liczb pseudolosowych (cz. II)

Mając już program wykonujący losowania i wizualizujący histogram trafień, możemy przetestować różne sposoby uzyskiwania rozkładów innych niż płaski.

W powyższym kodzie mamy jeszcze 8 różnych wariantów, z których możemy skorzystać. Poza bezpośrednim użyciem generatora z języka JAVA (linia 79), mamy kilka przykładów dwu podstawowych modyfikacji:
  1. Mnożenie kilku liczb losowych z zakresu 0..1 (linie 80-82) dające zamknięty do zakresu 0..1 rozkład skośny. Tym bardziej skośny, im więcej liczb pomnożymy.
  2. Uśrednianie kilku liczb losowych z zakresu 0..1 (linie 83-87) dające zamknięte w zakresie 0..1 rozkłady dzwonowe naśladujące na potrzeby symulacji rozkład normalny.
Przykład rozkładu dzwonowego był pokazany w poprzednim rozdziale, a jednego ze skośnych mamy poniżej:
 
No fajnie. Ale dlaczego robimy to w ten sposób? Przecież istnieją transformacje płaskiego rozkładu zmiennej losowej w inne rozkłady, np. transformacja Boxa-Mullera?
I na to kiedyś przyjdzie pora :-) To co tutaj pokazuje to najprostsze sposoby uzyskania rozkładów użytecznych do celów symulacji agentowych i gier, w których sam rozkład jest tylko środkiem do celu, a nie celem samym w sobie ;-)


wtorek, 13 czerwca 2017

Histogram liczb (pseudo-)losowych

W programowaniu gier czy symulacji, ale także w wielu bardziej specjalistycznych zastosowaniach potrzebujemy często sekwencji liczb losowych. Jednak uzyskiwanie prawdziwych liczb losowych wymaga specjalistycznego hardwaru, który nie jest rozpowszechniony, stąd programiści "od niepamiętnych czasów" zastępują je liczbami pseudo-losowymi uzyskiwanymi za pomocą "generatorów [liczb] pseudolosowych".
Każdy szanujący się język programowania posiada w standardowym wyposażeniu przynajmniej jeden najprostszy generator dający liczby "o rozkładzie płaskim", czyli teoretycznie równie prawdopodobne. Taki generator jest zazwyczaj funkcją biblioteczną o nazwie rand(), random() lub zbliżonej. Oczywiście w Processing nie jest ułomny i też takie funkcje o nazwie random() ma. Ta funkcja zwraca liczbę typu float z zadanego zakresu.
Ma też odziedziczoną po macierzystym języku JAVA funkcję biblioteczną Math.random() zwracającą liczbę typu double z zakresu 0..1

Na razie nie będziemy wnikać w szczegóły działania tych funkcji, natomiast zajmiemy się sprawdzeniem co to znaczy że generator daje rozkład płaski.
Czyli zrobimy program który za pomocą histogramu wizualizuje wyniki (pseudo-)losowania z takiego generatora.



Generator z Processingu ukrywamy w funkcji MyRandom() (linie 4-8) po to,  żeby nie modyfikując już potem programu móc testować inne generatory. Zakładamy że funkcja ta zawsze będzie zwracać liczbę typu double z zakresu 0..1. Reszta programu to wizualizacja rozkładu uzyskanego z generatora, która dla każdego generatora spełniającego powyższe wymagania będzie taka sama.
Także dla takiego jak poniżej, który ewidentnie nie daje rozkładu płaskiego.

Zatem musimy zadeklarować tablicę która będzie służyć za koszyki histogramu (nazwaną Basket[]) oraz zmienne reprezentujące liczbę koszyków i zliczające losowania (linie 10-12). Tablicę dla porządku i przypomnienia sposobu użycia zerujemy w funkcji setup() (linie 23-24).

Potrzebujemy też określić częstość wywoływaniu funkcji draw() (linia 15 oraz 22) oraz liczbę losowań wykonywanych w każdym wywołaniu draw(). Te zmienne wpływają tylko na szybkość działania programu, ale nie zmieniają końcowego rezultatu.





Odpowiednio do tych ustawień implementujemy więc funkcję draw() :


Główna pętla (w liniach 30-45) wykonuje odpowiednią liczbę losowań (linia 32). Każda wylosowana liczba jest sprawdzana pod względem spełnienia założeń, czyli znajdowania się w zakresie 0..1. Następnie mnożąc wylosowaną liczbę przez liczbę koszyków ustalamy indeks i koszyka do którego liczba "wpada" (linia 39 i 43). Tu czyha na nas jednak pułapka. Jeżeli trafimy na rzadką sytuację że funkcja MyRandom() zwróci dokładnie 1 to wynik będzie dokładnie równy NumOfBaskets. Podobnie gdy generator nie będzie spełniał założeń i da nam wynik większy od 1. Na takie wypadki mamy w tablicy Basket[] dodatkowy koszyk (zobacz linie 11), do którego wpadają wszystkie takie wyniki losowań. 
  • W normalnych warunkach zabezpieczenie (linie 40-41) nie jest potrzebne, ale kiedyś się jeszcze ucieszymy że je zrobiliśmy :-)
 Na koniec funkcji draw() czyścimy okno i rysujemy histogram (linie 47-48), ale stosując się do paradygmatu proceduralnego to ostatnie zadanie zlecamy kolejnej funkcji: