{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "dc5d5825",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-02-16T16:15:08.719062Z",
     "iopub.status.busy": "2026-02-16T16:15:08.718752Z",
     "iopub.status.idle": "2026-02-16T16:15:10.156553Z",
     "shell.execute_reply": "2026-02-16T16:15:10.155128Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OptimizR MCMC Module Loaded!\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from scipy import stats\n",
    "from optimizr import mcmc_sample\n",
    "\n",
    "np.random.seed(42)\n",
    "print(\"OptimizR MCMC Module Loaded!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ddfbd617",
   "metadata": {},
   "source": [
    "## Example 1: Inferring Parameters of a Normal Distribution\n",
    "\n",
    "Given observed data $\\{x_1, \\ldots, x_n\\}$, infer $\\mu$ and $\\sigma$.\n",
    "\n",
    "### Likelihood\n",
    "$$L(\\mu, \\sigma | \\mathbf{x}) = \\prod_{i=1}^n \\frac{1}{\\sqrt{2\\pi\\sigma^2}} \\exp\\left(-\\frac{(x_i - \\mu)^2}{2\\sigma^2}\\right)$$\n",
    "\n",
    "### Log-Likelihood\n",
    "$$\\log L(\\mu, \\sigma | \\mathbf{x}) = -\\frac{n}{2}\\log(2\\pi) - n\\log(\\sigma) - \\frac{1}{2\\sigma^2}\\sum_{i=1}^n (x_i - \\mu)^2$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "f929c18e",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-02-16T16:15:10.160126Z",
     "iopub.status.busy": "2026-02-16T16:15:10.159742Z",
     "iopub.status.idle": "2026-02-16T16:15:10.443829Z",
     "shell.execute_reply": "2026-02-16T16:15:10.442725Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True parameters: μ=5.0, σ=2.0\n",
      "Sample mean: 4.792\n",
      "Sample std: 1.807\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 49000 characters]",
      "text/plain": [
       "<Figure size 1000x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Generate synthetic data\n",
    "true_mu = 5.0\n",
    "true_sigma = 2.0\n",
    "n_obs = 100\n",
    "\n",
    "observed_data = np.random.normal(true_mu, true_sigma, n_obs)\n",
    "\n",
    "print(f\"True parameters: μ={true_mu}, σ={true_sigma}\")\n",
    "print(f\"Sample mean: {observed_data.mean():.3f}\")\n",
    "print(f\"Sample std: {observed_data.std():.3f}\")\n",
    "\n",
    "# Plot data\n",
    "plt.figure(figsize=(10, 5))\n",
    "plt.hist(observed_data, bins=20, density=True, alpha=0.6, color='skyblue', edgecolor='black')\n",
    "x_range = np.linspace(observed_data.min(), observed_data.max(), 100)\n",
    "plt.plot(x_range, stats.norm.pdf(x_range, true_mu, true_sigma), \n",
    "         'r-', linewidth=2, label=f'True: N({true_mu}, {true_sigma}²)')\n",
    "plt.xlabel('Value', fontsize=12)\n",
    "plt.ylabel('Density', fontsize=12)\n",
    "plt.title('Observed Data Distribution', fontsize=14, fontweight='bold')\n",
    "plt.legend()\n",
    "plt.grid(alpha=0.3)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5626ba97",
   "metadata": {},
   "source": [
    "### Define Log-Likelihood Function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "37ac93a1",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-02-16T16:15:10.447063Z",
     "iopub.status.busy": "2026-02-16T16:15:10.446784Z",
     "iopub.status.idle": "2026-02-16T16:15:10.453064Z",
     "shell.execute_reply": "2026-02-16T16:15:10.451933Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Log-likelihood at true params: -202.57\n"
     ]
    }
   ],
   "source": [
    "def log_likelihood_normal(params, data):\n",
    "    \"\"\"\n",
    "    Log-likelihood for Normal(μ, σ²) given data.\n",
    "    \n",
    "    Args:\n",
    "        params: [μ, σ]\n",
    "        data: observed data points\n",
    "    \"\"\"\n",
    "    mu, sigma = params\n",
    "    \n",
    "    # Ensure sigma is positive\n",
    "    if sigma <= 0:\n",
    "        return -np.inf\n",
    "    \n",
    "    n = len(data)\n",
    "    residuals = (data - mu) / sigma\n",
    "    \n",
    "    log_lik = -0.5 * n * np.log(2 * np.pi)\n",
    "    log_lik -= n * np.log(sigma)\n",
    "    log_lik -= 0.5 * np.sum(residuals**2)\n",
    "    \n",
    "    return log_lik\n",
    "\n",
    "# Test the function\n",
    "test_params = [5.0, 2.0]\n",
    "print(f\"Log-likelihood at true params: {log_likelihood_normal(test_params, observed_data):.2f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a36ddef7",
   "metadata": {},
   "source": [
    "### Run MCMC Sampling"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "60e64783",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-02-16T16:15:10.456697Z",
     "iopub.status.busy": "2026-02-16T16:15:10.456293Z",
     "iopub.status.idle": "2026-02-16T16:15:10.801889Z",
     "shell.execute_reply": "2026-02-16T16:15:10.800458Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Running MCMC sampling...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Samples generated successfully\n",
      "Generated 20000 samples after burn-in\n",
      "\n",
      "Posterior estimates:\n",
      "μ: 4.792 ± 0.187\n",
      "σ: 1.840 ± 0.136\n",
      "\n",
      "True values: μ=5.0, σ=2.0\n"
     ]
    }
   ],
   "source": [
    "# MCMC parameters\n",
    "initial_params = [0.0, 1.0]  # Start far from true values\n",
    "param_bounds = [(-10, 10), (0.1, 10)]  # μ ∈ [-10, 10], σ ∈ [0.1, 10]\n",
    "proposal_std = 0.3  # Proposal step sizes\n",
    "n_samples = 20000\n",
    "burn_in = 2000\n",
    "\n",
    "print(\"Running MCMC sampling...\")\n",
    "samples = mcmc_sample(\n",
    "    log_likelihood_fn=lambda params: log_likelihood_normal(params, observed_data),\n",
    "    initial_params=initial_params,\n",
    "    param_bounds=param_bounds,\n",
    "    proposal_std=proposal_std,\n",
    "    n_samples=n_samples,\n",
    "    burn_in=burn_in\n",
    ")\n",
    "\n",
    "print(f\"\\nSamples generated successfully\")\n",
    "print(f\"Generated {len(samples)} samples after burn-in\")\n",
    "print(f\"\\nPosterior estimates:\")\n",
    "print(f\"μ: {samples[:, 0].mean():.3f} ± {samples[:, 0].std():.3f}\")\n",
    "print(f\"σ: {samples[:, 1].mean():.3f} ± {samples[:, 1].std():.3f}\")\n",
    "print(f\"\\nTrue values: μ={true_mu}, σ={true_sigma}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2306bc9b",
   "metadata": {},
   "source": [
    "## Visualize MCMC Results\n",
    "\n",
    "### Trace Plots - Check Convergence"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "856b7ca8",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-02-16T16:15:10.805389Z",
     "iopub.status.busy": "2026-02-16T16:15:10.805001Z",
     "iopub.status.idle": "2026-02-16T16:15:12.016617Z",
     "shell.execute_reply": "2026-02-16T16:15:12.015169Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 175156 characters]",
      "text/plain": [
       "<Figure size 1400x800 with 4 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axes = plt.subplots(2, 2, figsize=(14, 8))\n",
    "\n",
    "# Trace plots\n",
    "axes[0, 0].plot(samples[:, 0], linewidth=0.5, alpha=0.7)\n",
    "axes[0, 0].axhline(true_mu, color='red', linestyle='--', linewidth=2, label='True μ')\n",
    "axes[0, 0].set_xlabel('Sample', fontsize=11)\n",
    "axes[0, 0].set_ylabel('μ', fontsize=11)\n",
    "axes[0, 0].set_title('Trace Plot: μ', fontsize=13, fontweight='bold')\n",
    "axes[0, 0].legend()\n",
    "axes[0, 0].grid(alpha=0.3)\n",
    "\n",
    "axes[0, 1].plot(samples[:, 1], linewidth=0.5, alpha=0.7, color='orange')\n",
    "axes[0, 1].axhline(true_sigma, color='red', linestyle='--', linewidth=2, label='True σ')\n",
    "axes[0, 1].set_xlabel('Sample', fontsize=11)\n",
    "axes[0, 1].set_ylabel('σ', fontsize=11)\n",
    "axes[0, 1].set_title('Trace Plot: σ', fontsize=13, fontweight='bold')\n",
    "axes[0, 1].legend()\n",
    "axes[0, 1].grid(alpha=0.3)\n",
    "\n",
    "# Posterior distributions\n",
    "axes[1, 0].hist(samples[:, 0], bins=50, density=True, alpha=0.6, color='skyblue', edgecolor='black')\n",
    "axes[1, 0].axvline(true_mu, color='red', linestyle='--', linewidth=2, label='True μ')\n",
    "axes[1, 0].axvline(samples[:, 0].mean(), color='green', linestyle='-', linewidth=2, label='Posterior mean')\n",
    "axes[1, 0].set_xlabel('μ', fontsize=11)\n",
    "axes[1, 0].set_ylabel('Density', fontsize=11)\n",
    "axes[1, 0].set_title('Posterior Distribution: μ', fontsize=13, fontweight='bold')\n",
    "axes[1, 0].legend()\n",
    "axes[1, 0].grid(alpha=0.3)\n",
    "\n",
    "axes[1, 1].hist(samples[:, 1], bins=50, density=True, alpha=0.6, color='orange', edgecolor='black')\n",
    "axes[1, 1].axvline(true_sigma, color='red', linestyle='--', linewidth=2, label='True σ')\n",
    "axes[1, 1].axvline(samples[:, 1].mean(), color='green', linestyle='-', linewidth=2, label='Posterior mean')\n",
    "axes[1, 1].set_xlabel('σ', fontsize=11)\n",
    "axes[1, 1].set_ylabel('Density', fontsize=11)\n",
    "axes[1, 1].set_title('Posterior Distribution: σ', fontsize=13, fontweight='bold')\n",
    "axes[1, 1].legend()\n",
    "axes[1, 1].grid(alpha=0.3)\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9b20c718",
   "metadata": {},
   "source": [
    "### Joint Posterior Distribution"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "ad2eebc7",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-02-16T16:15:12.020057Z",
     "iopub.status.busy": "2026-02-16T16:15:12.019756Z",
     "iopub.status.idle": "2026-02-16T16:15:12.343522Z",
     "shell.execute_reply": "2026-02-16T16:15:12.342502Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 52224 characters]",
      "text/plain": [
       "<Figure size 1000x800 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.figure(figsize=(10, 8))\n",
    "\n",
    "# 2D histogram\n",
    "plt.hist2d(samples[:, 0], samples[:, 1], bins=50, cmap='Blues')\n",
    "plt.colorbar(label='Sample Density')\n",
    "\n",
    "# Mark true values\n",
    "plt.scatter([true_mu], [true_sigma], c='red', s=200, marker='*', \n",
    "           edgecolors='black', linewidths=2, label='True values', zorder=5)\n",
    "\n",
    "# Mark posterior mean\n",
    "plt.scatter([samples[:, 0].mean()], [samples[:, 1].mean()], \n",
    "           c='green', s=200, marker='o', edgecolors='black', \n",
    "           linewidths=2, label='Posterior mean', zorder=5)\n",
    "\n",
    "plt.xlabel('μ', fontsize=12)\n",
    "plt.ylabel('σ', fontsize=12)\n",
    "plt.title('Joint Posterior Distribution', fontsize=14, fontweight='bold')\n",
    "plt.legend(fontsize=11)\n",
    "plt.grid(alpha=0.3)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "049df073",
   "metadata": {},
   "source": [
    "## Example 2: Logistic Regression with MCMC\n",
    "\n",
    "Bayesian inference for binary classification.\n",
    "\n",
    "### Model\n",
    "$$P(y=1 | \\mathbf{x}, \\boldsymbol{\\beta}) = \\frac{1}{1 + \\exp(-\\boldsymbol{\\beta}^T \\mathbf{x})}$$\n",
    "\n",
    "### Log-Likelihood\n",
    "$$\\log L(\\boldsymbol{\\beta}) = \\sum_{i=1}^n \\left[y_i \\log p_i + (1-y_i) \\log(1-p_i)\\right]$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "072639a3",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-02-16T16:15:12.346960Z",
     "iopub.status.busy": "2026-02-16T16:15:12.346678Z",
     "iopub.status.idle": "2026-02-16T16:15:13.562088Z",
     "shell.execute_reply": "2026-02-16T16:15:13.560700Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Features shape: (200, 3)\n",
      "Class distribution: [100 100]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 55936 characters]",
      "text/plain": [
       "<Figure size 800x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Generate synthetic classification data\n",
    "from sklearn.datasets import make_classification\n",
    "\n",
    "X, y = make_classification(n_samples=200, n_features=2, n_redundant=0,\n",
    "                          n_informative=2, random_state=42, n_clusters_per_class=1)\n",
    "\n",
    "# Add intercept\n",
    "X_with_intercept = np.column_stack([np.ones(len(X)), X])\n",
    "\n",
    "print(f\"Features shape: {X_with_intercept.shape}\")\n",
    "print(f\"Class distribution: {np.bincount(y)}\")\n",
    "\n",
    "# Visualize data\n",
    "plt.figure(figsize=(8, 6))\n",
    "plt.scatter(X[y == 0, 0], X[y == 0, 1], c='blue', label='Class 0', alpha=0.6, s=50)\n",
    "plt.scatter(X[y == 1, 0], X[y == 1, 1], c='red', label='Class 1', alpha=0.6, s=50)\n",
    "plt.xlabel('Feature 1', fontsize=12)\n",
    "plt.ylabel('Feature 2', fontsize=12)\n",
    "plt.title('Binary Classification Data', fontsize=14, fontweight='bold')\n",
    "plt.legend()\n",
    "plt.grid(alpha=0.3)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "6163cb52",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-02-16T16:15:13.565204Z",
     "iopub.status.busy": "2026-02-16T16:15:13.564814Z",
     "iopub.status.idle": "2026-02-16T16:15:14.500460Z",
     "shell.execute_reply": "2026-02-16T16:15:14.499241Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Running MCMC for logistic regression...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Posterior estimates:\n",
      "β₀ (intercept): 0.000 ± 0.000\n",
      "β₁: 0.000 ± 0.000\n",
      "β₂: 0.000 ± 0.000\n"
     ]
    }
   ],
   "source": [
    "def log_likelihood_logistic(beta, X, y):\n",
    "    \"\"\"\n",
    "    Log-likelihood for logistic regression.\n",
    "    \"\"\"\n",
    "    z = X @ beta\n",
    "    # Numerically stable sigmoid\n",
    "    p = 1 / (1 + np.exp(-np.clip(z, -500, 500)))\n",
    "    p = np.clip(p, 1e-10, 1 - 1e-10)  # Avoid log(0)\n",
    "    \n",
    "    log_lik = np.sum(y * np.log(p) + (1 - y) * np.log(1 - p))\n",
    "    \n",
    "    # Add weak prior: beta ~ N(0, 10²)\n",
    "    log_prior = -0.5 * np.sum(beta**2) / 100\n",
    "    \n",
    "    return log_lik + log_prior\n",
    "\n",
    "# Prepare data tuple\n",
    "logistic_data = (X_with_intercept, y)\n",
    "\n",
    "# MCMC for logistic regression\n",
    "initial_beta = np.zeros(3)  # [intercept, coef1, coef2]\n",
    "beta_bounds = [(-10, 10)] * 3\n",
    "beta_proposal_std = 0.1\n",
    "\n",
    "print(\"Running MCMC for logistic regression...\")\n",
    "beta_samples = mcmc_sample(\n",
    "    log_likelihood_fn=lambda params: log_likelihood_logistic(params, X_with_intercept, y),\n",
    "    initial_params=initial_beta,\n",
    "    param_bounds=beta_bounds,\n",
    "    proposal_std=beta_proposal_std,\n",
    "    n_samples=15000,\n",
    "    burn_in=1500\n",
    ")\n",
    "\n",
    "print(f\"\\nPosterior estimates:\")\n",
    "print(f\"β₀ (intercept): {beta_samples[:, 0].mean():.3f} ± {beta_samples[:, 0].std():.3f}\")\n",
    "print(f\"β₁: {beta_samples[:, 1].mean():.3f} ± {beta_samples[:, 1].std():.3f}\")\n",
    "print(f\"β₂: {beta_samples[:, 2].mean():.3f} ± {beta_samples[:, 2].std():.3f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3cc9ff7c",
   "metadata": {},
   "source": [
    "### Visualize Decision Boundary with Uncertainty"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "2eb5d0d5",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-02-16T16:15:14.503549Z",
     "iopub.status.busy": "2026-02-16T16:15:14.503254Z",
     "iopub.status.idle": "2026-02-16T16:15:14.807722Z",
     "shell.execute_reply": "2026-02-16T16:15:14.806187Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 67968 characters]",
      "text/plain": [
       "<Figure size 1000x800 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Plot decision boundaries from posterior samples\n",
    "plt.figure(figsize=(10, 8))\n",
    "\n",
    "# Plot data\n",
    "plt.scatter(X[y == 0, 0], X[y == 0, 1], c='blue', label='Class 0', alpha=0.6, s=50, zorder=3)\n",
    "plt.scatter(X[y == 1, 0], X[y == 1, 1], c='red', label='Class 1', alpha=0.6, s=50, zorder=3)\n",
    "\n",
    "# Create grid\n",
    "x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1\n",
    "x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1\n",
    "\n",
    "# Plot decision boundaries from random posterior samples\n",
    "n_boundary_samples = 100\n",
    "indices = np.random.choice(len(beta_samples), n_boundary_samples, replace=False)\n",
    "\n",
    "for idx in indices:\n",
    "    beta = beta_samples[idx]\n",
    "    # Decision boundary: β₀ + β₁x₁ + β₂x₂ = 0\n",
    "    # => x₂ = -(β₀ + β₁x₁) / β₂\n",
    "    if abs(beta[2]) > 0.01:  # Avoid division by zero\n",
    "        x1_line = np.array([x1_min, x1_max])\n",
    "        x2_line = -(beta[0] + beta[1] * x1_line) / beta[2]\n",
    "        plt.plot(x1_line, x2_line, 'gray', alpha=0.02, linewidth=0.5, zorder=1)\n",
    "\n",
    "# Plot mean decision boundary\n",
    "beta_mean = beta_samples.mean(axis=0)\n",
    "if abs(beta_mean[2]) > 0.01:\n",
    "    x1_line = np.array([x1_min, x1_max])\n",
    "    x2_line = -(beta_mean[0] + beta_mean[1] * x1_line) / beta_mean[2]\n",
    "    plt.plot(x1_line, x2_line, 'black', linewidth=3, label='Mean boundary', zorder=2)\n",
    "\n",
    "plt.xlim(x1_min, x1_max)\n",
    "plt.ylim(x2_min, x2_max)\n",
    "plt.xlabel('Feature 1', fontsize=12)\n",
    "plt.ylabel('Feature 2', fontsize=12)\n",
    "plt.title('Logistic Regression: Decision Boundary with Uncertainty', fontsize=14, fontweight='bold')\n",
    "plt.legend()\n",
    "plt.grid(alpha=0.3)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8c4facb3",
   "metadata": {},
   "source": [
    "## MCMC Diagnostics\n",
    "\n",
    "### Autocorrelation - Check Mixing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "0ab8cd82",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-02-16T16:15:14.810825Z",
     "iopub.status.busy": "2026-02-16T16:15:14.810478Z",
     "iopub.status.idle": "2026-02-16T16:15:15.622911Z",
     "shell.execute_reply": "2026-02-16T16:15:15.621002Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 34856 characters]",
      "text/plain": [
       "<Figure size 1400x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Low autocorrelation at large lags indicates good mixing!\n"
     ]
    }
   ],
   "source": [
    "from statsmodels.graphics.tsaplots import plot_acf\n",
    "\n",
    "fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n",
    "\n",
    "plot_acf(samples[:, 0], lags=100, ax=axes[0], alpha=0.05)\n",
    "axes[0].set_title('Autocorrelation: μ', fontsize=13, fontweight='bold')\n",
    "axes[0].set_xlabel('Lag', fontsize=11)\n",
    "\n",
    "plot_acf(samples[:, 1], lags=100, ax=axes[1], alpha=0.05)\n",
    "axes[1].set_title('Autocorrelation: σ', fontsize=13, fontweight='bold')\n",
    "axes[1].set_xlabel('Lag', fontsize=11)\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "print(\"Low autocorrelation at large lags indicates good mixing!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e2ed2ccd",
   "metadata": {},
   "source": [
    "## Key Takeaways\n",
    "\n",
    "1. **MCMC samples from complex distributions** using only likelihood evaluations\n",
    "2. **Metropolis-Hastings** uses proposal distribution and accept/reject steps\n",
    "3. **Burn-in period** allows chain to converge to target distribution\n",
    "4. **Diagnostics** (trace plots, autocorrelation) verify convergence and mixing\n",
    "5. **OptimizR provides 50-100x speedup** for likelihood evaluations\n",
    "6. **Bayesian inference** naturally quantifies uncertainty in parameters\n",
    "\n",
    "## Further Reading\n",
    "\n",
    "- Gelman, A., et al. (2013). \"Bayesian Data Analysis\" - Chapter 11\n",
    "- Brooks, S., et al. (2011). \"Handbook of Markov Chain Monte Carlo\"\n",
    "- Betancourt, M. (2017). \"A Conceptual Introduction to Hamiltonian Monte Carlo\""
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "rhftlab",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
