Random walks
Random walks (lesson 5.11.1)
0 of 12 correct
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
How to type answers
- Powers with
^:x^7,2^(1/3),a^-2. - Multiply with
*or by writing side by side:3x^2,2(x+1). ln(x)is the natural log,log(x, 2)is log base 2,eis Euler's number,sqrt(x)is a square root.sin(x),cos(x)andtan(x)take radians; writepifor π andsin(x)^2for sin²x.- Your first answer to each question is saved: it counts towards your score and brings the question back for review.
- Exact answers like
ln(20)/ln(3)are best. Decimals need about 4 significant figures.
Markov chains
Markov chains (lesson 5.11.2)
0 of 10 correct
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
How to type answers
- Powers with
^:x^7,2^(1/3),a^-2. - Multiply with
*or by writing side by side:3x^2,2(x+1). ln(x)is the natural log,log(x, 2)is log base 2,eis Euler's number,sqrt(x)is a square root.sin(x),cos(x)andtan(x)take radians; writepifor π andsin(x)^2for sin²x.- Your first answer to each question is saved: it counts towards your score and brings the question back for review.
- Exact answers like
ln(20)/ln(3)are best. Decimals need about 4 significant figures.
Coding exercise
The stationary distribution
Write stationary(P), which returns the stationary distribution of a transition matrix P, as in lesson 5.11.2. P is a NumPy array or nested lists, with rows the state now.
- Raise
ValueErrorunlessPis square, every entry lies between 0 and 1, and every row adds up to 1 within . - Solve with the entries adding up to 1. Raise
ValueErrorif the answer is not unique: then has rank below . - Return an array of probabilities that add up to 1.
Running the chain for many steps is not enough: the tests include a chain that swings and never settles. They also use the weather chain, lesson 3.6.2's towns and the rating table of case study 5.1.6. A chain with two absorbing states and some broken tables must raise ValueError.
See the tests
import numpy as np
WEATHER = [[0.7, 0.2, 0.1], [0.3, 0.4, 0.3], [0.2, 0.3, 0.5]]
RATINGS = [[0.90, 0.08, 0.015, 0.005], [0.05, 0.85, 0.08, 0.02], [0.01, 0.09, 0.80, 0.10], [0.0, 0.0, 0.0, 1.0]]
def expect_value_error(call, why):
try:
call()
except ValueError:
return
raise AssertionError(f"expected ValueError: {why}")
def test_weather():
"""The weather chain of lesson 5.11.2 has stationary distribution (21/46, 13/46, 6/23)"""
expect_close(np.asarray(stationary(WEATHER)), np.array([21 / 46, 13 / 46, 6 / 23]), what="stationary(weather)", rel=1e-9)
def test_two_towns():
"""Lesson 3.6.2's two towns, as rows: (2/3, 1/3)"""
expect_close(np.asarray(stationary(np.array([[0.9, 0.1], [0.2, 0.8]]))), np.array([2 / 3, 1 / 3]), what="stationary(towns)", rel=1e-9)
def test_swinging_chain():
"""A chain that swings between two states never settles, but (1/2, 1/2) is still stationary"""
expect_close(np.asarray(stationary([[0, 1], [1, 0]])), np.array([0.5, 0.5]), what="stationary([[0, 1], [1, 0]])", rel=1e-9)
def test_three_cycle():
"""A chain that cycles 1 → 2 → 3 → 1 has stationary distribution (1/3, 1/3, 1/3)"""
expect_close(np.asarray(stationary([[0, 1, 0], [0, 0, 1], [1, 0, 0]])), np.array([1, 1, 1]) / 3, what="stationary(cycle)", rel=1e-9)
def test_absorbing():
"""The rating table of case study 5.1.6 ends in default: (0, 0, 0, 1)"""
expect_close(np.asarray(stationary(RATINGS)), np.array([0.0, 0.0, 0.0, 1.0]), what="stationary(ratings)", abs_tol=1e-9)
def test_any_chain():
"""For a seeded 5-state chain, the answer is a pmf with πP = π"""
rng = np.random.default_rng(5113)
P = rng.random((5, 5))
P /= P.sum(axis=1, keepdims=True)
pi = np.asarray(stationary(P))
expect_close(pi.sum(), 1.0, what="sum of the entries", rel=1e-9)
assert (pi >= 0).all(), f"entries must not be negative, got {pi}"
expect_close(pi @ P, pi, what="πP", abs_tol=1e-12)
def test_not_unique():
"""Two absorbing states, as in the walk between 0 and 2, leave many stationary distributions: ValueError"""
expect_value_error(lambda: stationary([[1, 0, 0], [0.5, 0, 0.5], [0, 0, 1]]), "(1 - w, 0, w) is stationary for every w")
expect_value_error(lambda: stationary(np.eye(2)), "every distribution is stationary for the identity")
def test_bad_matrices():
"""A row adding up to 1.01, a negative entry or a matrix that is not square raise ValueError"""
expect_value_error(lambda: stationary([[0.7, 0.31], [0.2, 0.8]]), "the first row adds up to 1.01")
expect_value_error(lambda: stationary([[1.2, -0.2], [0.5, 0.5]]), "an entry is negative")
expect_value_error(lambda: stationary([[0.5, 0.5, 0.0], [0.1, 0.9, 0.0]]), "a 2 × 3 matrix is not a transition matrix")