Wiemy, jakie są założenia metody Monte Carlo. Przypomnijmy, w jaki sposób można wyznaczyć liczbę za jej pomocą.
Podczas przeprowadzania symulacji metodą Monte Carlo korzystamy z nierówności koła o środku w punkcie i promieniu , którą przedstawimy następująco:
Wybieramy punkt o środku w punkcie (0,0) i promieniu 1. Kwadrat opisany na tym kole może być przestrzenią ograniczoną warunkami oraz .
By przypomnieć sobie, w jaki sposób obliczaliśmy omawianą metodą liczbę , wróćmy do metafory rzucania długopisem. Rzucany nim w kartkę, na której narysowano kwadrat opisany na kole.
Jeśli będziesz rzucać długopisem dostatecznie długo, kropki tuszu pokryją cały rysunek. W konsekwencji stosunek liczby kropek wewnątrz koła do liczby wszystkich kropek będzie zbliżony do stosunku pola koła do pola kwadratu.
Znając wzór na pole kwadratu oraz pole koła, możemy wyznaczyć wartość liczby . W e‑materiale Wyznaczanie liczby πpi metodą Monte CarloPar6HTDQWWyznaczanie liczby πpi metodą Monte Carlo wyznaczyliśmy wzór na przybliżenie liczby . Przypomnijmy go:
Wyznaczanie liczby πpi metodą Monte Carlo w języku Java
Aby zaimplementować opisany algorytm, zastanówmy się, w jaki sposób wyznaczyć losową liczbę z przedziału . W języku Java dysponujemy wieloma generatorami liczb pseudolosowychgenerator liczb pseudolosowychgeneratorami liczb pseudolosowych. Użyjemy funkcji random() z biblioteki Math. Funkcja ta zwraca losowe liczby zmiennoprzecinkowe z przedziału z jednakowym prawdopodobieństwem. Nie musimy przejmować się w tym wypadku brakiem możliwości wylosowania liczby 1, ponieważ prawdopodobieństwo tego zdarzenia jest bardzo małe. Algorytm nie straci na dokładności.
Aby wylosować liczbę z przedziału , przeskalujmy przedział. Wylosowaną liczbę należy pomnożyć przez 2, a następnie odjąć od niej 1. Dzięki takim operacjom przedział zostanie rozciągnięty, a następnie przesunięty. Zapiszmy kod w języku Java:
Linia 1. prawy ukośnik prawy ukośnik losowa liczba z przedziału od minus 1 do 1.
Linia 2. double randomDouble znak równości 2 asterysk Math kropka random otwórz nawias okrągły zamknij nawias okrągły minus 1 średnik.
// losowa liczba z przedziału od -1 do 1
double randomDouble = 2 * Math.random() - 1;
Napiszmy funkcję, która wyznaczy liczbę metodą Monte Carlo:
Linia 1. static double wyznaczPi otwórz nawias okrągły int liczbaLosowan zamknij nawias okrągły otwórz nawias klamrowy.
Linia 2. int liczbaTrafien znak równości 0 średnik.
Linia 4. for otwórz nawias okrągły int i znak równości 0 średnik i otwórz nawias ostrokątny liczbaLosowan średnik plus plus i zamknij nawias okrągły otwórz nawias klamrowy.
Linia 5. prawy ukośnik prawy ukośnik losowe liczby z przedziału otwórz nawias ostrokątny minus 1 przecinek 1 zamknij nawias okrągły.
Linia 6. double x znak równości 2 asterysk Math kropka random otwórz nawias okrągły zamknij nawias okrągły minus 1 średnik.
Linia 7. double y znak równości 2 asterysk Math kropka random otwórz nawias okrągły zamknij nawias okrągły minus 1 średnik.
Linia 9. if otwórz nawias okrągły x asterysk x plus y asterysk y otwórz nawias ostrokątny znak równości 1 zamknij nawias okrągły otwórz nawias klamrowy.
Linia 10. plus plus liczbaTrafien średnik.
Linia 11. zamknij nawias klamrowy.
Linia 12. zamknij nawias klamrowy.
Linia 14. double pi znak równości otwórz nawias okrągły double zamknij nawias okrągły 4 asterysk liczbaTrafien prawy ukośnik liczbaLosowan średnik.
Linia 16. return pi średnik.
Linia 17. zamknij nawias klamrowy.
static double wyznaczPi(int liczbaLosowan) {
int liczbaTrafien = 0;
for (int i = 0; i < liczbaLosowan; ++i) {
// losowe liczby z przedziału <-1, 1)
double x = 2 * Math.random() - 1;
double y = 2 * Math.random() - 1;
if (x * x + y * y <= 1) {
++liczbaTrafien;
}
}
double pi = (double) 4 * liczbaTrafien / liczbaLosowan;
return pi;
}
Aby przetestować napisany algorytm, utwórzmy główną klasę oraz funkcję main():
Linia 1. public class Main otwórz nawias klamrowy.
Linia 2. static double wyznaczPi otwórz nawias okrągły int liczbaLosowan zamknij nawias okrągły otwórz nawias klamrowy.
Linia 3. int liczbaTrafien znak równości 0 średnik.
Linia 5. for otwórz nawias okrągły int i znak równości 0 średnik i otwórz nawias ostrokątny liczbaLosowan średnik plus plus i zamknij nawias okrągły otwórz nawias klamrowy.
Linia 6. prawy ukośnik prawy ukośnik losowe liczby z przedziału otwórz nawias ostrokątny minus 1 przecinek 1 zamknij nawias okrągły.
Linia 7. double x znak równości 2 asterysk Math kropka random otwórz nawias okrągły zamknij nawias okrągły minus 1 średnik.
Linia 8. double y znak równości 2 asterysk Math kropka random otwórz nawias okrągły zamknij nawias okrągły minus 1 średnik.
Linia 10. if otwórz nawias okrągły x asterysk x plus y asterysk y otwórz nawias ostrokątny znak równości 1 zamknij nawias okrągły otwórz nawias klamrowy.
Linia 11. plus plus liczbaTrafien średnik.
Linia 12. zamknij nawias klamrowy.
Linia 13. zamknij nawias klamrowy.
Linia 15. double pi znak równości otwórz nawias okrągły double zamknij nawias okrągły 4 asterysk liczbaTrafien prawy ukośnik liczbaLosowan średnik.
Linia 17. return pi średnik.
Linia 18. zamknij nawias klamrowy.
Linia 20. public static void main otwórz nawias okrągły String otwórz nawias kwadratowy zamknij nawias kwadratowy args zamknij nawias okrągły otwórz nawias klamrowy.
Linia 21. System kropka out kropka println otwórz nawias okrągły wyznaczPi otwórz nawias okrągły 10000 zamknij nawias okrągły zamknij nawias okrągły średnik.
Linia 22. zamknij nawias klamrowy.
Linia 23. zamknij nawias klamrowy.
public class Main {
static double wyznaczPi(int liczbaLosowan) {
int liczbaTrafien = 0;
for (int i = 0; i < liczbaLosowan; ++i) {
// losowe liczby z przedziału <-1, 1)
double x = 2 * Math.random() - 1;
double y = 2 * Math.random() - 1;
if (x * x + y * y <= 1) {
++liczbaTrafien;
}
}
double pi = (double) 4 * liczbaTrafien / liczbaLosowan;
return pi;
}
public static void main(String[] args) {
System.out.println(wyznaczPi(10000));
}
}
Dla liczby losowań 10 000 możemy spodziewać się wyników zbliżonych do liczby , jednak należy pamiętać, że nie zawsze będą one dokładne. Im więcej losowych punktów, tym dokładniejszy wynik. Przetestuj działanie programu, podając różne liczby losowań. Przeanalizuj, jak zmienia się wynik. Przypomnijmy, że .
Dokładność wyniku
Przetestujmy działanie programu dla różnych parametrów. Przeprowadźmy próby uruchomienia funkcji wyznaczPi() z parametrami 10, 100, 1000, 10 000, 100 000 oraz 1 000 000.
Aby utworzyć wykres dokładności, możemy obliczyć wartość bezwzględną różnicy pomiędzy wynikiem teoretycznym, a otrzymanym dzięki metodzie Monte Carlo. W tym celu wykorzystamy stałą PI z biblioteki Math oraz funkcję abs()funkcja abs()abs(), również z biblioteki Math:
Linia 1. public class Main otwórz nawias klamrowy.
Linia 2. static double wyznaczPi otwórz nawias okrągły int liczbaLosowan zamknij nawias okrągły otwórz nawias klamrowy.
Linia 3. int liczbaTrafien znak równości 0 średnik.
Linia 5. for otwórz nawias okrągły int i znak równości 0 średnik i otwórz nawias ostrokątny liczbaLosowan średnik plus plus i zamknij nawias okrągły otwórz nawias klamrowy.
Linia 6. prawy ukośnik prawy ukośnik losowe liczby z przedziału otwórz nawias ostrokątny minus 1 przecinek 1 zamknij nawias okrągły.
Linia 7. double x znak równości 2 asterysk Math kropka random otwórz nawias okrągły zamknij nawias okrągły minus 1 średnik.
Linia 8. double y znak równości 2 asterysk Math kropka random otwórz nawias okrągły zamknij nawias okrągły minus 1 średnik.
Linia 10. if otwórz nawias okrągły x asterysk x plus y asterysk y otwórz nawias ostrokątny znak równości 1 zamknij nawias okrągły otwórz nawias klamrowy.
Linia 11. plus plus liczbaTrafien średnik.
Linia 12. zamknij nawias klamrowy.
Linia 13. zamknij nawias klamrowy.
Linia 15. double pi znak równości otwórz nawias okrągły double zamknij nawias okrągły 4 asterysk liczbaTrafien prawy ukośnik liczbaLosowan średnik.
Linia 17. return pi średnik.
Linia 18. zamknij nawias klamrowy.
Linia 20. public static void main otwórz nawias okrągły String otwórz nawias kwadratowy zamknij nawias kwadratowy args zamknij nawias okrągły otwórz nawias klamrowy.
Linia 21. System kropka out kropka println otwórz nawias okrągły Math kropka abs otwórz nawias okrągły Math kropka PI minus wyznaczPi otwórz nawias okrągły 10 zamknij nawias okrągły zamknij nawias okrągły zamknij nawias okrągły średnik.
Linia 22. System kropka out kropka println otwórz nawias okrągły Math kropka abs otwórz nawias okrągły Math kropka PI minus wyznaczPi otwórz nawias okrągły 100 zamknij nawias okrągły zamknij nawias okrągły zamknij nawias okrągły średnik.
Linia 23. System kropka out kropka println otwórz nawias okrągły Math kropka abs otwórz nawias okrągły Math kropka PI minus wyznaczPi otwórz nawias okrągły 1000 zamknij nawias okrągły zamknij nawias okrągły zamknij nawias okrągły średnik.
Linia 24. System kropka out kropka println otwórz nawias okrągły Math kropka abs otwórz nawias okrągły Math kropka PI minus wyznaczPi otwórz nawias okrągły 10000 zamknij nawias okrągły zamknij nawias okrągły zamknij nawias okrągły średnik.
Linia 25. System kropka out kropka println otwórz nawias okrągły Math kropka abs otwórz nawias okrągły Math kropka PI minus wyznaczPi otwórz nawias okrągły 100000 zamknij nawias okrągły zamknij nawias okrągły zamknij nawias okrągły średnik.
Linia 26. System kropka out kropka println otwórz nawias okrągły Math kropka abs otwórz nawias okrągły Math kropka PI minus wyznaczPi otwórz nawias okrągły 1000000 zamknij nawias okrągły zamknij nawias okrągły zamknij nawias okrągły średnik.
Linia 27. zamknij nawias klamrowy.
Linia 28. zamknij nawias klamrowy.
public class Main {
static double wyznaczPi(int liczbaLosowan) {
int liczbaTrafien = 0;
for (int i = 0; i < liczbaLosowan; ++i) {
// losowe liczby z przedziału <-1, 1)
double x = 2 * Math.random() - 1;
double y = 2 * Math.random() - 1;
if (x * x + y * y <= 1) {
++liczbaTrafien;
}
}
double pi = (double) 4 * liczbaTrafien / liczbaLosowan;
return pi;
}
public static void main(String[] args) {
System.out.println(Math.abs(Math.PI - wyznaczPi(10)));
System.out.println(Math.abs(Math.PI - wyznaczPi(100)));
System.out.println(Math.abs(Math.PI - wyznaczPi(1000)));
System.out.println(Math.abs(Math.PI - wyznaczPi(10000)));
System.out.println(Math.abs(Math.PI - wyznaczPi(100000)));
System.out.println(Math.abs(Math.PI - wyznaczPi(1000000)));
}
}
Oto przykładowe wyniki, jakie otrzymamy po uruchomieniu algorytmu:
Linia 1. 0 kropka 8584073464102069.
Linia 2. 0 kropka 061592653589793045.
Linia 3. 0 kropka 05759265358979304.
Linia 4. 0 kropka 011207346410206931.
Linia 5. 0 kropka 005127346410207068.
Linia 6. 2 kropka 006535897929318E minus 4.
Analizując otrzymane dane, można zauważyć, że dokładność zwiększa się wraz ze wzrostem liczby losowań. Wykres danych w skali logarytmicznej prezentuje się następująco:
R1EYlKMsr1PKD
Ilustracja przedstawia wykres. Oś pozioma opisana jest jako: ilość losowań, a oś pionowa jako: dokładność. Linia na wykresie ma trend malejący.
Źródło: Contentplus.pl sp. z o.o., licencja: CC BY-SA 3.0.
Słownik
funkcja abs()
funkcja abs()
służy do obliczenia wartości bezwględnej wyrażenia
generator liczb pseudolosowych
generator liczb pseudolosowych
podprogram zwracający kolejne liczby z deterministycznego ciągu liczb, który ma podobne własności do ciągów losowych