<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Random Processes in Computational Physics
The contents of this Jupyter Notebook lecture notes are
Step1:  Random Processes in Physics
Examples of physical processes that are/can be modelled as random include
Step2:  Some basic functions to point out (we'll get to others in a bit)
Step3:  Notice you have to use 1-11 for the range. Why?
Step4:  In Class Exercise - Rolling Dice
Write a programme that generates and prints out two random numbers between 1 and 6. This simulates the rolling of two dice.
Now modify the programme to simulate making 2 million rolls of two dice. What fraction of the time do you get double six? 
Extension
Step5:  You might want to do this for
<ASSISTANT_TASK:>
Python Code:
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
Explanation: Random Processes in Computational Physics
The contents of this Jupyter Notebook lecture notes are: 
Introduction to Random Numbers in Physics
Random Number Generation
Python Packages for Random Numbers
Coding for Probability (atomic decay example)
Non-uniform random numbers
As usual I recommend you follow along by typing the code snippets into your own file. Don't forget to call the packages etc. at the start of each code file.
End of explanation
#Review the documentation for NumPy's random module:
np.random?
Explanation: Random Processes in Physics
Examples of physical processes that are/can be modelled as random include: 
Radioactive decay - we know the probability of decay per unit time from quantum physics, but the exact time of the decay is random. 
Brownian motion - if we could track the motion of all atomic particles, this would not actually be random, but appears random as we cannot. 
     Youtube Video of Brownian Motion: https://www.youtube.com/watch?v=cDcprgWiQEY
Chaotic systems - again not truely random in the sense of radioactive decay, but can be modelled as random. 
Human or animal behaviour can also be modelled as random in some circumstances.
Random Number Generation
There are many different ways to generate uniform random numbers over a specified range (such as 0-1).  Physically, we can for example: 
spin a roulette wheel
draw balls from a lottery
throw darts at a board
thow dice 
However, when we wish to use the numbers in a computer, we need a way to generate the numbers algorithmically.  
Numerically/arithmetically - use a sequential method where each new number is a deterministic function of the previous numbers.  
But: this destroys their true randomness and makes them at best, "pseudo-random".  
However, in most cases, it is sufficient if the numbers “look” uniformly distributed and have no correlation between them. i.e. they pass statistical tests and obey the central limit theorem.
For example consider the function: 
$x' = (ax + c) \mod m$
where $a$, $c$ and $m$ are integer constants, and $x$ is an integer variable. Recall that "$n \mod m$"  means you calculate the remainder when $n$ is divided by $m$.
Now we can use this to generate a sequence of numbers by putting the outcome of this equation ($x'$) back in as the new starting value ($x$). These will act like random numbers. Try it.....
Class Exercise
Starting from $x = 1$ write a short programme which generates 100 values in this sequence and plots them on a graph. Please use the following inputs: 
a = 1664525
c = 1013904223
m = 4294967296
Tip 1: python syntax for "mod m" is: 
%m
So your base code will look like: 
xp = (a*x+c)%m
Extension problem: this won't work for all values of a, c and m. Can you find some which don't generate pseudo-random numbers?  
This is an example of a simple pseudo-random number generator (PRNG). Technically it's a "linear congruential random number generator". Things to note: 
It's not really random
It can only generate numbers betwen 0 and m-1. 
The choices of a, c and m matter. 
The choice of x also matters. Do you get the same values for x=2? 
For many codes this is sufficient, but you can do better. Fortunately python (Numpy) comes with a number of better versions as in built packages, so we can benefit from the expertise of others in our computational physics codes. 
Good Pseudo-Random Number Generators
All pseudo-random number generators (PRNG) should possess a few key properties.  Namely, they should 
be fast and not memory intensive
be able to reproduce a given stream of random numbers (for debugging/verification of computer programs or so we can use identical numbers to compare different systems)
be able to produce several different independent “streams” of random numbers
have a long periodicity, so that they do not wrap around and produce the same numbers again within a reasonably long window.
To obtain a sequence of pseudo-random numbers: 
initilize the state of the generator with a truly random "seed" value
generator uses that seed to create an initial "state", then produces a pseudo-random sequence of numbers from that state.  
But note: 
* The sequence will eventually repeat when the generator's state returns to that initial one.
 The length of the sequence of non-repeating numbers is the period* of the PRNG.  
It is relatively easy to build PRNGs with periods long enough for many practical applications, but one must be cautious in applying PRNG's to problems that require very large quantities of random numbers.
Almost all languages and simulation packages have good built-in generators.  In Python, we can use the NumPy random library, which is based on the Mersenne-Twister algorithm developed in 1997.
Python Random Number Library
End of explanation
#print 5 uniformly distributed numbers between 0 and 1
print(np.random.random(5))
#print another 5 - should be different
print(np.random.random(5))
#print 5 uniformly distributed integers between 1 and 10
print(np.random.randint(1,11,5))
#print another 5 - should be different
print(np.random.randint(1,11,5))
Explanation: Some basic functions to point out (we'll get to others in a bit): 
random() - Uniformly distributed floats over [0, 1]. Will include zero, but not one. If you inclue a number, n in the bracket you get n random floats. 
randint(n,m) - A single random integer from n to m-1
End of explanation
#If you want to save a random number for future use: 
z=np.random.random()
print("The number is ",z)
#Rerun random
print(np.random.random())
print("The number is still",z)
Explanation: Notice you have to use 1-11 for the range. Why?
End of explanation
np.random.seed(42)
for i in range(4):
    print(np.random.random())
np.random.seed(42)
for i in range(4):
    print(np.random.random())
np.random.seed(39)
for i in range(4):
    print(np.random.random())
Explanation: In Class Exercise - Rolling Dice
Write a programme that generates and prints out two random numbers between 1 and 6. This simulates the rolling of two dice.
Now modify the programme to simulate making 2 million rolls of two dice. What fraction of the time do you get double six? 
Extension: Plot a histogram of the frequency of the total of the two dice over the 2 million rolls.
Seeded Random Numbers
Sometimes in computational physics we want to generate the same series of pseudo-random numbers many times. This can be done with 'seeds'.
End of explanation
for i in range(10):
    if np.random.random()<0.2: 
        print("Heads")
    else:
        print("Tails")
Explanation: You might want to do this for: 
Debugging
Code repeatability (i.e. when you hand in code for marking!). 
Coding For Probability
In some circumstances you will want to write code which simulates various events, each of which happen with a probability, $p$. 
This can be coded with random numbers. You generate a random number between zero and 1, and allow the event to occur if that number is greater than $p$. 
For example, consider a biased coin, which returns a head 20% of the time:
End of explanation
<END_TASK>