{"id":1007,"date":"2026-06-15T13:27:22","date_gmt":"2026-06-15T13:27:22","guid":{"rendered":"https:\/\/tensorzen.online\/?p=1007"},"modified":"2026-06-15T13:27:22","modified_gmt":"2026-06-15T13:27:22","slug":"how-non-singular-matrices-act-on-space","status":"publish","type":"post","link":"https:\/\/tensorzen.online\/?p=1007","title":{"rendered":"How Non-Singular Matrices Act on Space"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">This notebook builds geometric intuition for matrix transformations, covering:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The <strong>active vs. passive<\/strong> interpretation<\/li>\n\n\n\n<li>Columns of a matrix as <strong>images of basis vectors<\/strong><\/li>\n\n\n\n<li>What <strong>non-singularity<\/strong> means geometrically<\/li>\n\n\n\n<li>The <strong>SVD<\/strong> as a decomposition of the transformation into interpretable steps<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"\" data-line=\"\">import numpy as np\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as mpatches\nfrom matplotlib.patches import FancyArrowPatch\nfrom matplotlib.gridspec import GridSpec\n\n# \u2500\u2500 shared helpers \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ndef draw_grid(ax, A=None, color=&#039;lightgray&#039;, alpha=0.5, lw=0.8, extent=3):\n    &quot;&quot;&quot;Draw a transformed (or standard) grid.&quot;&quot;&quot;\n    lines = np.linspace(-extent, extent, 2 * extent + 1)\n    for v in lines:\n        pts_h = np.array([[-extent, v], [extent, v]]).T   # horizontal line\n        pts_v = np.array([[v, -extent], [v,  extent]]).T  # vertical line\n        if A is not None:\n            pts_h = A @ pts_h\n            pts_v = A @ pts_v\n        ax.plot(*pts_h, color=color, alpha=alpha, lw=lw, zorder=0)\n        ax.plot(*pts_v, color=color, alpha=alpha, lw=lw, zorder=0)\n\ndef draw_arrow(ax, vec, origin=None, color=&#039;black&#039;, lw=2, label=None, label_offset=None):\n    if origin is None:\n        origin = np.zeros(2)\n    ax.annotate(&#039;&#039;, xy=origin + vec, xytext=origin,\n                arrowprops=dict(arrowstyle=&#039;-&gt;&#039;, color=color, lw=lw))\n    if label:\n        pos = origin + vec + (label_offset if label_offset is not None else np.array([0.1, 0.1]))\n        ax.text(*pos, label, color=color, fontsize=12)\n\ndef style_ax(ax, title=&#039;&#039;, xlim=(-3.5, 3.5), ylim=(-3.5, 3.5)):\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.6); ax.axvline(0, color=&#039;gray&#039;, lw=0.6)\n    ax.set_title(title, fontsize=11, pad=8)\n    ax.set_xlabel(&#039;$x_1$&#039;); ax.set_ylabel(&#039;$x_2$&#039;)<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">1. Active vs. Passive Interpretation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Given a matrix $A$ and a vector $v$, the product $Av$ has two valid readings:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Active<\/strong> \u2014 $A$ <em>moves<\/em> $v$ to a new location in the same coordinate system.<br><strong>Passive<\/strong> \u2014 $A$ <em>redefines the rulers<\/em>; the vector stays put but its coordinates change.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Both produce the same numbers. The distinction is conceptual: are the vectors moving, or is the coordinate system?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Below we show both views of the same matrix $A = \\begin{bmatrix} 2 &amp; 0.5 \\ 0.5 &amp; 1.5 \\end{bmatrix}$ acting on $v = [1, 1]^\\top$.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"\" data-line=\"\">A = np.array([[2.0, 0.5],\n              [0.5, 1.5]])\nv = np.array([1.0, 1.0])\nAv = A @ v\n\nfig, axes = plt.subplots(1, 2, figsize=(13, 5))\n\n# \u2500\u2500 Active view \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\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]\ndraw_grid(ax, color=&#039;lightgray&#039;)\ndraw_arrow(ax, v,  color=&#039;steelblue&#039;, lw=2.5, label=&#039;$v$&#039;,  label_offset=np.array([ 0.05,  0.15]))\ndraw_arrow(ax, Av, color=&#039;crimson&#039;,   lw=2.5, label=&#039;$Av$&#039;, label_offset=np.array([ 0.1, -0.3]))\nstyle_ax(ax, title=&#039;Active view \u2014 $A$ moves $v$ to $Av$\\n(coordinate system fixed)&#039;)\n\n# \u2500\u2500 Passive view \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\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[1]\ndraw_grid(ax, color=&#039;lightgray&#039;)       # original grid (faint)\ndraw_grid(ax, A=A, color=&#039;#90c4e4&#039;, alpha=0.7, lw=1.2)  # transformed grid\ndraw_arrow(ax, v, color=&#039;steelblue&#039;, lw=2.5, label=&#039;$v$&#039;, label_offset=np.array([0.05, 0.15]))\nstyle_ax(ax, title=&#039;Passive view \u2014 $A$ reshapes the coordinate grid\\n(vector $v$ stays fixed)&#039;)\nhandles = [\n    mpatches.Patch(facecolor=&#039;lightgray&#039;, label=&#039;Original grid&#039;),\n    mpatches.Patch(facecolor=&#039;#90c4e4&#039;,   label=&#039;Transformed grid&#039;),\n]\naxes[1].legend(handles=handles, loc=&#039;lower right&#039;, fontsize=9)\n\nfig.suptitle(&#039;Two interpretations of the same matrix $A$&#039;, fontsize=13, y=1.01)\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=\"489\" src=\"https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-10-1024x489.png\" alt=\"\" class=\"wp-image-1017\" srcset=\"https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-10-1024x489.png 1024w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-10-300x143.png 300w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-10-768x367.png 768w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-10.png 1316w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">2. Columns of $A$ as Images of Basis Vectors<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The most useful geometric fact:<\/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>The $j$-th column of $A$ is exactly where the $j$-th standard basis vector $e_j$ lands.<\/strong><\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">This is because $A e_j$ selects the $j$-th column. So the matrix is completely described by where it sends the axes:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">$$A = \\begin{bmatrix} | &amp; | \\ Ae_1 &amp; Ae_2 \\ | &amp; | \\end{bmatrix}$$<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Everything else follows by linearity \u2014 any vector $v = v_1 e_1 + v_2 e_2$ maps to $Av = v_1(Ae_1) + v_2(Ae_2)$.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"\" data-line=\"\">e1 = np.array([1.0, 0.0])\ne2 = np.array([0.0, 1.0])\nAe1, Ae2 = A @ e1, A @ e2\n\nprint(f&quot;A =\\n{A}&quot;)\nprint(f&quot;\\nAe1 = {Ae1}  \u2190 first column of A&quot;)\nprint(f&quot;Ae2 = {Ae2}  \u2190 second column of A&quot;)\n\n# Verify linearity: A @ v = v[0]*Ae1 + v[1]*Ae2\nv_reconstructed = v[0] * Ae1 + v[1] * Ae2\nprint(f&quot;\\nv = {v}&quot;)\nprint(f&quot;Av via matrix multiply : {A @ v}&quot;)\nprint(f&quot;Av via linear combo    : {v_reconstructed}  \u2713&quot;)<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-preformatted\">A =\n[[2.  0.5]\n [0.5 1.5]]\n\nAe1 = [2.  0.5]  \u2190 first column of A\nAe2 = [0.5 1.5]  \u2190 second column of A\n\nv = [1. 1.]\nAv via matrix multiply : [2.5 2. ]\nAv via linear combo    : [2.5 2. ]  \u2713<\/pre>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"\" data-line=\"\">fig, axes = plt.subplots(1, 2, figsize=(13, 5))\n\nfor ax, (title, draw_A) in zip(axes, [\n    (&#039;Before: standard basis&#039;, False),\n    (&#039;After: columns of $A$ are new basis&#039;, True)\n]):\n    draw_grid(ax, A=(A if draw_A else None), color=&#039;lightgray&#039;)\n    if draw_A:\n        draw_arrow(ax, Ae1, color=&#039;crimson&#039;,   lw=2.5, label=&#039;$Ae_1$ (col 1)&#039;, label_offset=np.array([0.1, -0.35]))\n        draw_arrow(ax, Ae2, color=&#039;darkorange&#039;, lw=2.5, label=&#039;$Ae_2$ (col 2)&#039;, label_offset=np.array([0.1,  0.1]))\n        # show transformed v\n        draw_arrow(ax, A @ v, color=&#039;purple&#039;, lw=2, label=&#039;$Av$&#039;, label_offset=np.array([0.1, 0.1]))\n    else:\n        draw_arrow(ax, e1, color=&#039;crimson&#039;,    lw=2.5, label=&#039;$e_1$&#039;, label_offset=np.array([ 0.05, -0.25]))\n        draw_arrow(ax, e2, color=&#039;darkorange&#039;, lw=2.5, label=&#039;$e_2$&#039;, label_offset=np.array([-0.35,  0.1]))\n        draw_arrow(ax, v,  color=&#039;purple&#039;,     lw=2,   label=&#039;$v$&#039;,   label_offset=np.array([ 0.1,   0.1]))\n    style_ax(ax, title=title, xlim=(-3.5, 3.5), ylim=(-2, 4))\n\nfig.suptitle(&#039;Columns of $A$ are the images of $e_1$ and $e_2$&#039;, fontsize=13, y=1.01)\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=\"453\" src=\"https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-11-1024x453.png\" alt=\"\" class=\"wp-image-1018\" srcset=\"https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-11-1024x453.png 1024w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-11-300x133.png 300w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-11-768x340.png 768w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-11.png 1428w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">3. Non-Singular vs. Singular: Geometric Meaning<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A matrix is <strong>non-singular<\/strong> (invertible) when its columns are linearly independent \u2014 they still span the full space after transformation. A <strong>singular<\/strong> matrix collapses at least one dimension.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The <strong>determinant<\/strong> measures the signed area (2D) or volume (nD) scaling factor:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">$$\\det(A) \\neq 0 \\iff \\text{non-singular} \\iff \\text{no dimension is collapsed}$$<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Below we compare three matrices:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>$A_1$ \u2014 non-singular, stretches and rotates<\/li>\n\n\n\n<li>$A_2$ \u2014 singular, collapses 2D onto a line<\/li>\n\n\n\n<li>$A_3$ \u2014 non-singular, reflection (det &lt; 0, orientation flipped)<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"\" data-line=\"\"># Unit square corners (and closing point)\nsquare = np.array([[0,1,1,0,0],\n                   [0,0,1,1,0]], dtype=float)\n\nA1 = np.array([[2.0,  0.5],   # non-singular stretch\n               [0.5,  1.5]])\nA2 = np.array([[1.0,  2.0],   # singular: col2 = 2*col1\n               [0.5,  1.0]])\nA3 = np.array([[-1.0, 0.0],   # reflection across y-axis\n               [ 0.0, 1.0]])\n\nmatrices = [(A1, &#039;Non-singular\\n$A_1$, det={:.2f}&#039;.format(np.linalg.det(A1))),\n            (A2, &#039;Singular\\n$A_2$, det={:.2f}&#039;.format(np.linalg.det(A2))),\n            (A3, &#039;Non-singular (reflection)\\n$A_3$, det={:.2f}&#039;.format(np.linalg.det(A3)))]\n\nfig, axes = plt.subplots(2, 3, figsize=(14, 9))\n\nfor col, (M, label) in enumerate(matrices):\n    for row, (title_sfx, use_M) in enumerate([(&#039;Before&#039;, False), (&#039;After&#039;, True)]):\n        ax = axes[row][col]\n        draw_grid(ax, A=(M if use_M else None))\n        sq = M @ square if use_M else square\n        ax.fill(sq[0], sq[1], alpha=0.25, color=&#039;steelblue&#039;)\n        ax.plot(sq[0], sq[1], color=&#039;steelblue&#039;, lw=2)\n        t = label if use_M else &#039;Standard basis&#039;\n        style_ax(ax, title=t, xlim=(-4,4), ylim=(-3,4))\n        if use_M:\n            c1, c2 = M[:, 0], M[:, 1]\n            draw_arrow(ax, c1, color=&#039;crimson&#039;,    lw=2, label=&#039;col 1&#039;, label_offset=np.array([ 0.1, -0.3]))\n            draw_arrow(ax, c2, color=&#039;darkorange&#039;, lw=2, label=&#039;col 2&#039;, label_offset=np.array([ 0.1,  0.1]))\n\naxes[0][0].set_ylabel(&#039;Before&#039;, fontsize=12)\naxes[1][0].set_ylabel(&#039;After $A$&#039;, fontsize=12)\nfig.suptitle(&#039;Effect of non-singular vs. singular matrices on the unit square&#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=\"652\" src=\"https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-12-1024x652.png\" alt=\"\" class=\"wp-image-1019\" srcset=\"https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-12-1024x652.png 1024w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-12-300x191.png 300w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-12-768x489.png 768w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-12-1536x978.png 1536w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-12.png 1749w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Observations:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>$A_1$ (non-singular): the square becomes a parallelogram \u2014 area changes but it remains 2D<\/li>\n\n\n\n<li>$A_2$ (singular): the square collapses to a <strong>line segment<\/strong> \u2014 all area is destroyed, the transformation is irreversible<\/li>\n\n\n\n<li>$A_3$ (reflection): the square flips \u2014 the negative determinant signals the <strong>orientation reversal<\/strong><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">4. Determinant as a Volume Scaling Factor<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The determinant is not just a number \u2014 it is the <strong>signed area scaling factor<\/strong> of the transformation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">$$\\text{Area after} = |\\det(A)| \\times \\text{Area before}$$<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The sign encodes orientation: positive means the transformation preserves orientation (no reflection), negative means it reverses it.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"\" data-line=\"\">unit_area = 1.0  # area of unit square\n\nfor M, name in [(A1,&#039;A1 (non-singular)&#039;), (A2,&#039;A2 (singular)&#039;), (A3,&#039;A3 (reflection)&#039;)]:\n    d = np.linalg.det(M)\n    print(f&quot;{name}: det = {d:+.4f},  area after = {abs(d):.4f},  orientation {&#039;preserved&#039; if d &gt; 0 else &#039;reversed&#039; if d &lt; 0 else &#039;destroyed&#039;}&quot;)<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-preformatted\">A1 (non-singular): det = +2.7500,  area after = 2.7500,  orientation preserved\nA2 (singular): det = +0.0000,  area after = 0.0000,  orientation destroyed\nA3 (reflection): det = -1.0000,  area after = 1.0000,  orientation reversed<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">5. SVD Decomposes the Transformation into Three Steps<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The SVD $A = U \\Sigma V^\\top$ breaks every matrix transformation into three interpretable steps:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">$$v \\;\\xrightarrow{V^\\top}\\; \\text{rotate\/reflect} \\;\\xrightarrow{\\Sigma}\\; \\text{scale along axes} \\;\\xrightarrow{U}\\; \\text{rotate\/reflect}$$<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>$V^\\top$ \u2014 rotates the input to align with the matrix&#8217;s natural <strong>input directions<\/strong> (right singular vectors)<\/li>\n\n\n\n<li>$\\Sigma$ \u2014 stretches or compresses each axis independently by the <strong>singular values<\/strong> $\\sigma_i$<\/li>\n\n\n\n<li>$U$ \u2014 rotates the result into the <strong>output orientation<\/strong> (left singular vectors)<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Non-singular means all $\\sigma_i &gt; 0$ \u2014 every axis is scaled by a nonzero amount, so no dimension is lost.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"\" data-line=\"\">U, s, Vt = np.linalg.svd(A1)\nSigma = np.diag(s)\n\nprint(f&quot;A1 = U \u03a3 V\u1d40&quot;)\nprint(f&quot;\\nU  =\\n{np.round(U, 3)}   (rotation\/reflection)&quot;)\nprint(f&quot;\\n\u03a3  = diag{np.round(s, 3)}  (scaling: \u03c31={s[0]:.3f}, \u03c32={s[1]:.3f})&quot;)\nprint(f&quot;\\nV\u1d40 =\\n{np.round(Vt, 3)}   (rotation\/reflection)&quot;)\nprint(f&quot;\\nReconstructed: U @ \u03a3 @ V\u1d40 =\\n{np.round(U @ Sigma @ Vt, 6)}&quot;)\nprint(f&quot;Original A1:\\n{A1}  \u2713&quot;)<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-preformatted\">A1 = U \u03a3 V\u1d40\n\nU  =\n[[-0.851 -0.526]\n [-0.526  0.851]]   (rotation\/reflection)\n\n\u03a3  = diag[2.309 1.191]  (scaling: \u03c31=2.309, \u03c32=1.191)\n\nV\u1d40 =\n[[-0.851 -0.526]\n [-0.526  0.851]]   (rotation\/reflection)\n\nReconstructed: U @ \u03a3 @ V\u1d40 =\n[[2.  0.5]\n [0.5 1.5]]\nOriginal A1:\n[[2.  0.5]\n [0.5 1.5]]  \u2713<\/pre>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"\" data-line=\"\"># Visualise the four stages: v \u2192 V\u1d40v \u2192 \u03a3V\u1d40v \u2192 U\u03a3V\u1d40v\nfig, axes = plt.subplots(1, 4, figsize=(18, 5))\n\n# We&#039;ll track the unit circle and the standard basis arrows through each stage\ntheta = np.linspace(0, 2 * np.pi, 300)\ncircle = np.row_stack([np.cos(theta), np.sin(theta)])\n\nstages = [\n    (np.eye(2),      &#039;Stage 0: Input space\\n(unit circle + basis)&#039;),\n    (Vt,             &#039;Stage 1: Apply $V^\\\\top$\\n(rotate to align with singular vectors)&#039;),\n    (Sigma @ Vt,     &#039;Stage 2: Apply $\\\\Sigma$\\n(scale: stretch\/compress)&#039;),\n    (U @ Sigma @ Vt, &#039;Stage 3: Apply $U$\\n(rotate to output orientation = $A_1$)&#039;),\n]\n\nfor ax, (M, title) in zip(axes, stages):\n    draw_grid(ax, A=M if not np.allclose(M, np.eye(2)) else None)\n    c = M @ circle\n    ax.plot(c[0], c[1], color=&#039;steelblue&#039;, lw=2)\n    ax.fill(c[0], c[1], color=&#039;steelblue&#039;, alpha=0.15)\n    draw_arrow(ax, M[:, 0], color=&#039;crimson&#039;,    lw=2)\n    draw_arrow(ax, M[:, 1], color=&#039;darkorange&#039;, lw=2)\n    style_ax(ax, title=title, xlim=(-3.5, 3.5), ylim=(-3.5, 3.5))\n\nfig.suptitle(&#039;SVD decomposition: $A_1 = U\\\\Sigma V^\\\\top$ \u2014 three geometric stages&#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=\"293\" src=\"https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-13-1024x293.png\" alt=\"\" class=\"wp-image-1020\" srcset=\"https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-13-1024x293.png 1024w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-13-300x86.png 300w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-13-768x220.png 768w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-13-1536x440.png 1536w, https:\/\/tensorzen.online\/wp-content\/uploads\/2026\/06\/image-13-2048x586.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">6. What Non-Singularity Guarantees<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Bringing it all together:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"\" data-line=\"\">print(&quot;=== Non-singular matrix A1 ===&quot;)\nprint(f&quot;  Singular values      : {np.round(s, 4)}  (all &gt; 0 \u2713)&quot;)\nprint(f&quot;  Determinant          : {np.linalg.det(A1):.4f}  (\u2260 0 \u2713)&quot;)\nprint(f&quot;  Rank                 : {np.linalg.matrix_rank(A1)}  (= n \u2713)&quot;)\nprint(f&quot;  Condition number \u03ba   : {s[0]\/s[-1]:.2f}  (finite \u2713)&quot;)\nprint(f&quot;  Invertible           : Yes&quot;)\n\nprint()\nprint(&quot;=== Singular matrix A2 ===&quot;)\nU2, s2, Vt2 = np.linalg.svd(A2)\nprint(f&quot;  Singular values      : {np.round(s2, 6)}  (one = 0 \u2717)&quot;)\nprint(f&quot;  Determinant          : {np.linalg.det(A2):.6f}  (\u2248 0 \u2717)&quot;)\nprint(f&quot;  Rank                 : {np.linalg.matrix_rank(A2)}  (&lt; n \u2717)&quot;)\nprint(f&quot;  Condition number \u03ba   : \u221e&quot;)\nprint(f&quot;  Invertible           : No \u2014 collapses 2D space onto a 1D line&quot;)<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-preformatted\">=== Non-singular matrix A1 ===\n  Singular values      : [2.309 1.191]  (all &gt; 0 \u2713)\n  Determinant          : 2.7500  (\u2260 0 \u2713)\n  Rank                 : 2  (= n \u2713)\n  Condition number \u03ba   : 1.94  (finite \u2713)\n  Invertible           : Yes\n\n=== Singular matrix A2 ===\n  Singular values      : [2.5 0. ]  (one = 0 \u2717)\n  Determinant          : 0.000000  (\u2248 0 \u2717)\n  Rank                 : 1  (&lt; n \u2717)\n  Condition number \u03ba   : \u221e\n  Invertible           : No \u2014 collapses 2D space onto a 1D line<\/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>Concept<\/th><th>Geometric meaning<\/th><\/tr><\/thead><tbody><tr><td>Active interpretation<\/td><td>$A$ moves vectors; coordinate system is fixed<\/td><\/tr><tr><td>Passive interpretation<\/td><td>$A$ reshapes the grid; vectors are fixed<\/td><\/tr><tr><td>Columns of $A$<\/td><td>Where the standard basis vectors $e_j$ land<\/td><\/tr><tr><td>Non-singular<\/td><td>Columns independent \u2192 no dimension collapsed \u2192 full rank \u2192 invertible<\/td><\/tr><tr><td>$\\det(A) \\neq 0$<\/td><td>Area\/volume is scaled but not destroyed<\/td><\/tr><tr><td>$\\det(A) &lt; 0$<\/td><td>Orientation is reversed (reflection component present)<\/td><\/tr><tr><td>$\\det(A) = 0$<\/td><td>At least one dimension is collapsed \u2014 transformation irreversible<\/td><\/tr><tr><td>SVD $A = U\\Sigma V^\\top$<\/td><td>Rotate \u2192 scale along axes \u2192 rotate<\/td><\/tr><tr><td>All $\\sigma_i &gt; 0$<\/td><td>Equivalent condition for non-singularity via SVD<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The active and passive views are two lenses on the same object. Choosing between them is a matter of which makes the problem at hand easier to reason about \u2014 not a question of which is correct.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This notebook builds geometric intuition for matrix transformations, covering: 1. Active vs. Passive Interpretation Given a matrix $A$ and a vector $v$, the product $Av$ has two valid [&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,1],"tags":[],"class_list":["post-1007","post","type-post","status-publish","format-standard","hentry","category-in-english","category-matchematics","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/tensorzen.online\/index.php?rest_route=\/wp\/v2\/posts\/1007","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=1007"}],"version-history":[{"count":2,"href":"https:\/\/tensorzen.online\/index.php?rest_route=\/wp\/v2\/posts\/1007\/revisions"}],"predecessor-version":[{"id":1021,"href":"https:\/\/tensorzen.online\/index.php?rest_route=\/wp\/v2\/posts\/1007\/revisions\/1021"}],"wp:attachment":[{"href":"https:\/\/tensorzen.online\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1007"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tensorzen.online\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1007"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tensorzen.online\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1007"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}