@@ -355,13 +355,12 @@ At each step, we draw a demand shock from the geometric distribution and update
355355356356```{code-cell} ipython3
357357@numba.jit(nopython=True)
358-def sim_inventories(ts_length, σ, p, X_init=0, seed=0):
358+def sim_inventories(ts_length, σ, p, rng, X_init=0):
359359 """Simulate inventory dynamics under policy σ."""
360- np.random.seed(seed)
361360 X = np.zeros(ts_length, dtype=np.int32)
362361 X[0] = X_init
363362 for t in range(ts_length - 1):
364- d = np.random.geometric(p) - 1
363+ d = rng.geometric(p) - 1
365364 X[t+1] = max(X[t] - d, 0) + σ[X[t]]
366365 return X
367366```
@@ -373,8 +372,8 @@ a large order to replenish stock (the upward jumps), after which inventory
373372gradually declines as demand is served.
374373375374```{code-cell} ipython3
376-def plot_ts(ts_length=200, fontsize=10):
377- X = sim_inventories(ts_length, σ_star, p)
375+def plot_ts(ts_length=200, fontsize=10, seed=0):
376+ X = sim_inventories(ts_length, σ_star, p, np.random.default_rng(seed))
378377 fig, ax = plt.subplots()
379378380379 ax.plot(X, label=r"$X_t$", alpha=0.7)
@@ -588,8 +587,7 @@ At specified step counts (given by `snapshot_steps`), we record the current gree
588587```{code-cell} ipython3
589588@numba.jit(nopython=True)
590589def q_learning_kernel(K, p, c, κ, β, n_steps, X_init,
591- ε_init, ε_min, ε_decay, q_init, snapshot_steps, seed):
592- np.random.seed(seed)
590+ ε_init, ε_min, ε_decay, q_init, snapshot_steps, rng):
593591 q = np.full((K + 1, K + 1), q_init)
594592 n = np.zeros((K + 1, K + 1)) # visit counts for learning rate
595593 ε = ε_init
@@ -600,7 +598,7 @@ def q_learning_kernel(K, p, c, κ, β, n_steps, X_init,
600598601599 # Initialize state and action
602600 x = X_init
603- a = np.random.randint(0, K - x + 1)
601+ a = rng.integers(0, K - x + 1)
604602605603 for t in range(n_steps):
606604 # Record policy snapshot if needed
@@ -609,7 +607,7 @@ def q_learning_kernel(K, p, c, κ, β, n_steps, X_init,
609607 snap_idx += 1
610608611609 # === Draw D_{t+1} and observe outcome ===
612- d = np.random.geometric(p) - 1
610+ d = rng.geometric(p) - 1
613611 reward = min(x, d) - c * a - κ * (a > 0)
614612 x_next = max(x - d, 0) + a
615613@@ -629,8 +627,8 @@ def q_learning_kernel(K, p, c, κ, β, n_steps, X_init,
629627630628 # === Behavior policy: ε-greedy (uses a_next, the argmax action) ===
631629 x = x_next
632- if np.random.random() < ε:
633- a = np.random.randint(0, K - x + 1)
630+ if rng.random() < ε:
631+ a = rng.integers(0, K - x + 1)
634632 else:
635633 a = a_next
636634 ε = max(ε_min, ε * ε_decay)
@@ -648,8 +646,9 @@ def q_learning(model, n_steps=20_000_000, X_init=0,
648646 K = len(x_values) - 1
649647 if snapshot_steps is None:
650648 snapshot_steps = np.array([], dtype=np.int64)
649+ rng = np.random.default_rng(seed)
651650 return q_learning_kernel(K, p, c, κ, β, n_steps, X_init,
652- ε_init, ε_min, ε_decay, q_init, snapshot_steps, seed)
651+ ε_init, ε_min, ε_decay, q_init, snapshot_steps, rng)
653652```
654653655654Next we run $n$ = 5 million steps and take policy snapshots at steps 10,000, 1,000,000, and $n$.
@@ -726,7 +725,8 @@ X_init = K // 2
726725sim_seed = 5678
727726728727# Optimal policy
729-X_opt = sim_inventories(ts_length, σ_star, p, X_init, seed=sim_seed)
728+X_opt = sim_inventories(ts_length, σ_star, p,
729+ np.random.default_rng(sim_seed), X_init)
730730axes[0].plot(X_opt, alpha=0.7)
731731axes[0].set_ylabel("inventory")
732732axes[0].set_title("Optimal (VFI)")
@@ -735,7 +735,8 @@ axes[0].set_ylim(0, K + 2)
735735# Q-learning snapshots
736736for i in range(n_snaps):
737737 σ_snap = snapshots[i]
738- X = sim_inventories(ts_length, σ_snap, p, X_init, seed=sim_seed)
738+ X = sim_inventories(ts_length, σ_snap, p,
739+ np.random.default_rng(sim_seed), X_init)
739740 axes[i + 1].plot(X, alpha=0.7)
740741 axes[i + 1].set_ylabel("inventory")
741742 axes[i + 1].set_title(f"Step {snap_steps[i]:,}")