{"id":1022,"date":"2026-06-15T13:51:50","date_gmt":"2026-06-15T13:51:50","guid":{"rendered":"https:\/\/tensorzen.online\/?p=1022"},"modified":"2026-06-15T13:51:50","modified_gmt":"2026-06-15T13:51:50","slug":"the-determinant-volume-collapse-and-singular-matrices","status":"publish","type":"post","link":"https:\/\/tensorzen.online\/?p=1022","title":{"rendered":"The Determinant: Volume, Collapse, and Singular Matrices"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">This notebook builds the geometric and algebraic understanding of the determinant from first principles, culminating in why $\\det(A) = 0$ is the exact condition for singularity.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\" data-line=\"\">import numpy as np\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as mpatches\nfrom matplotlib.patches import Polygon\nfrom matplotlib.collections import PatchCollection\n\ndef style_ax(ax, title=&#039;&#039;, xlim=(-4, 4), ylim=(-4, 4)):\n    ax.set_xlim(*xlim); ax.set_ylim(*ylim)\n    ax.set_aspect(&#039;equal&#039;)\n    ax.axhline(0, color=&#039;gray&#039;, lw=0.7)\n    ax.axvline(0, color=&#039;gray&#039;, lw=0.7)\n    ax.set_title(title, fontsize=11, pad=8)\n    ax.set_xlabel(&#039;$x_1$&#039;); ax.set_ylabel(&#039;$x_2$&#039;)\n    ax.grid(True, linestyle=&#039;--&#039;, alpha=0.3)\n\ndef draw_arrow(ax, vec, origin=None, color=&#039;black&#039;, lw=2, label=None, offset=None):\n    o = np.zeros(2) if origin is None else np.array(origin)\n    ax.annotate(&#039;&#039;, xy=o + vec, xytext=o,\n                arrowprops=dict(arrowstyle=&#039;-&gt;&#039;, color=color, lw=lw))\n    if label:\n        pos = o + vec + (offset if offset is not None else np.array([0.1, 0.1]))\n        ax.text(*pos, label, color=color, fontsize=12, fontweight=&#039;bold&#039;)\n\ndef draw_parallelogram(ax, c1, c2, color=&#039;steelblue&#039;, alpha=0.25, edge_lw=1.5):\n    &quot;&quot;&quot;Draw the parallelogram spanned by two column vectors.&quot;&quot;&quot;\n    corners = np.array([[0,0], c1, c1+c2, c2, [0,0]])\n    ax.fill(corners[:,0], corners[:,1], color=color, alpha=alpha)\n    ax.plot(corners[:,0], corners[:,1], color=color, lw=edge_lw)<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">1. The Determinant as a Volume Scaling Factor<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The most fundamental interpretation of the determinant is geometric, not algebraic:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>$\\det(A)$ is the signed volume of the parallelepiped formed by the columns of $A$.<\/strong><\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">In 2D, this is the signed area of the parallelogram spanned by the two column vectors $a_1$ and $a_2$:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">$$A = \\begin{bmatrix} | &amp; | \\ a_1 &amp; a_2 \\ | &amp; | \\end{bmatrix}, \\qquad \\det(A) = \\text{signed area of parallelogram}(a_1, a_2)$$<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">More broadly, when $A$ is applied to any region $S$ of space:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">$$\\text{Vol}(A \\cdot S) = |\\det(A)| \\cdot \\text{Vol}(S)$$<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The determinant is a <strong>uniform volume scaling factor<\/strong> \u2014 it tells you by how much the transformation stretches or compresses all of space.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\" data-line=\"\"># Three matrices: stretch, compress, identity\nmatrices = [\n    (np.eye(2),                        &#039;Identity\\n$\\\\det = 1$&#039;),\n    (np.array([[2., 1.],[0.5, 1.5]]),   &#039;Non-singular\\n$\\\\det = {:.2f}$&#039;.format(np.linalg.det(np.array([[2.,1.],[0.5,1.5]])))),\n    (np.array([[0.5, 0.],[0.,  0.5]]), &#039;Uniform compression\\n$\\\\det = {:.2f}$&#039;.format(np.linalg.det(np.array([[0.5,0.],[0.,0.5]])))),\n]\n\n# Unit square\nunit_sq = np.array([[0,1,1,0,0],[0,0,1,1,0]], dtype=float)\n\nfig, axes = plt.subplots(1, 3, figsize=(15, 5))\nfor ax, (M, title) in zip(axes, matrices):\n    c1, c2 = M[:, 0], M[:, 1]\n    draw_parallelogram(ax, c1, c2, color=&#039;steelblue&#039;)\n    draw_arrow(ax, c1, color=&#039;crimson&#039;,    lw=2.5, label=&#039;$a_1$&#039;, offset=np.array([ 0.1, -0.3]))\n    draw_arrow(ax, c2, color=&#039;darkorange&#039;, lw=2.5, label=&#039;$a_2$&#039;, offset=np.array([ 0.1,  0.1]))\n    area = abs(np.linalg.det(M))\n    ax.text(0.05, 0.95, f&#039;Area = {area:.2f}&#039;, transform=ax.transAxes,\n            fontsize=10, va=&#039;top&#039;, bbox=dict(boxstyle=&#039;round&#039;, facecolor=&#039;wheat&#039;, alpha=0.5))\n    style_ax(ax, title=title, xlim=(-0.5, 3.5), ylim=(-0.5, 2.5))\n\nfig.suptitle(&#039;The determinant = signed area of the parallelogram spanned by the columns&#039;, fontsize=13)\nplt.tight_layout()\nplt.show()<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"322\" src=\"https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-14-1024x322.png\" alt=\"\" class=\"wp-image-1023\" srcset=\"https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-14-1024x322.png 1024w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-14-300x94.png 300w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-14-768x241.png 768w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-14-1536x483.png 1536w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-14.png 1804w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">2. The Sign Encodes Orientation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The determinant is <em>signed<\/em>. The sign indicates whether the transformation preserves or reverses orientation:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>$\\det(A)$<\/th><th>Volume effect<\/th><th>Orientation<\/th><\/tr><\/thead><tbody><tr><td>$&gt; 0$<\/td><td>Scales by $\\det(A)$<\/td><td>Preserved<\/td><\/tr><tr><td>$&lt; 0$<\/td><td>Scales by $<\/td><td>\\det(A)<\/td><\/tr><tr><td>$= 0$<\/td><td><strong>Destroyed<\/strong><\/td><td>Undefined<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">A negative determinant means the transformation includes a reflection \u2014 the columns have swapped their relative handedness compared to the standard basis.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\" data-line=\"\">A_pos = np.array([[ 2.0,  0.5], [0.5, 1.5]])   # det &gt; 0\nA_neg = np.array([[-2.0,  0.5], [0.5, 1.5]])   # det &lt; 0  (col 1 reflected)\n\nfig, axes = plt.subplots(1, 3, figsize=(15, 5))\n\n# Standard basis reference\nax = axes[0]\ndraw_parallelogram(ax, np.array([1.,0.]), np.array([0.,1.]), color=&#039;gray&#039;)\ndraw_arrow(ax, np.array([1.,0.]), color=&#039;crimson&#039;,    lw=2.5, label=&#039;$e_1$&#039;, offset=np.array([0.05,-0.25]))\ndraw_arrow(ax, np.array([0.,1.]), color=&#039;darkorange&#039;, lw=2.5, label=&#039;$e_2$&#039;, offset=np.array([-0.3, 0.1]))\nax.text(0.3, 0.3, &#039;CCW&#039;, fontsize=11, color=&#039;gray&#039;)\nstyle_ax(ax, title=&#039;Standard basis\\n$\\\\det(I) = +1$ (reference)&#039;, xlim=(-2.5,2.5), ylim=(-2.5,2.5))\n\n# Positive determinant\nax = axes[1]\nc1, c2 = A_pos[:,0], A_pos[:,1]\ndraw_parallelogram(ax, c1, c2, color=&#039;steelblue&#039;)\ndraw_arrow(ax, c1, color=&#039;crimson&#039;,    lw=2.5, label=&#039;$a_1$&#039;, offset=np.array([ 0.1,-0.3]))\ndraw_arrow(ax, c2, color=&#039;darkorange&#039;, lw=2.5, label=&#039;$a_2$&#039;, offset=np.array([ 0.1, 0.1]))\nd = np.linalg.det(A_pos)\nax.text(0.5, 0.5, &#039;CCW&#039;, fontsize=11, color=&#039;steelblue&#039;)\nstyle_ax(ax, title=f&#039;$\\\\det(A) = +{d:.2f}$\\nOrientation preserved&#039;, xlim=(-2.5,3.5), ylim=(-0.5,3.5))\n\n# Negative determinant\nax = axes[2]\nc1, c2 = A_neg[:,0], A_neg[:,1]\ndraw_parallelogram(ax, c1, c2, color=&#039;mediumorchid&#039;)\ndraw_arrow(ax, c1, color=&#039;crimson&#039;,    lw=2.5, label=&#039;$a_1$&#039;, offset=np.array([-0.6,-0.3]))\ndraw_arrow(ax, c2, color=&#039;darkorange&#039;, lw=2.5, label=&#039;$a_2$&#039;, offset=np.array([ 0.1, 0.1]))\nd = np.linalg.det(A_neg)\nax.text(-1.0, 0.5, &#039;CW&#039;, fontsize=11, color=&#039;mediumorchid&#039;)\nstyle_ax(ax, title=f&#039;$\\\\det(A) = {d:.2f}$\\nOrientation reversed (reflection)&#039;, xlim=(-3.5,2.5), ylim=(-0.5,3.5))\n\nfig.suptitle(&#039;Sign of the determinant encodes orientation&#039;, fontsize=13)\nplt.tight_layout()\nplt.show()<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"358\" src=\"https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-15-1024x358.png\" alt=\"\" class=\"wp-image-1024\" srcset=\"https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-15-1024x358.png 1024w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-15-300x105.png 300w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-15-768x268.png 768w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-15-1536x536.png 1536w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-15.png 1813w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">3. Why $\\det(A) = 0$ Means Singular: Dimension Collapse<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">When $\\det(A) = 0$, the transformation maps $\\mathbb{R}^n$ into a <strong>strictly lower-dimensional subspace<\/strong>. In 2D, the entire plane gets flattened onto a line (or a point). The parallelogram has zero area because the two column vectors are <strong>collinear<\/strong> \u2014 they both point along the same line.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">$$\\det(A) = 0 \\iff \\text{columns of } A \\text{ are linearly dependent} \\iff \\text{image of } A \\text{ has dimension} &lt; n$$<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"\" data-line=\"\"># Singular matrix: col2 = 2 * col1\nA_sing = np.array([[1.0, 2.0],\n                   [0.5, 1.0]])\n\n# A grid of input points\ngrid_pts = np.array([[x, y] for x in np.linspace(-2,2,9)\n                             for y in np.linspace(-2,2,9)]).T\ntransformed = A_sing @ grid_pts\n\nfig, axes = plt.subplots(1, 2, figsize=(13, 5))\n\n# Input space\nax = axes[0]\nax.scatter(grid_pts[0], grid_pts[1], color=&#039;steelblue&#039;, s=30, zorder=3)\ndraw_arrow(ax, A_sing[:,0], color=&#039;crimson&#039;,    lw=2.5, label=&#039;$a_1$&#039;, offset=np.array([ 0.1,-0.25]))\ndraw_arrow(ax, A_sing[:,1], color=&#039;darkorange&#039;, lw=2.5, label=&#039;$a_2 = 2a_1$&#039;, offset=np.array([0.1, 0.1]))\nstyle_ax(ax, title=&#039;Input: 2D grid of points&#039;, xlim=(-3,3), ylim=(-3,3))\n\n# Output space \u2014 everything collapses to a line\nax = axes[1]\nax.scatter(transformed[0], transformed[1], color=&#039;crimson&#039;, s=30, zorder=3, label=&#039;Transformed points&#039;)\n# draw the line that the space collapsed onto\nt = np.linspace(-4, 4, 100)\ncol1 = A_sing[:, 0]\nax.plot(col1[0]*t, col1[1]*t, &#039;k--&#039;, lw=1.5, alpha=0.5, label=&#039;Image: 1D line (col space)&#039;)\nax.legend(fontsize=9)\nstyle_ax(ax, title=f&#039;Output: entire 2D plane collapses to a 1D line\\n$\\\\det(A) = {np.linalg.det(A_sing):.4f} \\\\approx 0$&#039;,\n         xlim=(-4,4), ylim=(-3,3))\n\nfig.suptitle(&#039;Singular matrix: $a_2 = 2a_1$ \u2014 columns are linearly dependent&#039;, fontsize=13)\nplt.tight_layout()\nplt.show()\n\nprint(f&quot;A_sing =\\n{A_sing}&quot;)\nprint(f&quot;det(A_sing) = {np.linalg.det(A_sing):.6f}&quot;)\nprint(f&quot;rank(A_sing) = {np.linalg.matrix_rank(A_sing)}  (collapsed from 2 to 1)&quot;)<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"435\" src=\"https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-16-1024x435.png\" alt=\"\" class=\"wp-image-1025\" srcset=\"https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-16-1024x435.png 1024w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-16-300x127.png 300w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-16-768x326.png 768w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-16.png 1445w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<pre class=\"wp-block-preformatted\">A_sing =\n[[1.  2. ]\n [0.5 1. ]]\ndet(A_sing) = 0.000000\nrank(A_sing) = 1  (collapsed from 2 to 1)<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">4. The Mechanism: Alternating Multilinearity<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Why does linear dependence force $\\det = 0$? The answer lies in two algebraic properties that define the determinant:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Multilinear<\/strong> \u2014 scaling any one column scales the determinant by the same factor:<br>$$\\det([\\ldots, \\alpha a_j, \\ldots]) = \\alpha \\cdot \\det([\\ldots, a_j, \\ldots])$$<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Alternating<\/strong> \u2014 swapping any two columns negates the determinant, and a repeated column gives zero:<br>$$\\det([\\ldots, a_i, \\ldots, a_i, \\ldots]) = 0$$<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now suppose column $k$ is a linear combination of the others: $a_k = \\sum_{j \\neq k} c_j a_j$. By multilinearity, the determinant expands into a sum of terms \u2014 each one containing a repeated column. The alternating property kills each term. Therefore:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">$$a_k \\in \\text{span}(\\text{other columns}) \\implies \\det(A) = 0$$<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\" data-line=\"\"># Demonstrate: gradually make col2 more dependent on col1 and watch det \u2192 0\na1 = np.array([1.0, 0.5])\na2_original = np.array([0.5, 2.0])   # independent\na2_dependent = 2.0 * a1              # col2 = 2*col1\n\nalphas = np.linspace(0, 1, 200)\ndets = []\nfor alpha in alphas:\n    a2 = (1 - alpha) * a2_original + alpha * a2_dependent\n    M = np.column_stack([a1, a2])\n    dets.append(np.linalg.det(M))\n\nfig, axes = plt.subplots(1, 2, figsize=(13, 4))\n\n# Left: det vs interpolation\nax = axes[0]\nax.plot(alphas, dets, color=&#039;steelblue&#039;, lw=2)\nax.axhline(0, color=&#039;crimson&#039;, lw=1.5, linestyle=&#039;--&#039;, label=&#039;$\\\\det = 0$ (singular)&#039;)\nax.scatter([0, 1], [dets[0], dets[-1]], s=80, zorder=5,\n           color=[&#039;green&#039;, &#039;crimson&#039;], label=[&#039;Independent&#039;, &#039;Fully dependent&#039;])\nax.set_xlabel(&#039;$\\\\alpha$ (0 = independent, 1 = fully dependent)&#039;)\nax.set_ylabel(&#039;$\\\\det(A)$&#039;)\nax.set_title(&#039;Determinant as $a_2 \\\\to 2a_1$&#039;)\nax.legend(fontsize=9)\nax.grid(True, alpha=0.3)\n\n# Right: parallelogram shrinking\nax = axes[1]\nfor alpha in [0, 0.33, 0.66, 1.0]:\n    a2 = (1 - alpha) * a2_original + alpha * a2_dependent\n    corners = np.array([[0,0], a1, a1+a2, a2, [0,0]])\n    color = plt.cm.RdYlGn(1 - alpha)\n    ax.fill(corners[:,0], corners[:,1], color=color, alpha=0.35)\n    ax.plot(corners[:,0], corners[:,1], color=color, lw=1.5,\n            label=f&#039;$\\\\alpha={alpha:.2f}$, area={abs((1-alpha)*dets[0]):.2f}&#039;)\ndraw_arrow(ax, a1, color=&#039;black&#039;, lw=2, label=&#039;$a_1$ (fixed)&#039;, offset=np.array([0.05,-0.2]))\nax.legend(fontsize=8, loc=&#039;upper left&#039;)\nstyle_ax(ax, title=&#039;Parallelogram area collapsing to zero&#039;, xlim=(-0.3, 3.5), ylim=(-0.3, 2.5))\n\nplt.tight_layout()\nplt.show()<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"332\" src=\"https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-17-1024x332.png\" alt=\"\" class=\"wp-image-1026\" srcset=\"https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-17-1024x332.png 1024w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-17-300x97.png 300w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-17-768x249.png 768w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-17.png 1516w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">5. The Invertibility Connection<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A matrix $A$ is invertible iff $Ax = b$ has a unique solution for every $b$. This fails when $A$ collapses a dimension \u2014 either outputs are unreachable, or multiple inputs share the same output.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The <strong>product rule<\/strong> $\\det(AB) = \\det(A)\\det(B)$ makes the impossibility of inverting a singular matrix algebraically transparent:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">$$I = AA^{-1} \\implies 1 = \\det(I) = \\det(A)\\det(A^{-1})$$<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If $\\det(A) = 0$, this demands $0 \\cdot \\det(A^{-1}) = 1$ \u2014 a contradiction. No $A^{-1}$ can exist.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\" data-line=\"\"># Visualise the null space: Ax = 0 has non-trivial solutions when det = 0\nfig, axes = plt.subplots(1, 2, figsize=(13, 5))\n\nA_inv  = np.array([[2.0, 0.5], [0.5, 1.5]])   # invertible\nA_sing = np.array([[1.0, 2.0], [0.5, 1.0]])   # singular\n\ngrid_x = np.linspace(-3, 3, 300)\n\nfor ax, (M, title) in zip(axes, [\n    (A_inv,  f&#039;Non-singular ($\\\\det={np.linalg.det(A_inv):.2f}$)\\nNull space = {{0}} only&#039;),\n    (A_sing, f&#039;Singular ($\\\\det={np.linalg.det(A_sing):.4f}$)\\nNull space is a full line&#039;),\n]):\n    # Show column space\n    c1, c2 = M[:,0], M[:,1]\n    draw_parallelogram(ax, c1, c2, color=&#039;steelblue&#039;, alpha=0.15)\n    draw_arrow(ax, c1, color=&#039;crimson&#039;,    lw=2.5, label=&#039;$a_1$&#039;, offset=np.array([ 0.1,-0.3]))\n    draw_arrow(ax, c2, color=&#039;darkorange&#039;, lw=2.5, label=&#039;$a_2$&#039;, offset=np.array([ 0.1, 0.1]))\n\n    # Null space: solve Mx = 0\n    _, _, Vt = np.linalg.svd(M)\n    null_dim = M.shape[1] - np.linalg.matrix_rank(M)\n    if null_dim &gt; 0:\n        null_vec = Vt[-1]   # last row of Vt = null space basis vector\n        t = np.linspace(-3, 3, 100)\n        ax.plot(null_vec[0]*t, null_vec[1]*t, &#039;purple&#039;, lw=2.5,\n                label=f&#039;Null space: all $x$ with $Ax=0$&#039;)\n        # verify\n        print(f&quot;Null space vector: {null_vec.round(4)}&quot;)\n        print(f&quot;A @ null_vec = {(M @ null_vec).round(6)}  (\u2248 0 \u2713)\\n&quot;)\n    else:\n        ax.scatter([0],[0], color=&#039;purple&#039;, s=100, zorder=5, label=&#039;Null space: {0} only&#039;)\n        print(f&quot;Non-singular: only Ax=0 solution is x=0 \u2713\\n&quot;)\n\n    ax.legend(fontsize=9)\n    style_ax(ax, title=title, xlim=(-3,3), ylim=(-3,3))\n\nfig.suptitle(&#039;Null space of $A$: trivial (invertible) vs. a full line (singular)&#039;, fontsize=13)\nplt.tight_layout()\nplt.show()<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-preformatted\">Non-singular: only Ax=0 solution is x=0 \u2713<br><br>Null space vector: [ 0.8944 -0.4472]<br>A @ null_vec = [0. 0.]  (\u2248 0 \u2713)<\/pre>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"481\" src=\"https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-18-1024x481.png\" alt=\"\" class=\"wp-image-1027\" srcset=\"https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-18-1024x481.png 1024w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-18-300x141.png 300w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-18-768x361.png 768w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-18.png 1325w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">6. Determinant and Eigenvalues<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The determinant equals the <strong>product of all eigenvalues<\/strong>:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">$$\\det(A) = \\prod_{i=1}^{n} \\lambda_i$$<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This gives another route to the same conclusion: $\\det(A) = 0$ iff at least one $\\lambda_i = 0$. A zero eigenvalue means some nonzero vector $v$ satisfies $Av = 0 \\cdot v = 0$ \u2014 it gets annihilated by the transformation. That is exactly a nontrivial null space.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">And since singular values $\\sigma_i = \\sqrt{\\lambda_i(A^\\top A)}$, a zero singular value is the same condition expressed through the SVD lens.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"\" data-line=\"\">for M, name in [(A_inv, &#039;Non-singular&#039;), (A_sing, &#039;Singular&#039;)]:\n    eigvals = np.linalg.eigvals(M)\n    singular_vals = np.linalg.svd(M, compute_uv=False)\n    det_direct   = np.linalg.det(M)\n    det_via_eigs = np.prod(eigvals).real\n\n    print(f&quot;=== {name} ===&quot;)\n    print(f&quot;  Eigenvalues    : {np.round(eigvals.real, 4)}&quot;)\n    print(f&quot;  det (direct)   : {det_direct:.6f}&quot;)\n    print(f&quot;  det (\u220f \u03bb\u1d62)     : {det_via_eigs:.6f}  \u2713&quot;)\n    print(f&quot;  Singular values: {np.round(singular_vals, 6)}&quot;)\n    print(f&quot;  Rank           : {np.linalg.matrix_rank(M)}&quot;)\n    print()<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-preformatted\">=== Non-singular ===\n  Eigenvalues    : [2.309 1.191]\n  det (direct)   : 2.750000\n  det (\u220f \u03bb\u1d62)     : 2.750000  \u2713\n  Singular values: [2.309017 1.190983]\n  Rank           : 2\n\n=== Singular ===\n  Eigenvalues    : [2. 0.]\n  det (direct)   : 0.000000\n  det (\u220f \u03bb\u1d62)     : 0.000000  \u2713\n  Singular values: [2.5 0. ]\n  Rank           : 1<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">7. All Equivalent Conditions \u2014 Unified View<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Every condition below describes the same underlying geometric fact: <strong>the transformation collapses at least one dimension<\/strong>.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\" data-line=\"\">def audit(M, name):\n    eigvals = np.linalg.eigvals(M)\n    svals   = np.linalg.svd(M, compute_uv=False)\n    rank    = np.linalg.matrix_rank(M)\n    n       = M.shape[0]\n    det     = np.linalg.det(M)\n    null_trivial = rank == n   # Ax=0 has only x=0\n\n    print(f&quot;{&#039;=&#039;*55}&quot;)\n    print(f&quot; {name}&quot;)\n    print(f&quot;{&#039;=&#039;*55}&quot;)\n    print(f&quot;  det(A)                      : {det:.6f}&quot;)\n    print(f&quot;  Rank = n ({n})?              : {rank == n}  (rank = {rank})&quot;)\n    print(f&quot;  All eigenvalues \u2260 0?        : {all(abs(eigvals) &gt; 1e-10)}  {np.round(eigvals.real, 4)}&quot;)\n    print(f&quot;  All singular values &gt; 0?    : {all(svals &gt; 1e-10)}  {np.round(svals, 6)}&quot;)\n    print(f&quot;  Null space trivial ({{0}})?   : {null_trivial}&quot;)\n    try:\n        np.linalg.inv(M)\n        invertible = True\n    except np.linalg.LinAlgError:\n        invertible = False\n    print(f&quot;  Invertible?                 : {invertible}&quot;)\n    print()\n\naudit(A_inv,  &#039;Non-singular A&#039;)\naudit(A_sing, &#039;Singular A&#039;)<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-preformatted\">=======================================================<br> Non-singular A<br>=======================================================<br>  det(A)                      : 2.750000<br>  Rank = n (2)?              : True  (rank = 2)<br>  All eigenvalues \u2260 0?        : True  [2.309 1.191]<br>  All singular values > 0?    : True  [2.309017 1.190983]<br>  Null space trivial ({0})?   : True<br>  Invertible?                 : True<br><br>=======================================================<br> Singular A<br>=======================================================<br>  det(A)                      : 0.000000<br>  Rank = n (2)?              : False  (rank = 1)<br>  All eigenvalues \u2260 0?        : False  [2. 0.]<br>  All singular values > 0?    : False  [2.5 0. ]<br>  Null space trivial ({0})?   : False<br>  Invertible?                 : False<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Condition<\/th><th>What it says<\/th><\/tr><\/thead><tbody><tr><td>$\\det(A) = 0$<\/td><td>Transformation destroys volume \u2014 space is collapsed<\/td><\/tr><tr><td>Columns linearly dependent<\/td><td>At least one column lies in the span of the others<\/td><\/tr><tr><td>$\\text{rank}(A) &lt; n$<\/td><td>Image has lower dimension than domain<\/td><\/tr><tr><td>$Ax = 0$ has nontrivial solutions<\/td><td>Some nonzero vector is annihilated<\/td><\/tr><tr><td>$A$ is not invertible<\/td><td>The transformation cannot be undone<\/td><\/tr><tr><td>At least one eigenvalue $= 0$<\/td><td>Some direction gets completely flattened<\/td><\/tr><tr><td>At least one singular value $= 0$<\/td><td>Same condition via SVD<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">These are not separate facts. They are <strong>one geometric fact<\/strong> \u2014 the transformation collapses at least one dimension \u2014 expressed from different mathematical viewpoints.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The determinant is the single scalar that captures whether this collapse has occurred: nonzero means the full-dimensional structure of space is preserved and the transformation is reversible; zero means information has been irretrievably lost.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">8. What Does &#8220;Spanned By&#8221; Mean?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The phrase <strong>&#8220;spanned by&#8221;<\/strong> appeared throughout this notebook (e.g. <em>&#8220;the parallelogram spanned by $a_1$ and $a_2$&#8221;<\/em>). Here is what it means precisely.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Definition<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The <strong>span<\/strong> of vectors $v_1, v_2, \\ldots, v_k$ is the set of every vector reachable by scaling and adding them in any combination:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">$$\\text{span}(v_1, v_2, \\ldots, v_k) = \\left{ \\alpha_1 v_1 + \\alpha_2 v_2 + \\cdots + \\alpha_k v_k \\;\\middle|\\; \\alpha_i \\in \\mathbb{R} \\right}$$<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&#8220;The space spanned by $v_1$ and $v_2$&#8221; means this entire set \u2014 all possible linear combinations.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What spans look like geometrically<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Vectors<\/th><th>Span<\/th><\/tr><\/thead><tbody><tr><td>One nonzero vector in $\\mathbb{R}^2$<\/td><td>A line through the origin<\/td><\/tr><tr><td>Two independent vectors in $\\mathbb{R}^2$<\/td><td>The entire plane $\\mathbb{R}^2$<\/td><\/tr><tr><td>Two <strong>dependent<\/strong> vectors in $\\mathbb{R}^2$<\/td><td>Only a line (same as one vector)<\/td><\/tr><tr><td>Three independent vectors in $\\mathbb{R}^3$<\/td><td>All of $\\mathbb{R}^3$<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Key insight:<\/strong> linear dependence shrinks the span. If one vector is already a combination of the others, adding it contributes nothing new.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Connection to this notebook<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><em>&#8220;Parallelogram spanned by $a_1$ and $a_2$&#8221;<\/em> \u2014 the filled shape formed by all $\\alpha_1 a_1 + \\alpha_2 a_2$ with $0 \\leq \\alpha_1, \\alpha_2 \\leq 1$<\/li>\n\n\n\n<li><em>&#8220;Column space&#8221;<\/em> \u2014 another name for the span of the columns of $A$: all vectors $Ax$ can possibly reach<\/li>\n\n\n\n<li><em>&#8220;$a_k \\in \\text{span}(\\text{other columns})$&#8221;<\/em> \u2014 column $k$ is redundant; it adds no new direction, so the determinant is zero<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\" data-line=\"\">fig, axes = plt.subplots(1, 3, figsize=(16, 5))\n\n# \u2500\u2500 Case 1: span of one vector = a line \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nax = axes[0]\nv1 = np.array([1.5, 0.8])\nt = np.linspace(-2.5, 2.5, 200)\nspan_pts = np.outer(t, v1)                 # all \u03b1\u00b7v1\nax.plot(span_pts[:, 0], span_pts[:, 1], color=&#039;steelblue&#039;, lw=2, label=&#039;span($v_1$) = line&#039;)\n# sample a few representative points\nfor alpha in [-2, -1, 0, 1, 2]:\n    pt = alpha * v1\n    ax.scatter(*pt, color=&#039;steelblue&#039;, s=40, zorder=4)\ndraw_arrow(ax, v1, color=&#039;crimson&#039;, lw=2.5, label=&#039;$v_1$&#039;, offset=np.array([0.05, 0.12]))\nax.legend(fontsize=9)\nstyle_ax(ax, title=&#039;span($v_1$): one vector \u2192 a line\\nthrough the origin&#039;, xlim=(-3,3), ylim=(-2.5,2.5))\n\n# \u2500\u2500 Case 2: span of two independent vectors = the plane \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nax = axes[1]\nv1 = np.array([1.5, 0.5])\nv2 = np.array([0.3, 1.5])\n# shade the reachable region (the whole plane \u2014 approximate with a dense grid)\nalphas = np.linspace(-2, 2, 18)\npts = np.array([a1*v1 + a2*v2 for a1 in alphas for a2 in alphas])\nax.scatter(pts[:, 0], pts[:, 1], color=&#039;steelblue&#039;, s=15, alpha=0.4, label=&#039;sample of span($v_1, v_2$)&#039;)\ndraw_arrow(ax, v1, color=&#039;crimson&#039;,    lw=2.5, label=&#039;$v_1$&#039;, offset=np.array([ 0.05,-0.25]))\ndraw_arrow(ax, v2, color=&#039;darkorange&#039;, lw=2.5, label=&#039;$v_2$&#039;, offset=np.array([-0.4,  0.1]))\n# highlight the parallelogram (\u03b1 \u2208 [0,1])\ndraw_parallelogram(ax, v1, v2, color=&#039;steelblue&#039;, alpha=0.3)\nax.legend(fontsize=9)\nstyle_ax(ax, title=&#039;span($v_1, v_2$): two independent vectors\\n\u2192 fills the entire plane&#039;,\n         xlim=(-3.5, 3.5), ylim=(-3, 3.5))\n\n# \u2500\u2500 Case 3: span of two dependent vectors = still just a line \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nax = axes[2]\nv1 = np.array([1.2, 0.6])\nv2 = 2.0 * v1                             # v2 is just a scaled v1\nt = np.linspace(-2, 2, 200)\nspan_pts = np.outer(t, v1)\nax.plot(span_pts[:, 0], span_pts[:, 1], color=&#039;steelblue&#039;, lw=2, label=&#039;span($v_1, v_2$) = still a line&#039;)\ndraw_arrow(ax, v1, color=&#039;crimson&#039;,    lw=2.5, label=&#039;$v_1$&#039;,        offset=np.array([ 0.05,-0.25]))\ndraw_arrow(ax, v2, color=&#039;darkorange&#039;, lw=2.5, label=&#039;$v_2 = 2v_1$&#039;, offset=np.array([ 0.1,  0.1]))\nax.legend(fontsize=9)\nstyle_ax(ax, title=&#039;span($v_1, v_2$): two dependent vectors\\n\u2192 still only a line ($v_2$ adds nothing)&#039;,\n         xlim=(-3, 3), ylim=(-2, 2.5))\n\nfig.suptitle(&#039;&quot;Spanned by&quot;: the set of all linear combinations of a collection of vectors&#039;, fontsize=13)\nplt.tight_layout()\nplt.show()<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"334\" src=\"https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-19-1024x334.png\" alt=\"\" class=\"wp-image-1028\" srcset=\"https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-19-1024x334.png 1024w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-19-300x98.png 300w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-19-768x250.png 768w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-19-1536x501.png 1536w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-19.png 1819w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n","protected":false},"excerpt":{"rendered":"<p>This notebook builds the geometric and algebraic understanding of the determinant from first principles, culminating in why $\\det(A) = 0$ is the exact condition for singularity. 1. The [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[25,6],"tags":[],"class_list":["post-1022","post","type-post","status-publish","format-standard","hentry","category-in-english","category-matchematics"],"_links":{"self":[{"href":"https:\/\/tensorzen.online\/index.php?rest_route=\/wp\/v2\/posts\/1022","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tensorzen.online\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tensorzen.online\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tensorzen.online\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tensorzen.online\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1022"}],"version-history":[{"count":1,"href":"https:\/\/tensorzen.online\/index.php?rest_route=\/wp\/v2\/posts\/1022\/revisions"}],"predecessor-version":[{"id":1029,"href":"https:\/\/tensorzen.online\/index.php?rest_route=\/wp\/v2\/posts\/1022\/revisions\/1029"}],"wp:attachment":[{"href":"https:\/\/tensorzen.online\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1022"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tensorzen.online\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1022"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tensorzen.online\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1022"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}