{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# MATH 4242 Python Lab 10: Symmetric eigenproblems and the SVD\n",
        "\n",
        "**Course sections:** 8.5 and 8.7\n",
        "\n",
        "This notebook accompanies LEAP Assignment 10. LEAP supplies your\n",
        "parameter values and grades your mathematical responses. Copy those\n",
        "values into the indicated cell below. Run a cell with **Shift+Enter**.\n",
        "\n",
        "The notebook is a computational scratchpad: your graded answers are\n",
        "entered in LEAP unless your instructor says otherwise.\n",
        "\n",
        "**Programming support:** Independent application. Use the patterns from earlier labs, consulting the hints when needed.\n",
        "\n",
        "A cell marked `TODO` is intentionally unfinished. Read the directions\n",
        "immediately above it before running it. Placeholders such as zero keep\n",
        "the notebook runnable, but their output is not an answer. If you are\n",
        "new to notebooks,\n",
        "complete `00_Getting_Started.ipynb` before this lab.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "\n",
        "np.set_printoptions(precision=6, suppress=True)\n",
        "print(\"NumPy version:\", np.__version__)\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 1. Use symmetry\n",
        "\n",
        "Copy `k` from LEAP. For a real symmetric matrix, use `np.linalg.eigh`:\n",
        "it exploits symmetry, returns real eigenvalues, and returns an\n",
        "orthonormal eigenvector matrix.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# Replace this example value with your LEAP value.\n",
        "k = 3\n",
        "\n",
        "A = np.array([[k + 1, 1],\n",
        "              [1, k + 1]], dtype=float)\n",
        "\n",
        "# TODO: compute the symmetric eigendecomposition.\n",
        "eigenvalues = np.zeros(2)  # TODO: use np.linalg.eigh(A)\n",
        "Q = np.eye(2)              # TODO: assign the eigenvector output\n",
        "\n",
        "print(\"eigenvalues:\", eigenvalues)\n",
        "print(\"Q =\\n\", Q)\n",
        "print(\"Q.T @ Q =\\n\", Q.T @ Q)\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 2. Compare eigenvalues and singular values\n",
        "\n",
        "The matrix is symmetric positive definite, so its singular values are\n",
        "its positive eigenvalues arranged from largest to smallest.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# TODO: compute an SVD and the best rank-one approximation.\n",
        "U = np.eye(2)                   # TODO: output from np.linalg.svd(A)\n",
        "singular_values = np.zeros(2)   # TODO: output from np.linalg.svd(A)\n",
        "Vt = np.eye(2)                  # TODO: output from np.linalg.svd(A)\n",
        "A_rank1 = np.zeros_like(A)       # TODO: first singular triplet\n",
        "\n",
        "print(\"singular values:\", singular_values)\n",
        "print(\"rank-one approximation =\\n\", A_rank1)\n",
        "print(\"Frobenius error:\", np.linalg.norm(A - A_rank1, ord=\"fro\"))\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 3. SVD image compression\n",
        "\n",
        "The following cell creates a small synthetic grayscale image, so no\n",
        "external image file is required. A rank-$j$ approximation retains the\n",
        "first $j$ singular triplets.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "\n",
        "grid = np.linspace(-1, 1, 80)\n",
        "X, Y = np.meshgrid(grid, grid)\n",
        "image = (\n",
        "    np.exp(-8*((X + 0.35)**2 + (Y + 0.2)**2))\n",
        "    + 0.75*np.exp(-15*((X - 0.3)**2 + (Y - 0.3)**2))\n",
        "    + 0.20*(X + 1)\n",
        ")\n",
        "\n",
        "Ui, si, Vti = np.linalg.svd(image, full_matrices=False)\n",
        "\n",
        "# TODO: build a rank-5 approximation from the first five singular\n",
        "# triplets. Hint: (Ui[:, :5] * si[:5]) @ Vti[:5, :]\n",
        "image_rank5 = np.zeros_like(image)  # TODO: use the hint above\n",
        "\n",
        "relative_error = (\n",
        "    np.linalg.norm(image - image_rank5, ord=\"fro\")\n",
        "    / np.linalg.norm(image, ord=\"fro\")\n",
        ")\n",
        "print(\"rank-5 relative Frobenius error:\", relative_error)\n",
        "\n",
        "fig, axes = plt.subplots(1, 2, figsize=(8, 3.5))\n",
        "axes[0].imshow(image, cmap=\"gray\")\n",
        "axes[0].set_title(\"original\")\n",
        "axes[1].imshow(image_rank5, cmap=\"gray\")\n",
        "axes[1].set_title(\"rank 5\")\n",
        "for ax in axes:\n",
        "    ax.axis(\"off\")\n",
        "plt.tight_layout()\n",
        "plt.show()\n"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "name": "python",
      "version": "3",
      "mimetype": "text/x-python",
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "pygments_lexer": "ipython3",
      "nbconvert_exporter": "python",
      "file_extension": ".py"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 4
}
