@@ -63,6 +63,8 @@ from scipy.stats import beta as beta_dist
6363import pandas as pd
6464from IPython.display import display, Math
6565import quantecon as qe
66+
67+ rng = np.random.default_rng()
6668```
6769
6870## Likelihood Ratio Process
@@ -154,18 +156,18 @@ def likelihood_ratio(w, f_func, g_func):
154156 return f_func(w) / g_func(w)
155157
156158@jit
157- def simulate_likelihood_ratios(a, b, f_func, g_func, T=50, N=500):
159+ def simulate_likelihood_ratios(a, b, f_func, g_func, rng, T=50, N=500):
158160 """
159161 Generate N sets of T observations of the likelihood ratio.
160162 """
161163 l_arr = np.empty((N, T))
162164 for i in range(N):
163165 for j in range(T):
164- w = np.random .beta(a, b)
166+ w = rng .beta(a, b)
165167 l_arr[i, j] = f_func(w) / g_func(w)
166168 return l_arr
167169
168- def simulate_sequences(distribution, f_func, g_func,
170+ def simulate_sequences(distribution, f_func, g_func, rng,
169171 F_params=(1, 1), G_params=(3, 1.2), T=50, N=500):
170172 """
171173 Generate N sequences of T observations from specified distribution.
@@ -177,7 +179,7 @@ def simulate_sequences(distribution, f_func, g_func,
177179 else:
178180 raise ValueError("distribution must be 'f' or 'g'")
179181
180- l_arr = simulate_likelihood_ratios(a, b, f_func, g_func, T, N)
182+ l_arr = simulate_likelihood_ratios(a, b, f_func, g_func, rng, T, N)
181183 l_seq = np.cumprod(l_arr, axis=1)
182184 return l_arr, l_seq
183185
@@ -207,7 +209,7 @@ draws from $g$.
207209
208210``` {code-cell} ipython3
209211# Simulate when nature draws from g
210- l_arr_g, l_seq_g = simulate_sequences('g', f, g, (F_a, F_b), (G_a, G_b))
212+ l_arr_g, l_seq_g = simulate_sequences('g', f, g, rng, (F_a, F_b), (G_a, G_b))
211213plot_likelihood_paths(l_seq_g,
212214 title="$L(w^{t})$ paths when nature draws from g",
213215 ylim=[0, 3])
@@ -288,7 +290,7 @@ averaging across these many paths at each $t$.
288290
289291``` {code-cell} ipython3
290292l_arr_g, l_seq_g = simulate_sequences('g',
291- f, g, (F_a, F_b), (G_a, G_b), N=50000)
293+ f, g, rng, (F_a, F_b), (G_a, G_b), N=50000)
292294```
293295
294296It would be useful to use simulations to verify that unconditional means
@@ -335,7 +337,7 @@ Please note the scale of the $y$ axis.
335337
336338``` {code-cell} ipython3
337339# Simulate when nature draws from f
338- l_arr_f, l_seq_f = simulate_sequences('f', f, g,
340+ l_arr_f, l_seq_f = simulate_sequences('f', f, g, rng,
339341 (F_a, F_b), (G_a, G_b), N=50000)
340342```
341343
@@ -787,7 +789,7 @@ for i, scenario in enumerate(scenarios):
787789 T = 150
788790
789791 # Generate data from h
790- h_data = np.random .beta(scenario["h_params"][0],
792+ h_data = rng .beta(scenario["h_params"][0],
791793 scenario["h_params"][1], (N_paths, T))
792794 l_ratios, l_cumulative = compute_likelihood_ratios(h_data, f, g)
793795 log_l_cumulative = np.log(l_cumulative)
@@ -876,45 +878,45 @@ of IID draws from $g$.
876878Here is Python code that we'll use to implement timing protocol 1 and 2
877879
878880```{code-cell} ipython3
879- def protocol_1(π_minus_1, T, N=1000, F_params=(1, 1), G_params=(3, 1.2)):
881+ def protocol_1(π_minus_1, T, rng, N=1000, F_params=(1, 1), G_params=(3, 1.2)):
880882 """
881883 Simulate Protocol 1: Nature decides once at t=-1 which model to use.
882884 """
883885 F_a, F_b = F_params
884886 G_a, G_b = G_params
885887
886888 # Single coin flip for the true model
887- true_models_F = np .random.rand (N) < π_minus_1
889+ true_models_F = rng .random(N) < π_minus_1
888890 sequences = np.empty((N, T))
889891
890892 n_f = np.sum(true_models_F)
891893 n_g = N - n_f
892894
893895 if n_f > 0:
894- sequences[true_models_F, :] = np.random .beta(F_a, F_b, (n_f, T))
896+ sequences[true_models_F, :] = rng .beta(F_a, F_b, (n_f, T))
895897 if n_g > 0:
896- sequences[~true_models_F, :] = np.random .beta(G_a, G_b, (n_g, T))
898+ sequences[~true_models_F, :] = rng .beta(G_a, G_b, (n_g, T))
897899
898900 return sequences, true_models_F
899901
900- def protocol_2(π_minus_1, T, N=1000, F_params=(1, 1), G_params=(3, 1.2)):
902+ def protocol_2(π_minus_1, T, rng, N=1000, F_params=(1, 1), G_params=(3, 1.2)):
901903 """
902904 Simulate Protocol 2: Nature decides at each time step which model to use.
903905 """
904906 F_a, F_b = F_params
905907 G_a, G_b = G_params
906908
907909 # Coin flips for each time step
908- true_models_F = np .random.rand( N, T) < π_minus_1
910+ true_models_F = rng .random(( N, T) ) < π_minus_1
909911 sequences = np.empty((N, T))
910912
911913 n_f = np.sum(true_models_F)
912914 n_g = N * T - n_f
913915
914916 if n_f > 0:
915- sequences[true_models_F] = np.random .beta(F_a, F_b, n_f)
917+ sequences[true_models_F] = rng .beta(F_a, F_b, n_f)
916918 if n_g > 0:
917- sequences[~true_models_F] = np.random .beta(G_a, G_b, n_g)
919+ sequences[~true_models_F] = rng .beta(G_a, G_b, n_g)
918920
919921 return sequences, true_models_F
920922```
@@ -970,12 +972,12 @@ Now let's simulate timing protocol 1 and compute the error probabilities
970972```{code-cell} ipython3
971973
972974def compute_protocol_1_errors(π_minus_1, T_max, N_simulations, f_func, g_func,
973- F_params=(1, 1), G_params=(3, 1.2)):
975+ rng, F_params=(1, 1), G_params=(3, 1.2)):
974976 """
975977 Compute error probabilities for Protocol 1.
976978 """
977979 sequences, true_models = protocol_1(
978- π_minus_1, T_max, N_simulations, F_params, G_params)
980+ π_minus_1, T_max, rng, N_simulations, F_params, G_params)
979981 l_ratios, L_cumulative = compute_likelihood_ratios(sequences,
980982 f_func, g_func)
981983
@@ -1007,10 +1009,10 @@ The following code visualizes the error probabilities for timing protocol 1
10071009:tags: [hide-input]
10081010
10091011def analyze_protocol_1(π_minus_1, T_max, N_simulations, f_func, g_func,
1010- F_params=(1, 1), G_params=(3, 1.2)):
1012+ rng, F_params=(1, 1), G_params=(3, 1.2)):
10111013 """Analyze Protocol 1"""
10121014 result = compute_protocol_1_errors(π_minus_1, T_max, N_simulations,
1013- f_func, g_func, F_params, G_params)
1015+ f_func, g_func, rng, F_params, G_params)
10141016
10151017 # Plot results
10161018 fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
@@ -1046,7 +1048,7 @@ T_max = 30
10461048N_simulations = 10_000
10471049
10481050result_p1 = analyze_protocol_1(π_minus_1, T_max, N_simulations,
1049- f, g, (F_a, F_b), (G_a, G_b))
1051+ f, g, rng, (F_a, F_b), (G_a, G_b))
10501052```
10511053
10521054Notice how the model selection error probability approaches zero as $T$ grows.
@@ -1078,12 +1080,12 @@ Now let's write some code to simulate it
10781080
10791081```{code-cell} ipython3
10801082def compute_protocol_2_errors(π_minus_1, T_max, N_simulations, f_func, g_func,
1081- F_params=(1, 1), G_params=(3, 1.2)):
1083+ rng, F_params=(1, 1), G_params=(3, 1.2)):
10821084 """
10831085 Compute error probabilities for Protocol 2.
10841086 """
10851087 sequences, true_models = protocol_2(π_minus_1,
1086- T_max, N_simulations, F_params, G_params)
1088+ T_max, rng, N_simulations, F_params, G_params)
10871089 l_ratios, _ = compute_likelihood_ratios(sequences, f_func, g_func)
10881090
10891091 T_range = np.arange(1, T_max + 1)
@@ -1186,11 +1188,11 @@ Now we simulate timing protocol 2 and compute the classification error probabili
11861188In the next cell, we also compare the theoretical classification accuracy to the empirical classification accuracy
11871189
11881190```{code-cell} ipython3
1189- def analyze_protocol_2(π_minus_1, T_max, N_simulations, f_func, g_func,
1191+ def analyze_protocol_2(π_minus_1, T_max, N_simulations, f_func, g_func, rng,
11901192 theory_error=None, F_params=(1, 1), G_params=(3, 1.2)):
11911193 """Analyze Protocol 2."""
11921194 result = compute_protocol_2_errors(π_minus_1, T_max, N_simulations,
1193- f_func, g_func, F_params, G_params)
1195+ f_func, g_func, rng, F_params, G_params)
11941196
11951197 # Plot results
11961198 plt.figure(figsize=(10, 6))
@@ -1210,7 +1212,7 @@ def analyze_protocol_2(π_minus_1, T_max, N_simulations, f_func, g_func,
12101212 return result
12111213
12121214# Analyze Protocol 2
1213- result_p2 = analyze_protocol_2(π_minus_1, T_max, N_simulations, f, g,
1215+ result_p2 = analyze_protocol_2(π_minus_1, T_max, N_simulations, f, g, rng,
12141216 theory_error, (F_a, F_b), (G_a, G_b))
12151217```
12161218
@@ -1403,8 +1405,8 @@ for i, ((f_a, f_b), (g_a, g_b)) in enumerate(distribution_pairs):
14031405 chernoff_vals[i], _ = compute_chernoff_entropy(f, g)
14041406
14051407 # Generate samples
1406- sequences_f = np.random .beta(f_a, f_b, (N_half, T_large))
1407- sequences_g = np.random .beta(g_a, g_b, (N_half, T_large))
1408+ sequences_f = rng .beta(f_a, f_b, (N_half, T_large))
1409+ sequences_g = rng .beta(g_a, g_b, (N_half, T_large))
14081410
14091411 # Compute likelihood ratios and cumulative products
14101412 _, L_cumulative_f = compute_likelihood_ratios(sequences_f, f, g)
@@ -1585,12 +1587,12 @@ def markov_kl_divergence(P_f, P_g, pi_f):
15851587 kl_rate = np.sum(pi_f[:, np.newaxis] * P_f * log_ratios)
15861588 return kl_rate
15871589
1588- def simulate_markov_chain(P, pi_0, T, N_paths=1000):
1590+ def simulate_markov_chain(P, pi_0, T, rng, N_paths=1000):
15891591 """
15901592 Simulate N_paths sample paths from a Markov chain
15911593 """
15921594 mc = qe.MarkovChain(P, state_values=None)
1593- initial_states = np.random .choice(len(P), size=N_paths, p=pi_0)
1595+ initial_states = rng .choice(len(P), size=N_paths, p=pi_0)
15941596 paths = np.zeros((N_paths, T+1), dtype=int)
15951597
15961598 for i in range(N_paths):
@@ -1621,7 +1623,7 @@ def compute_likelihood_ratio_markov(paths, P_f, P_g, π_0_f, π_0_g):
16211623
16221624 return L_ratios
16231625
1624- def analyze_markov_chains(P_f, P_g,
1626+ def analyze_markov_chains(P_f, P_g, rng,
16251627 T=500, N_paths=1000, plot_paths=True, n_show=50):
16261628 """
16271629 Complete analysis of two Markov chains
@@ -1642,7 +1644,7 @@ def analyze_markov_chains(P_f, P_g,
16421644
16431645 if plot_paths:
16441646 # Simulate and plot paths
1645- paths_from_f = simulate_markov_chain(P_f, π_f, T, N_paths)
1647+ paths_from_f = simulate_markov_chain(P_f, π_f, T, rng, N_paths)
16461648 L_ratios_f = compute_likelihood_ratio_markov(
16471649 paths_from_f, P_f, P_g, π_f, π_g)
16481650
@@ -1676,16 +1678,16 @@ def analyze_markov_chains(P_f, P_g,
16761678 'kl_rate_gf': kl_rate_gf
16771679 }
16781680
1679- def compute_markov_selection_error(T_values, P_f, P_g, π_0_f, π_0_g, N_sim=1000):
1681+ def compute_markov_selection_error(T_values, P_f, P_g, π_0_f, π_0_g, rng, N_sim=1000):
16801682 """
16811683 Compute model selection error probability for Markov chains
16821684 """
16831685 errors = []
16841686
16851687 for T in T_values:
16861688 # Simulate from both models
1687- paths_f = simulate_markov_chain(P_f, π_0_f, T, N_sim//2)
1688- paths_g = simulate_markov_chain(P_g, π_0_g, T, N_sim//2)
1689+ paths_f = simulate_markov_chain(P_f, π_0_f, T, rng, N_sim//2)
1690+ paths_g = simulate_markov_chain(P_g, π_0_g, T, rng, N_sim//2)
16891691
16901692 # Compute likelihood ratios
16911693 L_f = compute_likelihood_ratio_markov(paths_f, P_f, P_g, π_0_f, π_0_g)
@@ -1717,7 +1719,7 @@ P_g = np.array([[0.5, 0.3, 0.2],
17171719 [0.2, 0.6, 0.2],
17181720 [0.2, 0.2, 0.6]])
17191721
1720- markov_results = analyze_markov_chains(P_f, P_g)
1722+ markov_results = analyze_markov_chains(P_f, P_g, rng )
17211723```
17221724
17231725## Related lectures
0 commit comments