import math import random random.seed(42) def sample_uniform(a, b): return a + (b - a) * random.random() def sample_exponential_inverse_cdf(lam): u = random.random() return -math.log(u) / lam def verify_inverse_cdf(lam, n=10000): samples = [sample_exponential_inverse_cdf(lam) for _ in range(n)] empirical_mean = sum(samples) / len(samples) theoretical_mean = 1.0 / lam print(f" Exponential(lambda={lam}): empirical mean={empirical_mean:.4f}, " f"theoretical={theoretical_mean:.4f}") return samples def normal_pdf(x, mu, sigma): coeff = 1.0 / (sigma * math.sqrt(2 * math.pi)) exponent = -0.5 * ((x - mu) / sigma) ** 2 return coeff * math.exp(exponent) def sample_normal_box_muller(mu=0.0, sigma=1.0): u1 = random.random() u2 = random.random() z = math.sqrt(-2 * math.log(u1)) * math.cos(2 * math.pi * u2) return mu + sigma * z def rejection_sample(target_pdf, proposal_sample, proposal_pdf, M): attempts = 0 while True: x = proposal_sample() u = random.random() attempts += 1 if u < target_pdf(x) / (M * proposal_pdf(x)): return x, attempts def rejection_sample_batch(target_pdf, proposal_sample, proposal_pdf, M, n): samples = [] total_attempts = 0 for _ in range(n): x, attempts = rejection_sample(target_pdf, proposal_sample, proposal_pdf, M) samples.append(x) total_attempts += attempts acceptance_rate = n / total_attempts return samples, acceptance_rate def truncated_normal_demo(mu, sigma, a, b, n=5000): norm_const = sum( normal_pdf(a + (b - a) * i / 1000, mu, sigma) * (b - a) / 1000 for i in range(1001) ) M_val = max( normal_pdf(x, mu, sigma) / (1.0 / (b - a)) for x in [a + (b - a) * i / 200 for i in range(201)] ) def target(x): if a <= x <= b: return normal_pdf(x, mu, sigma) return 0.0 def proposal(): return sample_uniform(a, b) def proposal_pdf(x): if a <= x <= b: return 1.0 / (b - a) return 0.0 samples, acc_rate = rejection_sample_batch(target, proposal, proposal_pdf, M_val, n) return samples, acc_rate def importance_sampling_estimate(f, target_pdf, proposal_pdf, proposal_sample, n): weighted_sum = 0.0 weight_sum = 0.0 for _ in range(n): x = proposal_sample() w = target_pdf(x) / proposal_pdf(x) weighted_sum += f(x) * w weight_sum += w unnormalized = weighted_sum / n self_normalized = weighted_sum / weight_sum return unnormalized, self_normalized def importance_sampling_demo(): mu, sigma = 2.0, 1.0 a, b = -3.0, 7.0 def f(x): return x ** 2 def target(x): return normal_pdf(x, mu, sigma) def proposal(): return sample_uniform(a, b) def proposal_pdf(x): if a <= x <= b: return 1.0 / (b - a) return 0.0 est_unnorm, est_selfnorm = importance_sampling_estimate( f, target, proposal_pdf, proposal, n=50000 ) theoretical = mu ** 2 + sigma ** 2 print(f" E[X^2] under N({mu},{sigma}):") print(f" Unnormalized IS: {est_unnorm:.4f}") print(f" Self-normalized IS: {est_selfnorm:.4f}") print(f" Theoretical: {theoretical:.4f}") def monte_carlo_pi(n): inside = 0 for _ in range(n): x = random.uniform(-1, 1) y = random.uniform(-1, 1) if x * x + y * y <= 1: inside += 1 return 4 * inside / n def monte_carlo_integral(f, a, b, n): total = 0.0 for _ in range(n): x = sample_uniform(a, b) total += f(x) return (b - a) * total / n def metropolis_hastings(target_log_pdf, x0, n_samples, burn_in, proposal_std=1.0): samples = [] x = x0 accepted = 0 total = n_samples + burn_in for i in range(total): x_new = x + random.gauss(0, proposal_std) log_alpha = target_log_pdf(x_new) - target_log_pdf(x) if math.log(random.random() + 1e-300) < log_alpha: x = x_new if i >= burn_in: accepted += 1 if i >= burn_in: samples.append(x) acceptance_rate = accepted / n_samples return samples, acceptance_rate def metropolis_hastings_2d(target_log_pdf, x0, y0, n_samples, burn_in, proposal_std=0.5): samples = [] x, y = x0, y0 accepted = 0 total = n_samples + burn_in for i in range(total): x_new = x + random.gauss(0, proposal_std) y_new = y + random.gauss(0, proposal_std) log_alpha = target_log_pdf(x_new, y_new) - target_log_pdf(x, y) if math.log(random.random() + 1e-300) < log_alpha: x, y = x_new, y_new if i >= burn_in: accepted += 1 if i >= burn_in: samples.append((x, y)) acceptance_rate = accepted / n_samples return samples, acceptance_rate def bimodal_log_pdf(x): p1 = 0.4 * normal_pdf(x, -3, 1) p2 = 0.6 * normal_pdf(x, 3, 1) return math.log(p1 + p2 + 1e-300) def gibbs_sampling_2d(rho, n_samples, burn_in): x, y = 0.0, 0.0 samples = [] for i in range(n_samples + burn_in): x = random.gauss(rho * y, math.sqrt(1 - rho ** 2)) y = random.gauss(rho * x, math.sqrt(1 - rho ** 2)) if i >= burn_in: samples.append((x, y)) return samples def softmax(logits): max_l = max(logits) exps = [math.exp(z - max_l) for z in logits] total = sum(exps) return [e / total for e in exps] def sample_from_probs(probs): r = random.random() cumsum = 0.0 for i, p in enumerate(probs): cumsum += p if r <= cumsum: return i return len(probs) - 1 def temperature_sample(logits, temperature): if temperature <= 0: return logits.index(max(logits)) scaled = [z / temperature for z in logits] probs = softmax(scaled) return sample_from_probs(probs) def temperature_distribution(logits, temperature): if temperature <= 0: result = [0.0] * len(logits) result[logits.index(max(logits))] = 1.0 return result scaled = [z / temperature for z in logits] return softmax(scaled) def top_k_sample(logits, k): indexed = sorted(enumerate(logits), key=lambda x: -x[1]) top = indexed[:k] top_logits = [l for _, l in top] probs = softmax(top_logits) idx = sample_from_probs(probs) return top[idx][0] def top_k_distribution(logits, k): probs = softmax(logits) indexed = sorted(enumerate(probs), key=lambda x: -x[1]) result = [0.0] * len(logits) top_indices = [idx for idx, _ in indexed[:k]] top_probs = [probs[idx] for idx in top_indices] total = sum(top_probs) for idx in top_indices: result[idx] = probs[idx] / total return result def top_p_sample(logits, p): probs = softmax(logits) indexed = sorted(enumerate(probs), key=lambda x: -x[1]) cumsum = 0.0 selected = [] for token_idx, prob in indexed: cumsum += prob selected.append((token_idx, prob)) if cumsum >= p: break sel_probs = [pr for _, pr in selected] total = sum(sel_probs) sel_probs = [pr / total for pr in sel_probs] idx = sample_from_probs(sel_probs) return selected[idx][0] def top_p_distribution(logits, p): probs = softmax(logits) indexed = sorted(enumerate(probs), key=lambda x: -x[1]) cumsum = 0.0 selected_indices = [] for token_idx, prob in indexed: cumsum += prob selected_indices.append(token_idx) if cumsum >= p: break result = [0.0] * len(logits) total = sum(probs[i] for i in selected_indices) for i in selected_indices: result[i] = probs[i] / total return result def reparam_sample(mu, sigma): epsilon = random.gauss(0, 1) z = mu + sigma * epsilon return z, epsilon def reparam_gradient(epsilon): dz_dmu = 1.0 dz_dsigma = epsilon return dz_dmu, dz_dsigma def vae_forward_demo(mu, log_var): sigma = math.exp(0.5 * log_var) z, epsilon = reparam_sample(mu, sigma) dz_dmu = 1.0 dz_dsigma = epsilon dsigma_dlogvar = 0.5 * sigma dz_dmu_total = dz_dmu dz_dlogvar = dz_dsigma * dsigma_dlogvar return z, epsilon, dz_dmu_total, dz_dlogvar def gumbel_sample(): u = random.random() while u == 0: u = random.random() return -math.log(-math.log(u)) def gumbel_max_sample(log_probs): gumbels = [lp + gumbel_sample() for lp in log_probs] return gumbels.index(max(gumbels)) def gumbel_softmax_sample(log_probs, temperature): gumbels = [lp + gumbel_sample() for lp in log_probs] scaled = [g / temperature for g in gumbels] return softmax(scaled) def gumbel_softmax_straight_through(log_probs, temperature): soft = gumbel_softmax_sample(log_probs, temperature) hard_idx = soft.index(max(soft)) hard = [0.0] * len(log_probs) hard[hard_idx] = 1.0 return hard, soft def stratified_sample_1d(n): samples = [] for i in range(n): u = random.random() samples.append((i + u) / n) return samples def compare_sampling_variance(f, n, n_trials=200): standard_estimates = [] stratified_estimates = [] for _ in range(n_trials): standard_samples = [random.random() for _ in range(n)] standard_est = sum(f(x) for x in standard_samples) / n standard_estimates.append(standard_est) strat_samples = stratified_sample_1d(n) strat_est = sum(f(x) for x in strat_samples) / n stratified_estimates.append(strat_est) std_mean = sum(standard_estimates) / len(standard_estimates) std_var = sum((e - std_mean) ** 2 for e in standard_estimates) / len(standard_estimates) strat_mean = sum(stratified_estimates) / len(stratified_estimates) strat_var = sum((e - strat_mean) ** 2 for e in stratified_estimates) / len(stratified_estimates) return std_var, strat_var def text_generation_demo(vocab, logits, length, method, **kwargs): tokens = [] for _ in range(length): if method == "greedy": idx = logits.index(max(logits)) elif method == "temperature": idx = temperature_sample(logits, kwargs.get("temperature", 1.0)) elif method == "top_k": idx = top_k_sample(logits, kwargs.get("k", 5)) elif method == "top_p": idx = top_p_sample(logits, kwargs.get("p", 0.9)) else: idx = sample_from_probs(softmax(logits)) tokens.append(vocab[idx]) return " ".join(tokens) if __name__ == "__main__": print("=" * 65) print("SAMPLING METHODS") print("=" * 65) print("\n--- 1. Inverse CDF Sampling (Exponential) ---") for lam in [0.5, 1.0, 2.0]: verify_inverse_cdf(lam) print("\n--- 2. Rejection Sampling (Truncated Normal) ---") trunc_samples, acc = truncated_normal_demo(0, 1, -1, 2, n=5000) trunc_mean = sum(trunc_samples) / len(trunc_samples) print(f" Truncated N(0,1) on [-1, 2]: mean={trunc_mean:.4f}, " f"acceptance rate={acc:.4f}") print("\n--- 3. Importance Sampling ---") importance_sampling_demo() print("\n--- 4. Monte Carlo Estimation ---") print(" Estimating pi:") for n in [1000, 10000, 100000]: pi_est = monte_carlo_pi(n) error = abs(pi_est - math.pi) print(f" N={n:>7d}: pi ~ {pi_est:.6f}, error={error:.6f}") print(" Estimating integral of sin(x) from 0 to pi (true = 2.0):") for n in [1000, 10000, 100000]: est = monte_carlo_integral(math.sin, 0, math.pi, n) error = abs(est - 2.0) print(f" N={n:>7d}: estimate={est:.6f}, error={error:.6f}") print("\n--- 5. Metropolis-Hastings MCMC ---") print(" Sampling from bimodal distribution (mixture of Gaussians):") for std in [0.5, 1.0, 3.0]: samples_mh, acc_rate = metropolis_hastings( bimodal_log_pdf, x0=0.0, n_samples=10000, burn_in=2000, proposal_std=std ) mh_mean = sum(samples_mh) / len(samples_mh) mh_std = (sum((x - mh_mean) ** 2 for x in samples_mh) / len(samples_mh)) ** 0.5 print(f" proposal_std={std}: mean={mh_mean:.4f}, std={mh_std:.4f}, " f"acceptance={acc_rate:.4f}") print("\n Sampling from 2D Gaussian:") def gaussian_2d_log_pdf(x, y): return -0.5 * (x ** 2 + y ** 2) samples_2d, acc_2d = metropolis_hastings_2d( gaussian_2d_log_pdf, x0=5.0, y0=5.0, n_samples=10000, burn_in=2000, proposal_std=1.0 ) xs = [s[0] for s in samples_2d] ys = [s[1] for s in samples_2d] print(f" mean_x={sum(xs)/len(xs):.4f}, mean_y={sum(ys)/len(ys):.4f}, " f"acceptance={acc_2d:.4f}") print("\n--- 6. Gibbs Sampling (Correlated 2D Gaussian) ---") for rho in [0.0, 0.5, 0.9]: gibbs_samples = gibbs_sampling_2d(rho, n_samples=10000, burn_in=1000) gx = [s[0] for s in gibbs_samples] gy = [s[1] for s in gibbs_samples] empirical_corr = ( sum(a * b for a, b in gibbs_samples) / len(gibbs_samples) - (sum(gx) / len(gx)) * (sum(gy) / len(gy)) ) print(f" rho={rho}: empirical correlation={empirical_corr:.4f}") print("\n--- 7. Temperature Sampling ---") token_logits = [3.0, 2.0, 1.5, 0.5, -1.0, -2.0] vocab = ["the", "a", "this", "one", "that", "some"] print(" Logits:", token_logits) print(" Vocab:", vocab) for temp in [0.1, 0.5, 1.0, 1.5, 3.0]: dist = temperature_distribution(token_logits, temp) formatted = [f"{p:.4f}" for p in dist] print(f" T={temp:.1f}: [{', '.join(formatted)}]") print("\n Generation samples at different temperatures:") for temp in [0.1, 0.7, 1.0, 2.0]: tokens_out = [] for _ in range(10): idx = temperature_sample(token_logits, temp) tokens_out.append(vocab[idx]) print(f" T={temp}: {' '.join(tokens_out)}") print("\n--- 8. Top-k Sampling ---") print(" Logits:", token_logits) for k in [1, 2, 3, 6]: dist = top_k_distribution(token_logits, k) formatted = [f"{p:.4f}" for p in dist] print(f" k={k}: [{', '.join(formatted)}]") print("\n--- 9. Top-p (Nucleus) Sampling ---") print(" Logits:", token_logits) for p in [0.5, 0.8, 0.9, 0.95, 1.0]: dist = top_p_distribution(token_logits, p) nonzero = sum(1 for d in dist if d > 0) formatted = [f"{d:.4f}" for d in dist] print(f" p={p:.2f}: [{', '.join(formatted)}] ({nonzero} tokens)") print("\n--- 10. Reparameterization Trick ---") mu_val, log_var_val = 2.0, 0.5 print(f" VAE encoder output: mu={mu_val}, log_var={log_var_val}") z_samples = [] for trial in range(5): z, eps, dz_dmu, dz_dlogvar = vae_forward_demo(mu_val, log_var_val) z_samples.append(z) print(f" Trial {trial+1}: z={z:.4f}, eps={eps:.4f}, " f"dz/dmu={dz_dmu:.4f}, dz/dlog_var={dz_dlogvar:.4f}") print(f" Mean of z samples: {sum(z_samples)/len(z_samples):.4f} " f"(expected ~{mu_val})") print(" Gradients exist because z = mu + sigma * epsilon is differentiable.") print("\n Verifying reparameterization matches direct sampling:") sigma_val = math.exp(0.5 * log_var_val) direct_samples = [sample_normal_box_muller(mu_val, sigma_val) for _ in range(10000)] reparam_samples = [reparam_sample(mu_val, sigma_val)[0] for _ in range(10000)] d_mean = sum(direct_samples) / len(direct_samples) r_mean = sum(reparam_samples) / len(reparam_samples) d_std = (sum((x - d_mean)**2 for x in direct_samples) / len(direct_samples)) ** 0.5 r_std = (sum((x - r_mean)**2 for x in reparam_samples) / len(reparam_samples)) ** 0.5 print(f" Direct: mean={d_mean:.4f}, std={d_std:.4f}") print(f" Reparam: mean={r_mean:.4f}, std={r_std:.4f}") print("\n--- 11. Gumbel-Softmax ---") probs = [0.5, 0.3, 0.15, 0.05] log_probs = [math.log(p) for p in probs] labels = ["cat", "dog", "bird", "fish"] print(f" True probs: {probs}") print("\n Gumbel-Max (exact categorical) verification:") counts = [0] * len(probs) n_gumbel = 10000 for _ in range(n_gumbel): idx = gumbel_max_sample(log_probs) counts[idx] += 1 empirical = [c / n_gumbel for c in counts] print(f" Empirical: [{', '.join(f'{p:.4f}' for p in empirical)}]") print(f" True: [{', '.join(f'{p:.4f}' for p in probs)}]") print("\n Gumbel-Softmax at different temperatures:") for tau in [0.1, 0.5, 1.0, 5.0]: soft = gumbel_softmax_sample(log_probs, tau) formatted = [f"{s:.4f}" for s in soft] max_idx = soft.index(max(soft)) print(f" tau={tau:.1f}: [{', '.join(formatted)}] -> {labels[max_idx]}") print("\n Straight-through estimator:") hard, soft = gumbel_softmax_straight_through(log_probs, temperature=0.5) print(f" Hard (forward): {hard}") print(f" Soft (backward): [{', '.join(f'{s:.4f}' for s in soft)}]") print("\n--- 12. Stratified Sampling ---") def test_fn(x): return math.sin(math.pi * x) print(" Comparing standard vs stratified Monte Carlo:") print(f" Function: sin(pi*x) on [0,1], true integral = 2/pi = {2/math.pi:.6f}") for n in [10, 50, 100]: std_var, strat_var = compare_sampling_variance(test_fn, n) ratio = std_var / strat_var if strat_var > 0 else float('inf') print(f" N={n:3d}: standard_var={std_var:.8f}, " f"stratified_var={strat_var:.8f}, ratio={ratio:.2f}x") print("\n--- 13. Text Generation Demo ---") gen_vocab = ["the", "cat", "sat", "on", "mat", "a", "dog", "ran", "big", "red"] gen_logits = [3.0, 2.5, 2.0, 1.8, 1.5, 1.0, 0.5, 0.0, -0.5, -1.0] print(f" Vocab: {gen_vocab}") print(f" Logits: {gen_logits}") print() methods = [ ("greedy", {}), ("temperature", {"temperature": 0.5}), ("temperature", {"temperature": 1.0}), ("temperature", {"temperature": 2.0}), ("top_k", {"k": 3}), ("top_p", {"p": 0.8}), ] for method_name, params in methods: label = method_name if params: label += f"({', '.join(f'{k}={v}' for k, v in params.items())})" sequences = [] for run in range(3): seq = text_generation_demo(gen_vocab, gen_logits, length=8, method=method_name, **params) sequences.append(seq) print(f" {label}:") for i, seq in enumerate(sequences): print(f" Run {i+1}: {seq}") unique = len(set(sequences)) print(f" Unique sequences: {unique}/3") print() print("\n--- 14. Visualizations ---") try: import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt fig, axes = plt.subplots(3, 3, figsize=(18, 16)) ax = axes[0][0] ax.set_title("Inverse CDF: Exponential Samples") exp_samples = [sample_exponential_inverse_cdf(1.0) for _ in range(10000)] ax.hist(exp_samples, bins=60, density=True, alpha=0.7, color="#4a90d9", label="Samples") xs_exp = [i * 0.05 for i in range(160)] ys_exp = [math.exp(-x) for x in xs_exp] ax.plot(xs_exp, ys_exp, "r-", linewidth=2, label="True PDF") ax.set_xlabel("x") ax.set_ylabel("Density") ax.legend() ax = axes[0][1] ax.set_title("Rejection Sampling: Truncated Normal") rej_samples, _ = truncated_normal_demo(0, 1, -1, 2, n=5000) ax.hist(rej_samples, bins=50, density=True, alpha=0.7, color="#4a90d9", label="Samples") xs_tn = [-1 + 3 * i / 200 for i in range(201)] ys_tn = [normal_pdf(x, 0, 1) for x in xs_tn] area = sum(ys_tn) * 3 / 200 ys_tn_norm = [y / area for y in ys_tn] ax.plot(xs_tn, ys_tn_norm, "r-", linewidth=2, label="True PDF (normalized)") ax.set_xlabel("x") ax.set_ylabel("Density") ax.legend() ax = axes[0][2] ax.set_title("Monte Carlo: Estimating Pi") n_mc_vis = 5000 mc_x = [random.uniform(-1, 1) for _ in range(n_mc_vis)] mc_y = [random.uniform(-1, 1) for _ in range(n_mc_vis)] inside_x = [mc_x[i] for i in range(n_mc_vis) if mc_x[i]**2 + mc_y[i]**2 <= 1] inside_y = [mc_y[i] for i in range(n_mc_vis) if mc_x[i]**2 + mc_y[i]**2 <= 1] outside_x = [mc_x[i] for i in range(n_mc_vis) if mc_x[i]**2 + mc_y[i]**2 > 1] outside_y = [mc_y[i] for i in range(n_mc_vis) if mc_x[i]**2 + mc_y[i]**2 > 1] ax.scatter(inside_x, inside_y, s=1, c="#4a90d9", alpha=0.5) ax.scatter(outside_x, outside_y, s=1, c="#d94a4a", alpha=0.5) theta = [2 * math.pi * i / 200 for i in range(201)] circle_x = [math.cos(t) for t in theta] circle_y = [math.sin(t) for t in theta] ax.plot(circle_x, circle_y, "k-", linewidth=1.5) ax.set_aspect("equal") pi_est = 4 * len(inside_x) / n_mc_vis ax.set_xlabel(f"pi ~ {pi_est:.4f}") ax = axes[1][0] ax.set_title("MCMC: Bimodal Distribution") mcmc_samples, _ = metropolis_hastings( bimodal_log_pdf, x0=0.0, n_samples=20000, burn_in=5000, proposal_std=2.0 ) ax.hist(mcmc_samples, bins=80, density=True, alpha=0.7, color="#4a90d9", label="MCMC samples") xs_bm = [-8 + 16 * i / 400 for i in range(401)] ys_bm = [math.exp(bimodal_log_pdf(x)) for x in xs_bm] area_bm = sum(ys_bm) * 16 / 400 ys_bm_norm = [y / area_bm for y in ys_bm] ax.plot(xs_bm, ys_bm_norm, "r-", linewidth=2, label="True density") ax.set_xlabel("x") ax.set_ylabel("Density") ax.legend() ax = axes[1][1] ax.set_title("Gibbs Sampling: 2D Gaussian (rho=0.8)") gibbs_vis = gibbs_sampling_2d(0.8, n_samples=3000, burn_in=500) gvx = [s[0] for s in gibbs_vis] gvy = [s[1] for s in gibbs_vis] ax.scatter(gvx, gvy, s=2, alpha=0.3, c="#4a90d9") ax.plot(gvx[:100], gvy[:100], "r-", alpha=0.3, linewidth=0.5) ax.set_xlabel("x") ax.set_ylabel("y") ax.set_aspect("equal") ax = axes[1][2] ax.set_title("Temperature Scaling") temps = [0.1, 0.5, 1.0, 2.0, 5.0] bar_width = 0.15 positions = list(range(len(token_logits))) for t_idx, temp in enumerate(temps): dist = temperature_distribution(token_logits, temp) offset = (t_idx - 2) * bar_width bars = [pos + offset for pos in positions] ax.bar(bars, dist, bar_width, label=f"T={temp}", alpha=0.8) ax.set_xticks(positions) ax.set_xticklabels(vocab, rotation=45) ax.set_ylabel("Probability") ax.legend(fontsize=8) ax = axes[2][0] ax.set_title("Top-k vs Top-p Distributions") k_dist = top_k_distribution(token_logits, k=3) p_dist = top_p_distribution(token_logits, p=0.9) full_dist = softmax(token_logits) x_pos = list(range(len(token_logits))) w = 0.25 ax.bar([x - w for x in x_pos], full_dist, w, label="Full", alpha=0.8, color="#aaaaaa") ax.bar(x_pos, k_dist, w, label="Top-3", alpha=0.8, color="#4a90d9") ax.bar([x + w for x in x_pos], p_dist, w, label="Top-p=0.9", alpha=0.8, color="#d94a4a") ax.set_xticks(x_pos) ax.set_xticklabels(vocab, rotation=45) ax.set_ylabel("Probability") ax.legend(fontsize=8) ax = axes[2][1] ax.set_title("Gumbel-Softmax: Temperature Effect") taus = [0.1, 0.5, 1.0, 5.0] g_log_probs = [math.log(p) for p in [0.5, 0.3, 0.15, 0.05]] n_trials_vis = 500 for tau in taus: max_vals = [] for _ in range(n_trials_vis): soft = gumbel_softmax_sample(g_log_probs, tau) max_vals.append(max(soft)) ax.hist(max_vals, bins=30, alpha=0.5, label=f"tau={tau}", density=True) ax.set_xlabel("Max component value") ax.set_ylabel("Density") ax.legend(fontsize=8) ax = axes[2][2] ax.set_title("Stratified vs Standard Sampling") n_strat_vis = 20 standard_pts = sorted([random.random() for _ in range(n_strat_vis)]) stratified_pts = sorted(stratified_sample_1d(n_strat_vis)) ax.scatter(standard_pts, [1] * n_strat_vis, s=30, c="#d94a4a", label="Standard", zorder=3) ax.scatter(stratified_pts, [0] * n_strat_vis, s=30, c="#4a90d9", label="Stratified", zorder=3) for i in range(n_strat_vis + 1): ax.axvline(i / n_strat_vis, color="#cccccc", linewidth=0.5, linestyle="--") ax.set_yticks([0, 1]) ax.set_yticklabels(["Stratified", "Standard"]) ax.set_xlabel("Sample value") ax.legend() ax.set_ylim(-0.5, 1.5) plt.tight_layout() plt.savefig("sampling_methods.png", dpi=150) print(" Saved: sampling_methods.png") plt.close() except ImportError: print(" matplotlib not available, skipping visualization.") print("\n" + "=" * 65) print("All sampling methods complete.") print("=" * 65)