Lage en tilfeldighetsgenerator
Introduksjon
I denne oppgaven skal dere lage en pseudotilfeldig tallgenerator, LCG-klassen i lcg.py, og undersøke hvor tilfeldig tallene den lager faktisk er. Plottefunksjonene er gitt i filen plott.py.
Kort om pseudotilfeldige tall
En datamaskin er deterministisk: gitt samme input gir den alltid samme output. Den kan derfor ikke lage ekte tilfeldige tall (de må hentes fra fysiske prosesser, for eksempel støy i maskinvare). I stedet bruker vi en pseudotilfeldig tallgenerator (PRNG): en klasse som starter med en intern tilstand, kalt frø, og regner ut en ny tilstand hver gang vi ber om et tall. Tallene ser tilfeldige ut, men hele rekken er bestemt av frøet. Samme frø gir samme tallfølge, noe som er nyttig for å teste og gjenskape resultater.
Gode generatorer skal ha noen egenskaper:
- Tallene skal være jevnt fordelt over et definert intervall.
- Et tall skal ikke kunne forutsies fra de forrige uten kjennskap til tilstanden (ingen synlige mønstre).
- Perioden, altså hvor mange tall som kommer før følgen starter på nytt, skal være lang.
En lineær kongruensgenerator (LCG) er en av de enkleste generatorene. Den bruker formelen
x[n+1] = (a * x[n] + c) mod m
der m er modulus, a multiplikator, c inkrement og x[0] er frøet. Alle tallene ligger i intervallet 0 til m - 1, og deler vi på m får vi et desimaltall mellom 0 og 1. Valget av m, a og c avgjør hvor god generatoren blir. LCG-er har blitt brukt i mange programmeringsspråk og operativsystemer, og noen av dem hadde kjente svakheter. En av de mest beryktede er RANDU (m = 2^31, a = 65539, c = 0), som dere skal undersøke.
Klassegrensesnitt
| Metode | Parametere | Resultat |
|---|---|---|
__init__ | mod, a, c, froe | Lagrer parameterne og frøet |
rand | Ingen | Oppdaterer frøet med formelen over og returnerer det nye heltallet |
random | Ingen | Returnerer et flyttall i intervallet [0, 1) |
randint | gulv, tak | Returnerer et heltall i intervallet [gulv, tak] |
distrubusjon | muligheter, dist | Returnerer ett av elementene i muligheter, trukket med sannsynlighetene i dist |
from lcg import LCG
generator = LCG(1 << 31, 65539, 0, 1444) # 1 << 31 er bitskifting, altså at vi flytter 1 31 plasser til venstre. Dette er det samme som 2^31 (2 ** 31 i Python)
print(generator.rand())
print(generator.random())
print(generator.randint(1, 6))
print(generator.distrubusjon(["mynt", "krone"], [0.5, 0.5]))
Del 1: Selve generatoren
- Implementer
__init__ograndslik at hvert kall bruker formelen over og lagrer resultatet som nytt frø. - Implementer
randomved å dele resultatet frarandpå modulus. - Lag to generatorer med samme frø og kontroller at de gir samme tallfølge. Lag så en med et annet frø og se hva som skjer.
- Kall
random100 ganger og skriv ut tallene. Ser de tilfeldige ut?
Del 2: Heltall og sannsynligheter
- Implementer
randint(gulv, tak). Resultatet skal være et heltall i[gulv, tak], og begge endepunktene skal kunne forekomme. - Kast en terning 60000 ganger med
randint(1, 6)og tell hvor mange ganger hvert tall forekommer. Ble det som forventet? - Implementer
distrubusjon(muligheter, dist). Listadistskal summere til 1 og ha like mange elementer sommuligheter. Metoden skal returnere et element fra muligiheter utfra distrubusjonen i dist. Et eksempel kan for eksempel være mulighetene ["bil","geit"] med distrubusjonen [0.33, 0.67].
Del 3: Undersøk kvaliteten til generatoren
Bruk funksjonene i plott.py til å se på tallene.
- Kjør
plott_histogram_og_par(generator). Venstre figur viser et histogram av tallene, og høyre figur viser hvert tall mot det neste. Ser det tilfeldig ut? - Kjør
plott_tripler(generator). Figuren viser de laveste bitene i de første tallene, påfølgende tripler(x[n], x[n+1], x[n+2])i 3D, og tripler sett fra en bestemt vinkel. Hva ser dere? - Prøv å endre
a,cogmod. Hva skjer med plottene hvismod = 2^16, ellera = 5?
Tips
- Bruk
%for modulo. - Heltallsdivisjon
//ungrå avrundingsdeil. Nyttig i randint() - Samme frø gir samme tall hver gang. Dette er en egenskap, ikke en feil.
- Plottene bruker
numpyogmatplotlib. Installer dem medpip install numpy matplotlibhvis dere ikke har dem. - Dere kan rotere på 3D-plottet som blir tegnet for å se på det fra forskjellige vinkler.
Utvidelser
- Lag en metode som finner perioden til generatoren ved å telle til frøet kommer tilbake. Prøv med små verdier for
mod. - Sammenlign med
random-modulen i Python. Hvordan ser plottene ut for den? - Prøv parameterne
mod = 2^32,a = 1664525,c = 1013904223. Ser dere noen forskjeller i plotene? - Bruk bare de høyeste bitene i resultatet i stedet for hele tallet.
hoyestebit = random.rand() >> 16 # Kaster de 16 laveste bitene
- Skriv en prosedyre som beregner pi ved å trekke tilfeldige punkter i et kvadrat og telle hvor mange som havner i en sirkel. Hvor nøyaktig ble svaret? Hint: En sirkel med radius 1 med senter i origo (0, 0) har formelen x^2 + y^2 = 1. Trekk ett tall for x, og ett tall for y.
- Lag en
shuffle-metode som stokker en liste (Fisher–Yates) med generatoren.