Some general remarks about the exercises:
Cell > Cell Type > Markdown). But make sure to replace any part that says YOUR CODE HERE or YOUR ANSWER HERE and remove the raise NotImplementedError().Kernel > Restart & Run All in the jupyter menu.Please fill in your name here:
NAME = "Kees van Kempen"
Exercise sheet 1
Code from the lecture:
import numpy as np
import matplotlib.pylab as plt
rng = np.random.default_rng()
%matplotlib inline
def random_in_square():
"""Returns a random position in the square [-1,1)x[-1,1)."""
return rng.uniform(-1,1,2)
def is_in_circle(x):
return np.dot(x,x) < 1
def simulate_number_of_hits(N):
"""Simulates number of hits in case of N trials in the pebble game."""
number_hits = 0
for i in range(N):
position = random_in_square()
if is_in_circle(position):
number_hits += 1
return number_hits
def random_in_disk():
"""Returns a uniform point in the unit disk via rejection."""
position = random_in_square()
while not is_in_circle(position):
position = random_in_square()
return position
def is_in_square(x):
"""Returns True if x is in the square (-1,1)^2."""
return np.abs(x[0]) < 1 and np.abs(x[1]) < 1
def sample_next_position_naively(position,delta):
"""Keep trying a throw until it ends up in the square."""
while True:
next_position = position + delta*random_in_disk()
if is_in_square(next_position):
return next_position
def naive_markov_pebble(start,delta,N):
"""Simulates the number of hits in the naive Markov-chain version
of the pebble game."""
number_hits = 0
position = start
for i in range(N):
position = sample_next_position_naively(position,delta)
if is_in_circle(position):
number_hits += 1
return number_hits
def naive_markov_pebble_generator(start,delta,N):
"""Same as naive_markov_pebble but only yields the positions."""
position = start
for i in range(N):
position = sample_next_position_naively(position,delta)
yield position
def sample_next_position(position,delta):
"""Attempt a throw and reject when outside the square."""
next_position = position + delta*random_in_disk()
if is_in_square(next_position):
return next_position # accept!
else:
return position # reject!
def markov_pebble(start,delta,N):
"""Simulates the number of hits in the proper Markov-chain version of the pebble game."""
number_hits = 0
position = start
for i in range(N):
position = sample_next_position(position,delta)
if is_in_circle(position):
number_hits += 1
return number_hits
def markov_pebble_generator(start,delta,N):
"""Same as markov_pebble but only yields the positions."""
position = start
for i in range(N):
position = sample_next_position(position,delta)
yield position
def pi_stddev(n,m):
"""Estimate the standard deviation in the pi estimate in the case of n trials,
based on m runs of the direct-sampling pebble game."""
return np.std([simulate_number_of_hits(n)/n*4 for i in range(m)])
stddev_data = np.array([[2**k,pi_stddev(2**k,200)] for k in range(4,12)])
stddev_data
cell-90b5022604409d60
Score: 12.0 / 12.0 (Top)
# If done correctly, your code should pass the following tests
from nose.tools import assert_almost_equal
assert_almost_equal(pi_stddev(2**3,1000),0.582,delta=0.03)
assert_almost_equal(pi_stddev(2**4,1000),0.411,delta=0.03)
### BEGIN HIDDEN TESTS
assert_almost_equal(pi_stddev(2**5,1000),0.291,delta=0.03)
assert_almost_equal(pi_stddev(2**6,1000),0.207,delta=0.03)
### END HIDDEN TESTS
(b) Write a function fit_power_law that takes an array of pairs and determines best-fit parameters for the curve . This is best done by fitting a straight line to the data on a log-log scale (). Hint: use curve_fit from SciPy. (12 pts)
import scipy.optimize
def fit_power_law(stddev_data):
"""Compute the best fit parameters a and p."""
# Define the to be fitted function. We use the log, due to the calculation of the uncertainties.
log_f = lambda n, a, p: np.log(a) + p*np.log(n)
n, 𝜎 = stddev_data.T
a_fit, p_fit = scipy.optimize.curve_fit(log_f, n, np.log(𝜎))[0]
return a_fit, p_fit
cell-311260afe5628598
Score: 12.0 / 12.0 (Top)
from nose.tools import assert_almost_equal
assert_almost_equal(fit_power_law(np.array([[16.0,1.4],[32.0,1.1],[64.0,0.9]]))[0],3.36,delta=0.05)
assert_almost_equal(fit_power_law(np.array([[16.0,1.4],[32.0,1.1],[64.0,0.9]]))[1],-0.31,delta=0.03)
### BEGIN HIDDEN TESTS
assert_almost_equal(fit_power_law(stddev_data)[0],1.82,delta=0.3)
assert_almost_equal(fit_power_law(stddev_data)[1],-0.5,delta=0.08)
### END HIDDEN TESTS
(c) Plot the data against the fitted curve in a log-log plot. Don't forget to properly label your axes. Hint: use loglog from matplotlib. (12 pts)
a_fit, p_fit = fit_power_law(stddev_data)
plt.figure()
plt.title("Convergence of standard deviation of direct-sampled $\pi$ guesses over $n$ throws among $m = 200$ trials")
plt.loglog(*stddev_data.T, label="data")
n_range = np.logspace(1, 4, 10)
plt.loglog(n_range, a_fit*n_range**p_fit,
label="fitted $\sigma = an^p$\n($a = {:.3f}, p = {:.3f})$".format(a_fit, p_fit))
plt.xlabel("$n$")
plt.ylabel("$\sigma$")
plt.legend()
plt.show()
In this exercise you will adapt the direct-sampling Monte Carlo method of the pebble game to higher dimensions to estimate the -dimensional volume of the -dimensional ball of radius .
(a) Adapt the direct-sampling Monte Carlo method to the pebble game in a -dimensional hypercube with an inscribed -dimensional unit ball. (12 pts)
def number_of_hits_in_d_dimensions(N,d):
"""Simulates number of hits in case of N trials in the d-dimensional direct-sampling pebble game."""
def random_in_d_dimensional_box():
"""Returns a random position in the box [-1,1)^d."""
return rng.uniform(-1,1,d)
number_hits = 0
for i in range(N):
position = random_in_d_dimensional_box()
if is_in_circle(position):
number_hits += 1
return number_hits
cell-e8e97677ad30041e
Score: 12.0 / 12.0 (Top)
from nose.tools import assert_almost_equal
assert_almost_equal(number_of_hits_in_d_dimensions(100,1),100,delta=1)
assert_almost_equal(number_of_hits_in_d_dimensions(2000,3),1045,delta=50)
### BEGIN HIDDEN TESTS
assert_almost_equal(number_of_hits_in_d_dimensions(2000,2),1572,delta=50)
assert_almost_equal(number_of_hits_in_d_dimensions(2000,4),619,delta=50)
### END HIDDEN TESTS
(b) Compare your estimates for the volume of the -dimensional unit ball for trials and to the exact formula in a plot. Hint: the Gamma function is available in scipy.special.gamma. (12 pts)
import scipy.special
def volume_d_ball(d):
"""Returns the volume of a d-dimensional unit ball."""
return np.pi**(d/2)/scipy.special.gamma(d/2 + 1)
N = 10000
d_range = np.array(range(1, 8))
V_d_mc = [number_of_hits_in_d_dimensions(N, d)/N*2**d for d in d_range]
## Plotting
plt.figure()
plt.plot(d_range, V_d_mc, label="direct-sampling")
plt.plot(d_range, volume_d_ball(d_range), label="exact")
plt.xlabel("dimension $d$")
plt.ylabel("volume $V_d$")
plt.legend()
plt.show()
In the lecture we mentioned the "1/2 rule" for Metropolis algorithms: if moves are rarely rejected then typically you do not explore the domain efficiently, while if most moves are rejected you are wasting efforts proposing moves. A good rule of thumb then is to aim for a rejection rate of . In this exercise you are asked to test whether this rule of thumb makes any sense for the Markov-chain pebble game by varying the throwing range .
(a) Estimate the mean square deviation from for different values of ranging between and , but fixed number of trials (say, 2000). For a decent estimate of the mean you will need at least repetitions. (14 pts)
N = 2000 # number of trials
m = 200 # number of repetitions
delta_range = np.linspace(0, 3, 10) # throwing ranges
p0 = np.array([0., 0.]) # starting position in origin
hits = np.zeros((len(delta_range), m), dtype=int)
for index_delta, delta in enumerate(delta_range):
for i in range(m):
hits[index_delta][i] = markov_pebble(p0, delta, N)
## Print the results
print("{:4} {:6} {:8} {:<10}".format('N','delta','4*hits/trials','E[(4*hits/trials - pi)^2]'))
for index_delta, delta in enumerate(delta_range):
pi_estimates = hits[index_delta]/N*4
print("{:4} {:1.4f} {:1.4f} {:1.3f}"
.format(N, delta, np.mean(pi_estimates), np.mean((pi_estimates - np.pi)**2)))
(b) Measure the rejection rate for the same simulations as in (a). (14 pts)
# To calculate the rejection rate, we need to know the positions. I will copy
# and adapt the above code and the code in the markov_pebble function as is
# provided at the beginning of this document.
N = 2000 # number of trials
m = 200 # number of repetitions
delta_range = np.linspace(0, 3, 10) # throwing ranges
p0 = np.array([0., 0.]) # starting position in origin
hits = np.zeros((len(delta_range), m), dtype=int)
rejections = np.zeros((len(delta_range), m), dtype=int)
for index_delta, delta in enumerate(delta_range):
for i in range(m):
prev_position = None
for position in markov_pebble_generator(p0,delta,N):
if np.all(position == prev_position):
rejections[index_delta][i] += 1
if is_in_circle(position):
hits[index_delta][i] += 1
prev_position = position
## Print the results
print("{:4} {:6} {:8} {:<10} {:<10}"
.format('N','delta','4*hits/trials','E[(4*hits/trials - pi)^2]', 'rejection rate'))
for index_delta, delta in enumerate(delta_range):
pi_estimates = hits[index_delta]/N*4
print("{:4} {:1.4f} {:1.4f} {:1.3f} {:1.3f}"
.format(N, delta, np.mean(pi_estimates), np.mean((pi_estimates - np.pi)**2), np.mean(rejections[index_delta]/N)))
(c) Plot both the mean square deviation and the rejection rate as function of . How well does the 1/2 rule apply in this situation? (12 pts)
plt.figure()
plt.title("Mean square deviation and rejection rate of $\\pi$ guesses by the Metropolis algorithm for {} trials of each {} repetitions".format(N, m))
plt.plot(delta_range, np.mean((hits/N*4 - np.pi)**2, axis=1), label="$\mathbb{E}[(\\frac{4hits}{trials} - \\pi)^2]$")
plt.plot(delta_range, np.mean(rejections/N, axis=1), label="rejection rate")
plt.xlabel("throwing range $\\delta$")
plt.legend()
plt.rc('legend', fontsize=13)
plt.show()
print("The rejection rate is around 1/2 for delta = 1.3333, which also yields near lowest mean square deviation to pi.")
print("The rule of thumb works quite nicely in this case, although it does not yields the best result.")