1. Exercise sheet#

Some general remarks about the exercises:

  • For your convenience functions from the lecture are included below. Feel free to reuse them without copying to the exercise solution box.

  • For each part of the exercise a solution box has been added, but you may insert additional boxes. Do not hesitate to add Markdown boxes for textual or LaTeX answers (via 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().

  • Please make your code readable by humans (and not just by the Python interpreter): choose informative function and variable names and use consistent formatting. Feel free to check the PEP 8 Style Guide for Python for the widely adopted coding conventions or this guide for explanation.

  • Make sure that the full notebook runs without errors before submitting your work. This you can do by selecting Kernel > Restart & Run All in the jupyter menu.

  • Each sheet has 100 points worth of exercises. Note that only the grades of sheets number 2, 4, 6, 8 count towards the course examination. Submitting sheets 1, 3, 5, 7 & 9 is voluntary and their grades are just for feedback.

Please fill in your name here:

NAME = ""

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

1.1. Empirical convergence rate in the pebble game#

(a) Write a function pi_stddev that estimates the standard deviation \(\sigma\) of the \(\pi\)-estimate using \(n\) trials by running the direct-sampling pebble game \(m\) times. Store this data for \(n=2^4,2^5,\ldots,2^{14}\) and \(m=200\) in an array. Hint: you may use the NumPy function np.std. (12 pts)

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."""
    # YOUR CODE HERE
    raise NotImplementedError()
    
stddev_data = np.array([[2**k,pi_stddev(2**k,200)] for k in range(4,12)])
stddev_data
# 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)

(b) Write a function fit_power_law that takes an array of \((n,\sigma)\) pairs and determines best-fit parameters \(a,p\) for the curve \(\sigma = a n^p\). This is best done by fitting a straight line to the data on a log-log scale (\(\log\sigma=\log a+p\log n\)). Hint: use curve_fit from SciPy. (12 pts)

def fit_power_law(stddev_data):
    """Compute the best fit parameters a and p."""
    # YOUR CODE HERE
    raise NotImplementedError()
    return a_fit, p_fit
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)

(c) Plot the data against the fitted curve \(\sigma = a n^p\) in a log-log plot. Don’t forget to properly label your axes. Hint: use loglog from matplotlib. (12 pts)

# YOUR CODE HERE
raise NotImplementedError()

1.2. Volume of a unit ball in other dimensions#

In this exercise you will adapt the direct-sampling Monte Carlo method of the pebble game to higher dimensions to estimate the \(d\)-dimensional volume of the \(d\)-dimensional ball of radius \(1\).

(a) Adapt the direct-sampling Monte Carlo method to the pebble game in a \(d\)-dimensional hypercube \((-1,1)^d\) with an inscribed \(d\)-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."""
    # YOUR CODE HERE
    raise NotImplementedError()
    return number_hits
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)

(b) Compare your estimates for the volume \(V_d\) of the \(d\)-dimensional unit ball for \(N=10000\) trials and \(d=1,2,\ldots,7\) to the exact formula \(V_d = \frac{\pi^{d/2}}{\Gamma(\tfrac{d}{2}+1)}\) in a plot. Hint: the Gamma function is available in scipy.special.gamma. (12 pts)

# YOUR CODE HERE
raise NotImplementedError()

1.3. Efficiency of the Metropolis algorithm and the 1/2-rule#

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 \(1/2\). 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 \(\delta\).

(a) Estimate the mean square deviation \(\mathbb{E}[(\tfrac{4\text{hits}}{\text{trials}} - \pi)^2 ]\) from \(\pi\) for different values of \(\delta\) ranging between \(0\) and \(3\), but fixed number of trials (say, 2000). For a decent estimate of the mean you will need at least \(100\) repetitions. (14 pts)

# YOUR CODE HERE
raise NotImplementedError()

(b) Measure the rejection rate for the same simulations as in (a). (14 pts)

# YOUR CODE HERE
raise NotImplementedError()

(c) Plot both the mean square deviation and the rejection rate as function of \(\delta\). How well does the 1/2 rule apply in this situation? (12 pts)

# YOUR CODE HERE
raise NotImplementedError()