<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>Synthetic Musings</title>
<link>https://project-delphi.github.io/ml-blog/</link>
<atom:link href="https://project-delphi.github.io/ml-blog/index.xml" rel="self" type="application/rss+xml"/>
<description>Technical write-ups on machine learning, statistics, linear algebra, and the tooling around them, by Ravi Kalia.</description>
<generator>quarto-1.6.40</generator>
<lastBuildDate>Thu, 17 Sep 2026 05:00:00 GMT</lastBuildDate>
<item>
  <title>Who Does the Clearing?</title>
  <dc:creator>Ravi Kalia</dc:creator>
  <link>https://project-delphi.github.io/ml-blog/posts/efferocytosis-history-model/</link>
  <description><![CDATA[ 





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/efferocytosis-history-model/cover.png" class="img-fluid figure-img" alt="Two cumulative uptake curves lying on top of each other, beside a bar chart showing the per-cell contributions underneath them diverging sharply."></p>
<figcaption>Who Does the Clearing?</figcaption>
</figure>
</div>
<p>A macrophage population clears apoptotic cells at some rate, and that rate is usually all an assay reports. The number is a sum over cells, and a sum has no memory of which cells produced it.</p>
<p>Watch a clearance assay for long enough and the same total uptake can arrive two ways. Experienced macrophages can take on more, so clearance concentrates in the cells that have already eaten. Or experienced macrophages can slow down while fresh cells absorb what they leave, so clearance spreads out. Both trajectories are consistent with continual efferocytosis as it is described, and both give the same population curve. Separating them needs a model of the per-cell event history and a design that assigns the history rather than observing it.</p>
<p>Everything here is simulated. No experimental data were used, and no result below is a claim about a real macrophage population — the point is what an analysis can and cannot recover when the generating process is known exactly.</p>
<section id="efferocytosis" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="efferocytosis"><span class="header-section-number">1</span> Efferocytosis</h2>
<p>Efferocytosis is the recognition, engulfment and disposal of an apoptotic cell by a phagocyte (<a href="https://doi.org/10.1038/s41577-019-0240-6">Doran et al.&nbsp;2020</a>). It runs in stages that are worth keeping separate:</p>
<ul>
<li><strong>Find-me.</strong> Nucleotides and lipids released by the dying cell recruit the phagocyte (<a href="https://doi.org/10.1016/j.immuni.2011.09.004">Ravichandran 2011</a>).</li>
<li><strong>Eat-me.</strong> Exposed phosphatidylserine, read directly or through a bridging molecule, marks the target for engulfment (<a href="https://doi.org/10.1038/nri3607">Poon et al.&nbsp;2014</a>).</li>
<li><strong>Internalisation.</strong> The target is drawn into a phagosome.</li>
<li><strong>Processing.</strong> The phagolysosome degrades the cargo, and the phagocyte metabolises what it recovers.</li>
</ul>
<p>These are not a strict queue. A macrophage can bind one target while still digesting another, so binding, internalisation and processing overlap in time.</p>
<p>Macrophages clear targets repeatedly rather than once, and what happens after the first meal is the part that matters here. Processing the first corpse changes the cell: mitochondrial fission supports continued uptake (<a href="https://doi.org/10.1016/j.cell.2017.08.041">Wang et al.&nbsp;2017</a>), arginine recovered from the cargo is metabolised into polyamines that promote further uptake (<a href="https://doi.org/10.1016/j.cmet.2020.01.001">Yurdagul et al.&nbsp;2020</a>), and loss of the phagocyte’s UCP2 protein blocks continued clearance specifically (<a href="https://doi.org/10.1038/nature10340">Park et al.&nbsp;2011</a>). Against that, a cell carrying undigested cargo has finite capacity.</p>
<p>So a cell’s own history could push its next uptake either way. Prior uptake could prime it, or saturate it. Both are described in the literature, and an assay that reports a population mean cannot tell which one is running.</p>
<p>Neighbours matter too, in two different ways that are easy to conflate. A neighbour that eats a target removes that target, which lowers the opportunity in the local neighbourhood. Separately, metabolites released by apoptotic cells act on surrounding tissue as signals in their own right (<a href="https://doi.org/10.1038/s41586-020-2121-3">Medina et al.&nbsp;2020</a>). The first is depletion; the second is signalling; they are distinguishable only if opportunity is measured.</p>
</section>
<section id="same-mean-different-mechanism" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="same-mean-different-mechanism"><span class="header-section-number">2</span> Same mean, different mechanism</h2>
<p>When targets are limited, the population curve is set by supply, and the allocation underneath it is free.</p>
<p>Two rules draw on one fixed pool of targets. Under priming, cells with prior experience take a larger share; under satiety they take a smaller one and the naive cells absorb the remainder. The pool is what runs out, so both rules eat through the same supply on the same schedule.</p>
<div id="cell-fig-same-mean" class="cell" data-fig-height="3.6" data-fig-width="9" data-execution_count="3">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1">fig, (ax1, ax2) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.6</span>))</span>
<span id="cb1-2"></span>
<span id="cb1-3">steps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.arange(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(priming.cumulative))</span>
<span id="cb1-4">ax1.plot(steps, priming.cumulative, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>house.ACCENT, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.4</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"priming"</span>)</span>
<span id="cb1-5">ax1.plot(steps, satiety.cumulative, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>house.CORAL, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.4</span>, ls<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--"</span>,</span>
<span id="cb1-6">         label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"satiety + redistribution"</span>)</span>
<span id="cb1-7">ax1.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"time step"</span>)</span>
<span id="cb1-8">ax1.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cumulative targets cleared"</span>)</span>
<span id="cb1-9">ax1.legend(loc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lower right"</span>)</span>
<span id="cb1-10"></span>
<span id="cb1-11">ax2.bar([<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"priming"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"satiety +</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">redistribution"</span>], [share_p, share_s],</span>
<span id="cb1-12">        color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[house.ACCENT, house.CORAL], width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.55</span>)</span>
<span id="cb1-13">ax2.axhline(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.35</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>house.MUTED, ls<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">":"</span>, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.2</span>)</span>
<span id="cb1-14">ax2.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"share of uptake by experienced cells"</span>)</span>
<span id="cb1-15">ax2.set_ylim(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb1-16">ax2.annotate(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"experienced cells are 35</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">% o</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">f the population"</span>,</span>
<span id="cb1-17">             xy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.38</span>), xycoords<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"axes fraction"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"data"</span>),</span>
<span id="cb1-18">             ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"center"</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>house.MUTED, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb1-19">fig.tight_layout()</span>
<span id="cb1-20">plt.show()</span>
<span id="cb1-21"></span>
<span id="cb1-22"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"final totals differ by </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>total_gap<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.0f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> targets"</span>)</span>
<span id="cb1-23"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"experienced-cell share: priming </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>share_p<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, satiety </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>share_s<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-same-mean" class="quarto-float quarto-figure quarto-figure-center anchored" alt="Two panels. The left shows two cumulative curves that rise together and overlap so closely that one hides the other. The right shows two bars of very different height.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-same-mean-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/efferocytosis-history-model/index_files/figure-html/fig-same-mean-output-1.png" class="img-fluid figure-img" alt="Two panels. The left shows two cumulative curves that rise together and overlap so closely that one hides the other. The right shows two bars of very different height.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-same-mean-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Simulated supply-limited clearance of 900 targets by 200 cells under two allocation rules. Left: cumulative population uptake through time under priming and under satiety plus redistribution. Right: the share of all uptake taken by the 35% of cells designated as experienced, under each rule.
</figcaption>
</figure>
</div>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>final totals differ by 0 targets
experienced-cell share: priming 0.602, satiety 0.144</code></pre>
</div>
</div>
<p>The two population curves are identical, not merely close: the pool empties on a fixed schedule, so the allocation rule cannot change the total. That is the construction, not a finding — it is what lets the figure vary the mechanism with the population curve held exactly still. The share of the work done by experienced cells is 0.60 against 0.14 — a factor of 4.2. The mean is the same; the mechanism is not.</p>
</section>
<section id="a-recurrent-event-intensity" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="a-recurrent-event-intensity"><span class="header-section-number">3</span> A recurrent-event intensity</h2>
<p>Uptake by one cell is a recurrent event, so the object to model is an intensity: the instantaneous rate of the next uptake given everything that has happened to that cell so far (<a href="https://doi.org/10.1214/aos/1176345976">Andersen and Gill 1982</a>, <a href="https://doi.org/10.1007/978-0-387-69810-6">Cook and Lawless 2007</a>).</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Clambda_i(t)%20=%20%5Clambda_0%20%5Cexp%5CBigl%5C%7B%20f_1%5Cbigl(%5Cell_i(t)%5Cbigr)%20+%20f_2%5Cbigl(g_i(t)%5Cbigr)%0A+%20f_3%5Cbigl(n_i(t)%5Cbigr)%20+%20u_%7Bd(i)%7D%20+%20w_%7Bj(i)%7D%20%5CBigr%5C%7D%20%5Ccdot%20o_i(t)%0A"></p>
<p>Each term carries a separate piece of biology.</p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?%5Cell_i(t)"> — <strong>current load</strong>, targets internalised but not yet processed. This is where priming and satiety compete, so <img src="https://latex.codecogs.com/png.latex?f_1"> is the curve of interest.</li>
<li><img src="https://latex.codecogs.com/png.latex?g_i(t)"> — <strong>time since the last uptake</strong>. <img src="https://latex.codecogs.com/png.latex?f_2"> is a refractory term: a cell that has just engulfed something is briefly occupied.</li>
<li><img src="https://latex.codecogs.com/png.latex?n_i(t)"> — <strong>neighbour load</strong>. <img src="https://latex.codecogs.com/png.latex?f_3"> is appetite changed by neighbours through signalling, over and above their effect on supply.</li>
<li><img src="https://latex.codecogs.com/png.latex?u_%7Bd(i)%7D">, <img src="https://latex.codecogs.com/png.latex?w_%7Bj(i)%7D"> — donor and well random effects. Donors differ, and so do wells within a donor.</li>
<li><img src="https://latex.codecogs.com/png.latex?o_i(t)"> — <strong>opportunity</strong>: the number of unconsumed targets within reach of cell <img src="https://latex.codecogs.com/png.latex?i">.</li>
</ul>
<p>The opportunity term enters as an offset with coefficient fixed at one, not as a fitted covariate. This is the load-bearing choice. A cell in a depleted neighbourhood eats little because there is nothing to eat, and without the offset that cell’s low count is read as low appetite. Low uptake is not low appetite; the offset is what keeps the two apart, and it is also what makes depletion and neighbour signalling separately identifiable.</p>
</section>
<section id="a-simulated-two-wave-experiment" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="a-simulated-two-wave-experiment"><span class="header-section-number">4</span> A simulated two-wave experiment</h2>
<p>The design delivers the <strong>same first-wave target budget</strong> two ways, then gives every arm the same second wave.</p>
<ul>
<li><strong><code>broad</code></strong> — the first-wave targets are scattered across the well.</li>
<li><strong><code>focal</code></strong> — the same number of targets, packed around a subset of cells fixed in advance.</li>
<li><strong><code>none</code></strong> — no first wave, as a reference for what an inexperienced population does.</li>
</ul>
<p>After the first wave there is a wash, a three-hour interval, and a second wave common to every arm. 6 donors each contribute one well to each arm: 18 wells and 1440 cells in total. Cells are lost from tracking at a constant hazard, and 12.8% of them are censored before the end.</p>
<p>The generating process gives <img src="https://latex.codecogs.com/png.latex?f_1"> a rise then a fall — priming at low load, satiety at high — and <img src="https://latex.codecogs.com/png.latex?f_2"> a refractory dip that decays with a time constant of 0.75 h. Digestion clears a target in 4 h, which is slow relative to the interval between waves; that is what carries first-wave history into the second wave rather than letting it wash out. Neighbour effects are generated twice: once by depletion alone, where <img src="https://latex.codecogs.com/png.latex?f_3"> is flat at zero and neighbours act only through the offset, and once by depletion plus signalling.</p>
<div id="cell-fig-raster" class="cell" data-fig-height="5" data-fig-width="9" data-execution_count="5">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">fig, axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.0</span>), sharex<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb3-2">panel <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> exp.panel</span>
<span id="cb3-3"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> ax, arm <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(axes, (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"broad"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"focal"</span>)):</span>
<span id="cb3-4">    sub <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> panel[(panel[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"arm"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> arm) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> (panel[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"donor"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)]</span>
<span id="cb3-5">    order <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (</span>
<span id="cb3-6">        cells[(cells[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"arm"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> arm) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> (cells[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"donor"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)]</span>
<span id="cb3-7">        .sort_values(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"wave1_uptake"</span>, ascending<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"uid"</span>].to_numpy()[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>]</span>
<span id="cb3-8">    )</span>
<span id="cb3-9">    rank <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {u: r <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> r, u <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(order)}</span>
<span id="cb3-10">    ev <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sub[(sub[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"y"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> (sub[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"uid"</span>].isin(rank))]</span>
<span id="cb3-11">    ax.axvspan(sim.WAVE1[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> sim.DT, sim.WAVE1[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> sim.DT, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>house.RULE, alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.55</span>)</span>
<span id="cb3-12">    ax.axvspan(sim.WAVE2[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> sim.DT, sim.WAVE2[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> sim.DT, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>house.RULE, alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.55</span>)</span>
<span id="cb3-13">    ax.scatter(</span>
<span id="cb3-14">        ev[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bin"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> sim.DT, [rank[u] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> u <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> ev[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"uid"</span>]],</span>
<span id="cb3-15">        s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>house.ARM_COLOUR[arm], alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.85</span>, linewidths<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,</span>
<span id="cb3-16">    )</span>
<span id="cb3-17">    ax.set_ylabel(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>arm<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">cell"</span>)</span>
<span id="cb3-18">    ax.set_ylim(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>)</span>
<span id="cb3-19">    ax.grid(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb3-20">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"hours"</span>)</span>
<span id="cb3-21">fig.tight_layout()</span>
<span id="cb3-22">plt.show()</span>
<span id="cb3-23"></span>
<span id="cb3-24"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(mean_w1.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>).to_string())</span>
<span id="cb3-25"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(mean_w2.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>).to_string())</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-raster" class="quarto-float quarto-figure quarto-figure-center anchored" alt="Two stacked panels of dot rows. In the upper panel marks are spread evenly across rows during the first shaded band. In the lower panel the marks in the first band are concentrated in the rows at the top.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-raster-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/efferocytosis-history-model/index_files/figure-html/fig-raster-output-1.png" class="img-fluid figure-img" alt="Two stacked panels of dot rows. In the upper panel marks are spread evenly across rows during the first shaded band. In the lower panel the marks in the first band are concentrated in the rows at the top.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-raster-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: Simulated uptake events for 60 cells drawn from one donor, in the broad and focal arms. Each row is one cell, each mark one uptake. The shaded bands are the two target waves; the gap between them is the wash and interval. Cells are ordered by first-wave uptake.
</figcaption>
</figure>
</div>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>arm
broad    2.79
focal    3.21
none     0.00
arm
broad    3.49
focal    3.45
none     3.51</code></pre>
</div>
</div>
<p>Mean first-wave uptake is 2.79 per cell in <code>broad</code> and 3.21 in <code>focal</code>, from the same delivered budget. Mean second-wave uptake is 3.49 against 3.45 — a difference of 1.1%. The population readout is nearly blind to the manipulation, which is the point of running it.</p>
</section>
<section id="fitting" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="fitting"><span class="header-section-number">5</span> Fitting</h2>
<p>The model is fitted on the second wave, with first-wave history carried in as covariates. The counting-process likelihood factorises into independent contributions on any partition of follow-up, so the events are aggregated to 1.5-hour blocks — about twice the refractory time constant, chosen on that scale rather than by looking at the fit. That gives 7,805 rows.</p>
<p>Two models, fixed before either was run:</p>
<ul>
<li><strong>Baseline</strong> — negative-binomial, linear in load, gap and <img src="https://latex.codecogs.com/png.latex?%5Clog(1%20+%20%5Ctext%7Bneighbour%0Aload%7D)">, with the same offset.</li>
<li><strong>Main model</strong> — the same likelihood with <img src="https://latex.codecogs.com/png.latex?f_1">, <img src="https://latex.codecogs.com/png.latex?f_2"> and <img src="https://latex.codecogs.com/png.latex?f_3"> as penalised cubic B-splines. The penalty is a second-order random walk on the basis coefficients, which is the P-spline prior (<a href="https://doi.org/10.1201/9781315370279">Wood 2017</a>), written non-centred so the sampler sees unit-scale parameters. Fitted in PyMC (<a href="https://doi.org/10.7717/peerj-cs.1516">Abril-Pla et al.&nbsp;2023</a>), with donor and well random effects.</li>
</ul>
<p>The two are fitted by different machinery, and that matters for what the comparison means. The smooths and their credible bands come from the Bayesian fit, random effects included. The model comparison in the next section refits <strong>both</strong> models by maximum likelihood <strong>without</strong> random effects, because full leave-one-donor-out on two Bayesian models was out of compute budget. The approximation is the same on both sides, so the comparison is fair, but it is not a comparison of the two models exactly as specified here.</p>
<div id="fit-gam" class="cell" data-execution_count="7">
<details open="" class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> nutpie</span>
<span id="cb5-2"></span>
<span id="cb5-3">t0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> time.perf_counter()</span>
<span id="cb5-4">weights <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.ones(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(fit_panel))</span>
<span id="cb5-5">compiled <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nutpie.compile_pymc_model(fitting.gam_model(design, weights))</span>
<span id="cb5-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># `cores` is pinned, not left to the machine. nutpie is bit-reproducible for a</span></span>
<span id="cb5-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># fixed seed only when the chain-to-core allocation is also fixed; leaving it to</span></span>
<span id="cb5-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># whatever the host has free moves the posterior summaries in the last digit.</span></span>
<span id="cb5-9">trace <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nutpie.sample(</span>
<span id="cb5-10">    compiled, chains<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, cores<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, draws<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">300</span>, tune<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">300</span>,</span>
<span id="cb5-11">    seed<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20260917</span>, progress_bar<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>,</span>
<span id="cb5-12">)</span>
<span id="cb5-13">t0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> lap(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sample"</span>, t0)</span></code></pre></div>
</details>
</div>
<div id="cell-fig-smooths" class="cell" data-fig-height="3.6" data-fig-width="9" data-execution_count="9">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1">fig, (ax1, ax2) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.6</span>))</span>
<span id="cb6-2"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> ax, grid, draws, truth, xlab, title <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (</span>
<span id="cb6-3">    (ax1, grid_load, d_load, true_load, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"load (undigested targets)"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"$f_1$: load"</span>),</span>
<span id="cb6-4">    (ax2, grid_gap, d_gap, true_gap, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"hours since last uptake"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"$f_2$: refractory"</span>),</span>
<span id="cb6-5">):</span>
<span id="cb6-6">    lo, hi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.quantile(draws, [<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.025</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.975</span>], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb6-7">    ax.fill_between(grid, lo, hi, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>house.ACCENT, alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.20</span>, linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb6-8">    ax.plot(grid, draws.mean(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>house.ACCENT, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"estimated"</span>)</span>
<span id="cb6-9">    ax.plot(grid, truth, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>house.INK, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.4</span>, ls<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--"</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generating"</span>)</span>
<span id="cb6-10">    ax.set_xlabel(xlab)</span>
<span id="cb6-11">    ax.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"contribution to log intensity"</span>)</span>
<span id="cb6-12">    ax.set_title(title)</span>
<span id="cb6-13">ax1.legend(loc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lower left"</span>)</span>
<span id="cb6-14">fig.tight_layout()</span>
<span id="cb6-15">plt.show()</span>
<span id="cb6-16"></span>
<span id="cb6-17"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"f1 RMSE </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>rmse_load<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, 95% band covers </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>cover_load<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.0%}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> of the grid"</span>)</span>
<span id="cb6-18"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"f2 RMSE </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>rmse_gap<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb6-19"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"estimated f1 peak at load </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>peak_est<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">; generating peak at </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>peak_true<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-smooths" class="quarto-float quarto-figure quarto-figure-center anchored" alt="Two panels, each with a dashed line, a solid line close to it, and a shaded band around the solid line that widens towards the right edge.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-smooths-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/efferocytosis-history-model/index_files/figure-html/fig-smooths-output-1.png" class="img-fluid figure-img" alt="Two panels, each with a dashed line, a solid line close to it, and a shaded band around the solid line that widens towards the right edge.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-smooths-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: Estimated against generating smooths from the simulated depletion-only experiment, on the log-intensity scale. Left: the load term, rising at low load and falling at high load. Right: the refractory term against time since the last uptake. Bands are 95% credible intervals; both curves are centred over the fitted rows.
</figcaption>
</figure>
</div>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>f1 RMSE 0.339, 95% band covers 18% of the grid
f2 RMSE 0.233
estimated f1 peak at load 3.34; generating peak at 1.62</code></pre>
</div>
</div>
<p>The load term recovers its <strong>sign pattern</strong> and not much more. The estimate rises then falls, as the generating curve does, so the qualitative claim that priming gives way to satiety survives. The quantitative recovery is poor: the estimated peak sits at load 3.3 against a generating peak at 1.6, root-mean-square error is 0.339 on the log-intensity scale, and the 95% credible band covers the true curve over only 18% of the grid.</p>
<p>That last number is the honest headline. A band that excludes the truth over 82% of the range is <strong>under-covering</strong>, so the intervals here are too narrow to be taken at face value. The random-walk penalty is doing more shrinking than the data warrant, and the 1.5-hour aggregation blurs the covariate within each block. The refractory term is better behaved, at root-mean-square error 0.233. Only 1.6% of rows carry a load above 4, which is where the band is widest and the estimate worst.</p>
<p>The estimated dispersion is effectively Poisson: with the opportunity offset in place, there is no extra-Poisson variation left for the negative binomial to absorb. That estimate comes from a fixed-effect Poisson fit on the same design, not from the Bayesian model’s own dispersion posterior. The negative binomial nests the Poisson, so this costs nothing, but it is worth saying rather than reporting a dispersion that is doing no work.</p>
</section>
<section id="out-of-donor-evaluation" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="out-of-donor-evaluation"><span class="header-section-number">6</span> Out-of-donor evaluation</h2>
<p>The margin was fixed before any model was fitted: <strong>keep the spline model only if it beats the baseline by more than 4 log points and by more than twice the standard error of the difference.</strong> Both conditions, not either.</p>
<div id="lodo" class="cell" data-execution_count="10">
<details open="" class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1">t0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> time.perf_counter()</span>
<span id="cb8-2">folds <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fitting.lodo_glm(fit_panel, design, alpha_hat)</span>
<span id="cb8-3">score <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fitting.score_summary(folds)</span>
<span id="cb8-4">calib <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fitting.calibration_table(fit_panel, design, alpha_hat)</span>
<span id="cb8-5">t0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> lap(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"evaluation"</span>, t0)</span>
<span id="cb8-6"></span>
<span id="cb8-7">kept <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (score[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"diff"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> (score[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"diff"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> score[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"se"</span>])</span>
<span id="cb8-8"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"spline  </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>score[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'gam'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:9.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb8-9"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"linear  </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>score[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'linear'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:9.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb8-10"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"diff    </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>score[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'diff'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:9.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb8-11"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"  se     </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>score[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'se'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:9.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  (across </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>score[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'n_donors'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> donors, the resampling unit)"</span>)</span>
<span id="cb8-12"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"  se_row </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>score[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'se_row'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:9.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  (across </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>score[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'n'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> rows, which are not independent)"</span>)</span>
<span id="cb8-13"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"margin met: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>kept<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>spline    -7818.0
linear    -7827.3
diff         9.30
  se          3.63  (across 6 donors, the resampling unit)
  se_row      5.78  (across 7805 rows, which are not independent)
margin met: True</code></pre>
</div>
</div>
<p>Each donor is held out in turn, both models are refitted by maximum likelihood on the remaining donors, and the held-out donor’s counts are scored. These are <strong>plug-in log scores at the fitted coefficients, not expected log predictive densities</strong> (<a href="https://doi.org/10.1007/s11222-016-9696-4">Vehtari et al.&nbsp;2017</a>): they ignore coefficient uncertainty, carry no random effects, and predict the held-out donor at the average donor. The approximation is identical for both models, so the comparison is fair, but neither number is an ELPD. The scoring rule is the log score, which is strictly proper (<a href="https://doi.org/10.1198/016214506000001437">Gneiting and Raftery 2007</a>). Both fits carry a small ridge on the non-intercept columns, because an unpenalised spline GLM extrapolates without limit on a fold whose training donors do not span the knot range. The standard error is taken across donors, the unit that is actually resampled; the row-wise figure is printed beside it and is the larger of the two here.</p>
<p>The spline model scores 9.3 log points above the baseline with a standard error of 3.6, so it meets the pre-registered margin. Recovering the shape of <img src="https://latex.codecogs.com/png.latex?f_1"> and predicting a new donor better are different achievements, and on this simulation both hold.</p>
<div id="cell-fig-calibration" class="cell" data-fig-height="4" data-fig-width="6.2" data-execution_count="11">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6.2</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.0</span>))</span>
<span id="cb10-2"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name, colour, label <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (</span>
<span id="cb10-3">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gam"</span>, house.ACCENT, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"spline"</span>), (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"linear"</span>, house.CORAL, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"linear baseline"</span>),</span>
<span id="cb10-4">):</span>
<span id="cb10-5">    sub <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> calib[calib[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"model"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> name]</span>
<span id="cb10-6">    ax.errorbar(</span>
<span id="cb10-7">        sub[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"predicted"</span>], sub[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"observed"</span>], yerr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>sub[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"se"</span>],</span>
<span id="cb10-8">        fmt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"o"</span>, ms<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.2</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>colour, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>label, capsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>,</span>
<span id="cb10-9">    )</span>
<span id="cb10-10">lims <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb10-11">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(calib[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"predicted"</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(), calib[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"observed"</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>()) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>,</span>
<span id="cb10-12">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(calib[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"predicted"</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(), calib[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"observed"</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>()) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.2</span>,</span>
<span id="cb10-13">]</span>
<span id="cb10-14">ax.plot(lims, lims, ls<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--"</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>house.MUTED, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.2</span>)</span>
<span id="cb10-15">ax.set_xscale(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"log"</span>)</span>
<span id="cb10-16">ax.set_yscale(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"log"</span>)</span>
<span id="cb10-17">ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"predicted uptake per block"</span>)</span>
<span id="cb10-18">ax.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"observed uptake per block"</span>)</span>
<span id="cb10-19">ax.legend(loc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"upper left"</span>)</span>
<span id="cb10-20">fig.tight_layout()</span>
<span id="cb10-21">plt.show()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-calibration" class="quarto-float quarto-figure quarto-figure-center anchored" alt="A scatter of points with error bars lying close to a dashed diagonal line, in two colours, on log-scaled axes.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-calibration-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/efferocytosis-history-model/index_files/figure-html/fig-calibration-output-1.png" class="img-fluid figure-img" alt="A scatter of points with error bars lying close to a dashed diagonal line, in two colours, on log-scaled axes.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-calibration-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;4: Held-out donor calibration for both models on the simulated experiment. Points are bins of predicted rate; the vertical extent is one standard error of the observed mean. The diagonal is perfect calibration.
</figcaption>
</figure>
</div>
</div>
</div>
</section>
<section id="prediction-and-causation" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="prediction-and-causation"><span class="header-section-number">7</span> Prediction and causation</h2>
<p>Within an arm, a cell’s load is observed, not assigned. Cells that are intrinsically avid eat more in the first wave <em>and</em> more in the second, so the observed relationship between history and later uptake is confounded by that avidity — which no instrument measures.</p>
<p>Latent avidity correlates 0.41 with first-wave uptake and 0.43 with second-wave uptake. Regressing later uptake on observed history gives a slope of 0.0941; holding the latent term fixed — which the simulation can do and an experiment cannot — gives 0.0663. The observed-load curve is biased by 42%.</p>
<p>The assigned arm is different. Arm is randomised across donors, so the broad-versus-focal contrast is a causal contrast of delivery patterns whatever avidity does (<a href="https://miguelhernan.org/whatifbook">Hernan and Robins 2020</a>). The quantity it moves is not the mean.</p>
<div id="cell-fig-share" class="cell" data-fig-height="4" data-fig-width="6.4" data-execution_count="13">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6.4</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.0</span>))</span>
<span id="cb11-2">order <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"broad"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"focal"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"none"</span>]</span>
<span id="cb11-3">means <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(share_by_arm[a]) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> order]</span>
<span id="cb11-4">ax.bar(order, means, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[house.ARM_COLOUR[a] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> order], width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.55</span>, alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.75</span>)</span>
<span id="cb11-5"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, arm <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(order):</span>
<span id="cb11-6">    pts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ns[ns[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"arm"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> arm][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"share"</span>].to_numpy()</span>
<span id="cb11-7">    ax.scatter(</span>
<span id="cb11-8">        np.full(pts.size, i) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> np.linspace(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.12</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.12</span>, pts.size),</span>
<span id="cb11-9">        pts, s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">26</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>house.INK, zorder<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, linewidths<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,</span>
<span id="cb11-10">    )</span>
<span id="cb11-11">ax.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"share of second-wave uptake</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">from first-wave-naive cells"</span>)</span>
<span id="cb11-12">ax.set_ylim(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.08</span>)</span>
<span id="cb11-13">fig.tight_layout()</span>
<span id="cb11-14">plt.show()</span>
<span id="cb11-15"></span>
<span id="cb11-16"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(share_by_arm.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>).to_string())</span>
<span id="cb11-17"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"logit contrast focal - broad = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>contrast_logit<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  95% CI [</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>ci[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>ci[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">]"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-share" class="quarto-float quarto-figure quarto-figure-center anchored" alt="A bar chart of three bars of very different height, with individual donor points scattered over each bar.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-share-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/efferocytosis-history-model/index_files/figure-html/fig-share-output-1.png" class="img-fluid figure-img" alt="A bar chart of three bars of very different height, with individual donor points scattered over each bar.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-share-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;5: Share of second-wave uptake taken by cells that ate nothing in the first wave, by assigned arm, across the simulated donors. Points are donors; bars are the donor mean. The no-first-wave arm is at one by construction and is shown for scale.
</figcaption>
</figure>
</div>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>arm
broad    0.043
focal    0.169
none     1.000
logit contrast focal - broad = 1.58  95% CI [0.98, 2.18]</code></pre>
</div>
</div>
<p>Naive cells supply 4.3% of second-wave uptake in <code>broad</code> and 16.9% in <code>focal</code>, from the same delivered budget and with second-wave means that differ by under two percent. The logit contrast is 1.58 (95% CI 0.98 to 2.18, donor bootstrap).</p>
<p>One caution about that readout. A share is a composition, and a composition moves when <em>any</em> part moves (<a href="https://doi.org/10.3389/fmicb.2017.02224">Gloor et al.&nbsp;2017</a>). The naive-cell share can rise because naive cells ate more or because experienced cells ate less, and those are different mechanisms. Relative abundance is not flux: the share needs the total beside it, which is why the second-wave totals are reported above.</p>
</section>
<section id="design" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="design"><span class="header-section-number">8</span> Design</h2>
<p>What a design has to record, if this model is to be fitted at all:</p>
<ul>
<li><strong>Per-cell event times</strong>, not endpoint counts. The intensity is defined on the history.</li>
<li><strong>Two distinguishable waves.</strong> Separate labels are what make a second-wave event attributable and make “naive” a measurable category.</li>
<li><strong>Targets within reach.</strong> The opportunity offset is not optional, and it cannot be reconstructed after the fact.</li>
<li><strong>Viability and tracking loss.</strong> A cell that stops being tracked is censored, not a cell that stopped eating.</li>
<li><strong>Assigned delivery.</strong> Observed history is confounded; assigned delivery is not.</li>
</ul>
<div id="cell-fig-power" class="cell" data-fig-height="3.8" data-fig-width="6.2" data-execution_count="14">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1">t0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> time.perf_counter()</span>
<span id="cb13-2">power <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fitting.power_curve([<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>], n_rep<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">200</span>)</span>
<span id="cb13-3">t0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> lap(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"power"</span>, t0)</span>
<span id="cb13-4"></span>
<span id="cb13-5">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6.2</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.8</span>))</span>
<span id="cb13-6">ax.plot(power[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"donors"</span>], power[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"power"</span>], <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"o-"</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>house.ACCENT, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>, ms<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>)</span>
<span id="cb13-7">ax.axhline(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>house.MUTED, ls<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--"</span>, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.2</span>)</span>
<span id="cb13-8">ax.annotate(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"80%"</span>, xy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(power[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"donors"</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(), <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>), xytext<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">24</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>),</span>
<span id="cb13-9">            textcoords<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"offset points"</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>house.MUTED, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>)</span>
<span id="cb13-10">ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"donors"</span>)</span>
<span id="cb13-11">ax.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"probability of detection"</span>)</span>
<span id="cb13-12">ax.set_ylim(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.05</span>)</span>
<span id="cb13-13">fig.tight_layout()</span>
<span id="cb13-14">plt.show()</span>
<span id="cb13-15"></span>
<span id="cb13-16"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(power.to_string(index<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>))</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-power" class="quarto-float quarto-figure quarto-figure-center anchored" alt="A rising curve of points against number of donors, flattening near the top of the panel, with a horizontal dashed reference line.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-power-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/efferocytosis-history-model/index_files/figure-html/fig-power-output-1.png" class="img-fluid figure-img" alt="A rising curve of points against number of donors, flattening near the top of the panel, with a horizontal dashed reference line.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-power-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;6: Simulated probability of detecting the assigned broad-versus-focal contrast in the naive-cell share, against the number of donors. Each point is 200 simulated experiments tested with a donor-paired t-test on the logit share at the 5% level.
</figcaption>
</figure>
</div>
</div>
<div class="cell-output cell-output-stdout">
<pre><code> donors  power  n_rep
      3  0.195    200
      4  0.485    200
      5  0.675    200
      6  0.825    200
      8  0.955    200</code></pre>
</div>
</div>
<p>6 donors reach 80% power for this contrast at this effect size. That is a statement about the naive-cell share; the same experiment is badly underpowered for the population mean, which the design deliberately holds still. The test used here is a donor-paired comparison, which is cheaper than the fitted intensity model and therefore conservative.</p>
</section>
<section id="constraints" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="constraints"><span class="header-section-number">9</span> Constraints</h2>
<ul>
<li><strong>The generating process is mine.</strong> The smooths, the digestion time and the neighbour radius were chosen to make the identification problem visible, not measured from an assay. Recovering <img src="https://latex.codecogs.com/png.latex?f_1"> here says the estimator works when the model is true; it says nothing about how it behaves under a mechanism nobody wrote down. <strong>This is modelling opinion, not an empirical finding.</strong></li>
<li><strong>The evaluation is a plug-in score, not an ELPD, and not of the fitted models.</strong> Full leave-one-donor-out with both Bayesian models was out of compute budget, so the fold comparison refits by maximum likelihood with no random effects. Coefficient uncertainty is ignored and the held-out donor is predicted at the average donor. The smooths shown above come from the Bayesian fit; the score below does not.</li>
<li><strong>The score is sensitive to the aggregation grid.</strong> The 1.5 h block was chosen against the refractory time constant, before looking at the fit; a coarser grid narrows the gap between the two models substantially. A comparison that moves with a nuisance choice should be reported with that choice, not without it.</li>
<li><strong>Six donors is a small random-effect sample.</strong> The donor standard deviation is estimated from six values, and its posterior is correspondingly wide.</li>
<li><strong>The credible bands under-cover.</strong> The 95% band for <img src="https://latex.codecogs.com/png.latex?f_1"> contains the generating curve over 18% of its range. Reported rather than tuned away: a penalty prior and an aggregation grid both chosen before fitting should not be adjusted until the intervals look right. The shape conclusion stands; the interval widths do not.</li>
<li><strong>The penalty shrinks more than a P-spline should.</strong> The random walk is written <code>tau * cumsum(cumsum(z))</code>, which puts the constant and linear components of each smooth under the same <code>tau</code> as the curvature. A standard P-spline leaves that two-dimensional null space unpenalised. The result is asymmetric in the basis index — the first coefficient is pinned hardest — which is a specific structural reason the estimated peak sits to the right of the true one, over and above block blurring.</li>
<li><strong>The folds are not fully held out.</strong> The dispersion estimate, the spline knots and the covariate standardisation are all computed on the full panel, so a held-out donor contributes to them. It is the same leakage on both sides, so the comparison stays fair, but each absolute score is optimistic.</li>
<li><strong><img src="https://latex.codecogs.com/png.latex?f_3"> is the weakest of the three.</strong> Depletion and signalling are separable in principle through the offset, but the two generating modes are not compared head to head here.</li>
<li><strong>Simulated tracking loss is non-informative.</strong> Real loss is not: a cell that dies after over-eating is censored precisely because of its history, and that breaks the censoring assumption this model relies on.</li>
<li><strong>One label-free imaging method is cited as an example only</strong> (<a href="https://doi.org/10.7554/eLife.77373">Neto et al.&nbsp;2022</a>). Nothing here rests on it.</li>
</ul>
</section>
<section id="reproducibility" class="level2" data-number="10">
<h2 data-number="10" class="anchored" data-anchor-id="reproducibility"><span class="header-section-number">10</span> Reproducibility</h2>
<div id="session" class="cell" data-execution_count="16">
<div class="cell-output cell-output-stdout">
<pre><code>toy             0.0 s
simulate        0.4 s
design          1.2 s
sample         86.8 s
evaluation      0.6 s
power         110.4 s
total         201.2 s

python 3.12.13  numpy 2.5.3  pandas 3.0.5
pymc 6.3.2  nutpie 0.16.11</code></pre>
</div>
</div>
<p>Every seed is fixed in <code>src/sim.py</code> and at each call site, and the post’s numbers are computed at render time and interpolated into the prose, so a figure and the sentence beside it cannot drift apart.</p>
<p>The simulation, the maximum-likelihood scoring and the power curve are deterministic and reproduce exactly. The posterior summaries need one more thing: nutpie is bit-reproducible for a fixed seed only when the chain-to-core allocation is fixed too, so <code>cores</code> is pinned alongside <code>chains</code> above. Left to the host, the smooth summaries moved in the last reported digit between runs on this machine. To rerun it, build the environment in <code>requirements.txt</code>, register the kernel, and render this document.</p>
<p>Means. Hide. Mechanisms. Offsets. Restore. Opportunity. Assignment. Beats. Observation. Every. Time.</p>
</section>
<section id="references" class="level2" data-number="11">
<h2 data-number="11" class="anchored" data-anchor-id="references"><span class="header-section-number">11</span> References</h2>
<ul>
<li>Abril-Pla, O., et al.&nbsp;(2023). PyMC: a modern, and comprehensive probabilistic programming framework in Python. <em>PeerJ Computer Science</em> 9: e1516. <a href="https://doi.org/10.7717/peerj-cs.1516">doi:10.7717/peerj-cs.1516</a></li>
<li>Andersen, P. K., and Gill, R. D. (1982). Cox’s regression model for counting processes: a large sample study. <em>The Annals of Statistics</em> 10(4). <a href="https://doi.org/10.1214/aos/1176345976">doi:10.1214/aos/1176345976</a></li>
<li>Cook, R. J., and Lawless, J. F. (2007). <em>The Statistical Analysis of Recurrent Events</em>. Springer. <a href="https://doi.org/10.1007/978-0-387-69810-6">doi:10.1007/978-0-387-69810-6</a></li>
<li>Doran, A. C., Yurdagul, A., and Tabas, I. (2020). Efferocytosis in health and disease. <em>Nature Reviews Immunology</em> 20(4): 254-267. <a href="https://doi.org/10.1038/s41577-019-0240-6">doi:10.1038/s41577-019-0240-6</a></li>
<li>Gloor, G. B., Macklaim, J. M., Pawlowsky-Glahn, V., and Egozcue, J. J. (2017). Microbiome datasets are compositional: and this is not optional. <em>Frontiers in Microbiology</em> 8: 2224. <a href="https://doi.org/10.3389/fmicb.2017.02224">doi:10.3389/fmicb.2017.02224</a></li>
<li>Gneiting, T., and Raftery, A. E. (2007). Strictly proper scoring rules, prediction, and estimation. <em>Journal of the American Statistical Association</em> 102(477): 359-378. <a href="https://doi.org/10.1198/016214506000001437">doi:10.1198/016214506000001437</a></li>
<li>Hernan, M. A., and Robins, J. M. (2020). <em>Causal Inference: What If</em>. Chapman and Hall/CRC. <a href="https://miguelhernan.org/whatifbook">miguelhernan.org/whatifbook</a></li>
<li>Medina, C. B., et al.&nbsp;(2020). Metabolites released from apoptotic cells act as tissue messengers. <em>Nature</em> 580: 130-135. <a href="https://doi.org/10.1038/s41586-020-2121-3">doi:10.1038/s41586-020-2121-3</a></li>
<li>Neto, N. G. B., O’Rourke, S. A., Zhang, M., Fitzgerald, H. K., Dunne, A., and Monaghan, M. G. (2022). Non-invasive classification of macrophage polarisation by 2P-FLIM and machine learning. <em>eLife</em> 11: e77373. <a href="https://doi.org/10.7554/eLife.77373">doi:10.7554/eLife.77373</a></li>
<li>Park, D., et al.&nbsp;(2011). Continued clearance of apoptotic cells critically depends on the phagocyte Ucp2 protein. <em>Nature</em> 477(7363): 220-224. <a href="https://doi.org/10.1038/nature10340">doi:10.1038/nature10340</a></li>
<li>Poon, I. K. H., Lucas, C. D., Rossi, A. G., and Ravichandran, K. S. (2014). Apoptotic cell clearance: basic biology and therapeutic potential. <em>Nature Reviews Immunology</em> 14(3): 166-180. <a href="https://doi.org/10.1038/nri3607">doi:10.1038/nri3607</a></li>
<li>Ravichandran, K. S. (2011). Beginnings of a good apoptotic meal: the find-me and eat-me signaling pathways. <em>Immunity</em> 35(4): 445-455. <a href="https://doi.org/10.1016/j.immuni.2011.09.004">doi:10.1016/j.immuni.2011.09.004</a></li>
<li>Vehtari, A., Gelman, A., and Gabry, J. (2017). Practical Bayesian model evaluation using leave-one-out cross-validation and WAIC. <em>Statistics and Computing</em> 27(5): 1413-1432. <a href="https://doi.org/10.1007/s11222-016-9696-4">doi:10.1007/s11222-016-9696-4</a></li>
<li>Wang, Y., et al.&nbsp;(2017). Mitochondrial fission promotes the continued clearance of apoptotic cells by macrophages. <em>Cell</em> 171(2): 331-345.e22. <a href="https://doi.org/10.1016/j.cell.2017.08.041">doi:10.1016/j.cell.2017.08.041</a></li>
<li>Wood, S. N. (2017). <em>Generalized Additive Models: An Introduction with R</em>, 2nd edition. Chapman and Hall/CRC. <a href="https://doi.org/10.1201/9781315370279">doi:10.1201/9781315370279</a></li>
<li>Yurdagul, A., et al.&nbsp;(2020). Macrophage metabolism of apoptotic cell-derived arginine promotes continual efferocytosis and resolution of injury. <em>Cell Metabolism</em> 31(3): 518-533.e10. <a href="https://doi.org/10.1016/j.cmet.2020.01.001">doi:10.1016/j.cmet.2020.01.001</a></li>
<li><a href="../causality-from-toddlers-to-do-calculus/">Causality: From Toddlers to Do-Calculus</a> - assignment against observation.</li>
<li><a href="../sir-training-vs-calibration/">Training Is Not Calibration</a> - fitting a mechanistic model to simulated epidemic data.</li>
</ul>


</section>

 ]]></description>
  <category>Bayesian</category>
  <category>Biology</category>
  <category>Numerical Methods</category>
  <category>Statistics</category>
  <guid>https://project-delphi.github.io/ml-blog/posts/efferocytosis-history-model/</guid>
  <pubDate>Thu, 17 Sep 2026 05:00:00 GMT</pubDate>
  <media:content url="https://project-delphi.github.io/ml-blog/posts/efferocytosis-history-model/cover.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>CP or Tucker</title>
  <dc:creator>Ravi Kalia</dc:creator>
  <link>https://project-delphi.github.io/ml-blog/posts/cp-or-tucker/</link>
  <description><![CDATA[ 





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/cp-or-tucker/cover.png" class="img-fluid figure-img"></p>
<figcaption>CP or Tucker</figcaption>
</figure>
</div>
<p><code>parafac(X, rank=3)</code> and <code>tucker(X, rank=[3, 3, 3])</code> take the same array, hand back factor matrices, and differ in the call by one integer against three — so they look like two settings of one algorithm. They are not. CP returns components that, under a condition on the factors, are the only ones that fit the array; Tucker returns a basis per mode and a small core, fixed only up to a rotation that slides between them.</p>
<p>The gap shows up when someone reads the output. A CP component can be named and argued about. A Tucker factor column is a coordinate: rotate it, push the inverse rotation into the core, and the fit does not change. Naming a Tucker column picks one of infinitely many equally good answers.</p>
<p>Every worked form here is three-way. The algebra for general <img src="https://latex.codecogs.com/png.latex?N">, with unfoldings, HOSVD, tensor train and the t-product, is in <a href="../tensor-factorizations/">Tensor Factorizations and Tensor Inverses</a>. What these compressions cost on real video and audio is in <a href="../uses-of-tensor-factorizations/">Uses of Tensor Factorizations</a>.</p>
<section id="the-two-arrays" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="the-two-arrays"><span class="header-section-number">1</span> The two arrays</h2>
<p>Two shapes recur, and they pull in opposite directions.</p>
<p>A ratings table with a time axis — user by movie by month — gets asked what the recurring patterns are. The answer is meant to be read out: <em>this group of users, these films, these months</em>.</p>
<p>A colour image is height by width by colour, and gets asked how few numbers can stand in for it. Nobody reads a picture’s factors.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/cp-or-tucker/cube-ml.gif" class="img-fluid figure-img"></p>
<figcaption>A three-way array. Each axis is a mode, and no two modes are interchangeable.</figcaption>
</figure>
</div>
</section>
<section id="figure-data" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="figure-data"><span class="header-section-number">2</span> Figure data</h2>
<p>The volumes in the figures are synthetic and no decomposition is fitted to them.</p>
<ul>
<li>The three CP components are outer products of Gaussian bumps on a <img src="https://latex.codecogs.com/png.latex?12%5Ctimes%2012%5Ctimes%0A12"> grid, each centred somewhere else in it, so a component reads as one blob. They stand in for concepts recovered from a ratings cube, which is far too large and too sparse to draw.</li>
<li>The Tucker figure is a layout: a full cube, a small core, and three random <img src="https://latex.codecogs.com/png.latex?12%5Ctimes%20R_n"> heatmaps standing in for factor matrices. It is not a fit of the CP volumes.</li>
<li>The rotating cubes are labelled boxes, not data at all.</li>
<li><code>src/make_cubes.py</code> writes all five images. The page has no executable cells.</li>
</ul>
<p>Which decomposition answers which question is settled by the algebra, so a MovieLens-shaped table or a public-domain film clip would not change the answer. What the compression costs does need measured data, and is measured in <a href="../uses-of-tensor-factorizations/">Uses of Tensor Factorizations</a>.</p>
</section>
<section id="notation" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="notation"><span class="header-section-number">3</span> Notation</h2>
<p>For a three-way <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BX%7D%5Cin%5Cmathbb%7BR%7D%5E%7BI%5Ctimes%20J%5Ctimes%20K%7D">:</p>
<ul>
<li><strong>Mode.</strong> One axis. Mode 1 has length <img src="https://latex.codecogs.com/png.latex?I">, mode 2 has length <img src="https://latex.codecogs.com/png.latex?J">, mode 3 has length <img src="https://latex.codecogs.com/png.latex?K">.</li>
<li><strong>Outer product.</strong> <img src="https://latex.codecogs.com/png.latex?a%5Ccirc%20b%5Ccirc%20c"> is the <img src="https://latex.codecogs.com/png.latex?I%5Ctimes%20J%5Ctimes%20K"> array with entries <img src="https://latex.codecogs.com/png.latex?a_i%20b_j%20c_k">. An array that can be written this way is <strong>rank-1</strong>.</li>
<li><strong>Mode-<img src="https://latex.codecogs.com/png.latex?n"> product.</strong> <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BG%7D%5Ctimes_n%20U"> multiplies every mode-<img src="https://latex.codecogs.com/png.latex?n"> fiber of <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BG%7D"> by <img src="https://latex.codecogs.com/png.latex?U">, leaving the other modes alone.</li>
<li><strong>Mode-<img src="https://latex.codecogs.com/png.latex?n"> unfolding.</strong> <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BX%7D_%7B(n)%7D">, the matrix whose columns are the mode-<img src="https://latex.codecogs.com/png.latex?n"> fibers.</li>
</ul>
</section>
<section id="cp" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="cp"><span class="header-section-number">4</span> CP</h2>
<p>CP — CANDECOMP/PARAFAC — writes the array as a sum of <img src="https://latex.codecogs.com/png.latex?R"> rank-1 terms:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7BX%7D%20%5Capprox%20%5Csum_%7Br=1%7D%5E%7BR%7D%20%5Clambda_r%5C,%20a_r%20%5Ccirc%20b_r%20%5Ccirc%20c_r%20.%0A"></p>
<p>One integer <img src="https://latex.codecogs.com/png.latex?R"> sets the model. Each term carries one vector per mode, so a term is a triple: a weighting of users, a weighting of movies, a weighting of months.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/cp-or-tucker/fig-cp.png" class="img-fluid figure-img"></p>
<figcaption>Three rank-1 terms and the array they add up to. Each term is an outer product of three Gaussian bumps, so it occupies one compact region. Two of the three sit close enough to merge into one lump in the sum: separating them takes a decomposition, not a threshold.</figcaption>
</figure>
</div>
<section id="uniqueness" class="level3" data-number="4.1">
<h3 data-number="4.1" class="anchored" data-anchor-id="uniqueness"><span class="header-section-number">4.1</span> Uniqueness</h3>
<p>CP’s value is that the triples are pinned down. Write <img src="https://latex.codecogs.com/png.latex?k_A"> for the <strong>k-rank</strong> of the factor matrix <img src="https://latex.codecogs.com/png.latex?A"> — the largest <img src="https://latex.codecogs.com/png.latex?k"> such that every <img src="https://latex.codecogs.com/png.latex?k"> columns of <img src="https://latex.codecogs.com/png.latex?A"> are linearly independent. Kruskal’s condition is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ak_A%20+%20k_B%20+%20k_C%20%5C%20%5Cge%5C%202R%20+%202%20.%0A"></p>
<p>When it holds, the rank-<img src="https://latex.codecogs.com/png.latex?R"> CP decomposition is unique up to permuting the <img src="https://latex.codecogs.com/png.latex?R"> terms and rescaling the three vectors inside a term against each other. No rotation is available. That is why a CP component survives being given a name: a second analyst fitting the same array recovers the same triples in a different order.</p>
<p>Matrix factorization has no such property. <img src="https://latex.codecogs.com/png.latex?AB%5E%5Ctop%20=%20(AQ)(BQ%5E%7B-%5Ctop%7D)%5E%5Ctop"> for any invertible <img src="https://latex.codecogs.com/png.latex?Q">, so PCA needs an extra rule — maximise variance, or rotate to a varimax criterion — before a component means anything. The third mode is what makes that rule unnecessary.</p>
</section>
<section id="storage" class="level3" data-number="4.2">
<h3 data-number="4.2" class="anchored" data-anchor-id="storage"><span class="header-section-number">4.2</span> Storage</h3>
<p><img src="https://latex.codecogs.com/png.latex?R"> components on an <img src="https://latex.codecogs.com/png.latex?I%5Ctimes%20J%5Ctimes%20K"> array need <img src="https://latex.codecogs.com/png.latex?R(I+J+K)"> numbers, with each <img src="https://latex.codecogs.com/png.latex?%5Clambda_r"> scaled into one of its own three vectors rather than stored. At <img src="https://latex.codecogs.com/png.latex?R=3"> on <img src="https://latex.codecogs.com/png.latex?12%5Ctimes%2012%5Ctimes%2012"> that is 108 against the dense 1728.</p>
</section>
</section>
<section id="tucker" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="tucker"><span class="header-section-number">5</span> Tucker</h2>
<p>Tucker compresses each mode against its own budget:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7BX%7D%20%5Capprox%20%5Cmathcal%7BG%7D%20%5Ctimes_1%20U%20%5Ctimes_2%20V%20%5Ctimes_3%20W,%0A%5Cqquad%20%5Cmathcal%7BG%7D%5Cin%5Cmathbb%7BR%7D%5E%7BR_1%5Ctimes%20R_2%5Ctimes%20R_3%7D.%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?U"> is <img src="https://latex.codecogs.com/png.latex?I%5Ctimes%20R_1">, <img src="https://latex.codecogs.com/png.latex?V"> is <img src="https://latex.codecogs.com/png.latex?J%5Ctimes%20R_2">, <img src="https://latex.codecogs.com/png.latex?W"> is <img src="https://latex.codecogs.com/png.latex?K%5Ctimes%20R_3">. The triple <img src="https://latex.codecogs.com/png.latex?(R_1,R_2,R_3)"> is the <strong>multilinear rank</strong>, and it is three separate dials: the ranks of the three unfoldings.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/cp-or-tucker/fig-tucker.png" class="img-fluid figure-img"></p>
<figcaption>A <img src="https://latex.codecogs.com/png.latex?12%5Ctimes%2012%5Ctimes%2012"> array, a <img src="https://latex.codecogs.com/png.latex?4%5Ctimes%204%5Ctimes%203"> core, and the three factor matrices <img src="https://latex.codecogs.com/png.latex?U"> (<img src="https://latex.codecogs.com/png.latex?12%5Ctimes%204">), <img src="https://latex.codecogs.com/png.latex?V"> (<img src="https://latex.codecogs.com/png.latex?12%5Ctimes%204">) and <img src="https://latex.codecogs.com/png.latex?W"> (<img src="https://latex.codecogs.com/png.latex?12%5Ctimes%0A3">).</figcaption>
</figure>
</div>
<p>CP is the special case where <img src="https://latex.codecogs.com/png.latex?R_1=R_2=R_3=R"> and <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BG%7D"> is zero off its superdiagonal. That shared ancestry is why the two library calls look alike.</p>
<p>The dials move independently. A colour image has three colour slices and a good compression keeps all three, while the height and width modes give up most of their length.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/cp-or-tucker/cube-film.gif" class="img-fluid figure-img"></p>
<figcaption>Each picture mode gives up most of its length while the colour mode, three slices deep, keeps all three. The shrunk box is the core: the relabel to <img src="https://latex.codecogs.com/png.latex?R_1,R_2,R_3"> marks where its axes stop counting pixels and start counting components.</figcaption>
</figure>
</div>
<section id="rotational-freedom" class="level3" data-number="5.1">
<h3 data-number="5.1" class="anchored" data-anchor-id="rotational-freedom"><span class="header-section-number">5.1</span> Rotational freedom</h3>
<p>For any invertible <img src="https://latex.codecogs.com/png.latex?Q%5Cin%5Cmathbb%7BR%7D%5E%7BR_1%5Ctimes%20R_1%7D">,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7BG%7D%20%5Ctimes_1%20U%0A=%20%5Cleft(%5Cmathcal%7BG%7D%20%5Ctimes_1%20Q%5E%7B-1%7D%5Cright)%20%5Ctimes_1%20(UQ),%0A"></p>
<p>and the same holds in modes 2 and 3. The fit does not change. Requiring orthonormal columns, as the higher-order SVD does, narrows <img src="https://latex.codecogs.com/png.latex?Q"> to an orthogonal matrix but does not remove it: the factors are a basis for a subspace, and the subspace is what Tucker identifies.</p>
<p>Truncated HOSVD is the cheap non-iterative way to get that basis: take the SVD of each unfolding and keep <img src="https://latex.codecogs.com/png.latex?R_n"> left singular vectors. It is not the best multilinear-rank-<img src="https://latex.codecogs.com/png.latex?(R_1,R_2,R_3)"> approximation, but for an <img src="https://latex.codecogs.com/png.latex?N">-way array its error is within <img src="https://latex.codecogs.com/png.latex?%5Csqrt%7BN%7D"> of the best one. That is close enough to start an iterative fit from.</p>
</section>
<section id="storage-1" class="level3" data-number="5.2">
<h3 data-number="5.2" class="anchored" data-anchor-id="storage-1"><span class="header-section-number">5.2</span> Storage</h3>
<p><img src="https://latex.codecogs.com/png.latex?R_1R_2R_3%20+%20IR_1%20+%20JR_2%20+%20KR_3"> numbers. At <img src="https://latex.codecogs.com/png.latex?(4,4,3)"> on <img src="https://latex.codecogs.com/png.latex?12%5Ctimes%2012%5Ctimes%2012"> that is <img src="https://latex.codecogs.com/png.latex?48%20+%2048%20+%2048%20+%2036%20=%20180">, against the dense 1728.</p>
<p>Two parameter counts on one toy array settle nothing: 108 for CP at <img src="https://latex.codecogs.com/png.latex?R=3"> and 180 for Tucker at <img src="https://latex.codecogs.com/png.latex?(4,4,3)"> compare two models that fit the array to different accuracies. Storage separates the two only at fixed error, and at fixed error the winner depends on whether the array really is a short sum of rank-1 terms.</p>
</section>
</section>
<section id="choosing" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="choosing"><span class="header-section-number">6</span> Choosing</h2>
<p>The question decides:</p>
<ul>
<li><strong>“What are the parts, and what do they mean?”</strong> CP — but check where Kruskal’s condition actually bites before leaning on it. Generic factors have full k-rank, so for <img src="https://latex.codecogs.com/png.latex?R%20%5Cle%20%5Cmin(I,J,K)"> it reduces to <img src="https://latex.codecogs.com/png.latex?3R%20%5Cge%202R+2"> and holds for every <img src="https://latex.codecogs.com/png.latex?R%20%5Cge%202">. It bites when <img src="https://latex.codecogs.com/png.latex?R"> exceeds a mode’s length, and when a fit returns near-collinear columns, whose k-rank is numerically lower than the exact arithmetic says.</li>
<li><strong>“How few numbers can stand in for this?”</strong> Tucker. The per-mode budget is the feature — spend where the array has structure, and leave a short mode alone.</li>
<li><strong>“Which modes carry the variation?”</strong> Tucker, read as multilinear rank. The three unfolding ranks answer directly, and the factors need no interpretation to do it.</li>
<li><strong>“How many components are there?”</strong> Neither, not directly. Determining tensor rank is NP-hard, so <img src="https://latex.codecogs.com/png.latex?R"> is chosen by fitting a range and watching the residual and the stability of the factors across restarts.</li>
</ul>
<p>A useful order when both are on the table: fit Tucker first, because it always has a best fit and gives the multilinear rank cheaply, then fit CP inside the compressed core if the components are going to be read.</p>
</section>
<section id="constraints" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="constraints"><span class="header-section-number">7</span> Constraints</h2>
<p>CP:</p>
<ul>
<li>A best rank-<img src="https://latex.codecogs.com/png.latex?R"> approximation need not exist for <img src="https://latex.codecogs.com/png.latex?R%20%5Cge%202">. The infimum can be approached by factors that grow without bound and cancel — the ALS iterates diverge while the residual keeps falling. Rising factor norms are the tell.</li>
<li>ALS gets stuck on long plateaus where the residual barely moves for hundreds of iterations and then drops. Stopping on “no progress” stops early.</li>
<li>Kruskal’s condition is sufficient, not necessary. Failing it means uniqueness is not guaranteed, not that the fit is wrong.</li>
</ul>
<p>Tucker:</p>
<ul>
<li>The core is <img src="https://latex.codecogs.com/png.latex?%5Cprod_n%20R_n">, exponential in the number of modes. At eight modes with <img src="https://latex.codecogs.com/png.latex?R_n%20=%2010"> the core alone is <img src="https://latex.codecogs.com/png.latex?10%5E8"> numbers. Tensor train exists to avoid that.</li>
<li>The factors are bases. A heatmap of <img src="https://latex.codecogs.com/png.latex?U"> is not a picture of concepts, and reading it as one is the mistake CP exists to prevent.</li>
</ul>
<p>Uniqueness. Or. Compression. CP. Names. Parts. Tucker. Spends. Budget. Ask. First.</p>
</section>
<section id="references" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="references"><span class="header-section-number">8</span> References</h2>
<ul>
<li>Kolda, T. G., and Bader, B. W. (2009). <a href="https://doi.org/10.1137/07070111X">Tensor decompositions and applications</a>. <em>SIAM Review</em> 51(3). The survey both models are usually cited from.</li>
<li>Kruskal, J. B. (1977). <a href="https://doi.org/10.1016/0024-3795(77)90069-6">Three-way arrays: rank and uniqueness of trilinear decompositions</a>. <em>Linear Algebra and its Applications</em> 18(2). The k-rank condition.</li>
<li>De Lathauwer, L., De Moor, B., and Vandewalle, J. (2000). <a href="https://doi.org/10.1137/S0895479896305696">A multilinear singular value decomposition</a>. <em>SIAM Journal on Matrix Analysis and Applications</em> 21(4). HOSVD and the <img src="https://latex.codecogs.com/png.latex?%5Csqrt%7BN%7D"> bound.</li>
<li>de Silva, V., and Lim, L.-H. (2008). <a href="https://doi.org/10.1137/06066518X">Tensor rank and the ill-posedness of the best low-rank approximation problem</a>. <em>SIAM Journal on Matrix Analysis and Applications</em> 30(3). Why a best rank-<img src="https://latex.codecogs.com/png.latex?R"> fit can fail to exist.</li>
<li>Hillar, C. J., and Lim, L.-H. (2013). <a href="https://doi.org/10.1145/2512329">Most tensor problems are NP-hard</a>. <em>Journal of the ACM</em> 60(6). Tensor rank among them.</li>
<li><a href="../tensor-factorizations/">Tensor Factorizations and Tensor Inverses</a> — the algebra for general <img src="https://latex.codecogs.com/png.latex?N">, plus tensor train, t-SVD and four tensor inverses.</li>
<li><a href="../uses-of-tensor-factorizations/">Uses of Tensor Factorizations</a> — what these compressions cost on a film clip, a conv kernel and a dense layer.</li>
</ul>


</section>

 ]]></description>
  <category>Linear Algebra</category>
  <category>Machine Learning</category>
  <category>Numerical Methods</category>
  <category>Tensors</category>
  <guid>https://project-delphi.github.io/ml-blog/posts/cp-or-tucker/</guid>
  <pubDate>Tue, 15 Sep 2026 05:00:00 GMT</pubDate>
  <media:content url="https://project-delphi.github.io/ml-blog/posts/cp-or-tucker/cover.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Probabilistic PCA</title>
  <dc:creator>Ravi Kalia</dc:creator>
  <link>https://project-delphi.github.io/ml-blog/posts/probabilistic-pca/</link>
  <description><![CDATA[ 





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/probabilistic-pca/cover.png" class="img-fluid figure-img" alt="A two-sensor cloud with classical PCA and PPCA reconstructions, beside an example of a posterior distribution for an unobserved coordinate."></p>
<figcaption>Probabilistic PCA</figcaption>
</figure>
</div>
<p>Probabilistic PCA estimates a noisy low-dimensional signal with a Gaussian model: its maximum-likelihood fit recovers PCA’s principal subspace, while its posterior reconstruction shrinks toward the mean.</p>
<p>A classical PCA projection gives us coordinates and a reconstruction error. If we also want uncertainty about the unobserved signal, we need assumptions about how the measurements were generated; that is the role of the probability model.</p>
<p><a href="../six-views-of-pca/">Six Views of PCA</a> derives the geometric optimization. We use that subspace result here and distinguish three objects: the fitted distribution of observations, the posterior latent coordinates, and the reconstructed signal.</p>
<section id="gaussian-latent-variable-model" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="gaussian-latent-variable-model"><span class="header-section-number">1</span> Gaussian latent-variable model</h2>
<p>Suppose a few unobserved quantities influence many measurements. Write one observation as</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ax=%5Cmu+Wz+%5Cvarepsilon,%5Cqquad%0Az%5Csim%20N(0,I_k),%5Cqquad%0A%5Cvarepsilon%5Csim%20N(0,%5Csigma%5E2%20I_d),%0A%5Cqquad%20z%5Cperp%5Cvarepsilon.%0A"></p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?x%5Cin%5Cmathbb%20R%5Ed"> contains observed features; <img src="https://latex.codecogs.com/png.latex?z%5Cin%5Cmathbb%20R%5Ek"> contains latent, or unobserved, coordinates, with <img src="https://latex.codecogs.com/png.latex?k%3Cd">.</li>
<li><img src="https://latex.codecogs.com/png.latex?W%5Cin%5Cmathbb%20R%5E%7Bd%5Ctimes%20k%7D"> maps latent coordinates to features. Its columns need not be orthonormal.</li>
<li><img src="https://latex.codecogs.com/png.latex?%5Cmu"> is the observation mean. We assume independent observations with common parameters.</li>
<li><img src="https://latex.codecogs.com/png.latex?%5Csigma%5E2%3E0"> is the noise variance in every feature direction. This equal-variance assumption is called <strong>isotropic noise</strong>; it refers to the chosen measurement coordinates.</li>
</ul>
<p>Independent Gaussian contributions add their covariances, giving</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ax%5Csim%20N(%5Cmu,C),%5Cqquad%20C=WW%5E%5Ctop+%5Csigma%5E2I_d.%0A"></p>
<p>The signal covariance <img src="https://latex.codecogs.com/png.latex?WW%5E%5Ctop"> has rank at most <img src="https://latex.codecogs.com/png.latex?k">. Noise adds variance in all <img src="https://latex.codecogs.com/png.latex?d"> directions, so the observation distribution has full rank even though its signal lies in a smaller subspace.</p>
<p>This is the PPCA model introduced by <a href="https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/bishop-ppca-jrss.pdf">Tipping and Bishop (1999)</a>. Fixing the latent covariance to <img src="https://latex.codecogs.com/png.latex?I_k"> leaves a rotation ambiguity: <img src="https://latex.codecogs.com/png.latex?W"> and <img src="https://latex.codecogs.com/png.latex?WR"> describe the same observation distribution whenever <img src="https://latex.codecogs.com/png.latex?R%5E%5Ctop%20R=I_k">.</p>
</section>
<section id="maximum-likelihood-fit" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="maximum-likelihood-fit"><span class="header-section-number">2</span> Maximum-likelihood fit</h2>
<section id="covariance-convention" class="level3" data-number="2.1">
<h3 data-number="2.1" class="anchored" data-anchor-id="covariance-convention"><span class="header-section-number">2.1</span> Covariance convention</h3>
<p>For <img src="https://latex.codecogs.com/png.latex?n"> complete observations, the fitted mean is <img src="https://latex.codecogs.com/png.latex?%5Chat%5Cmu=%5Cbar%20x">. Let <img src="https://latex.codecogs.com/png.latex?X"> contain the centred observations as rows, and define</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS_%7B%5Cmathrm%7BML%7D%7D=%5Cfrac1nX%5E%5Ctop%20X%0A=V%5Coperatorname%7Bdiag%7D(%5Clambda_1,%5Cldots,%5Clambda_d)V%5E%5Ctop,%0A%5Cqquad%20%5Clambda_1%5Cge%5Ccdots%5Cge%5Clambda_d.%0A"></p>
<p>The denominator is <img src="https://latex.codecogs.com/png.latex?n">, as required by this Gaussian likelihood. Eigenvalues computed using <img src="https://latex.codecogs.com/png.latex?n-1"> must be multiplied by <img src="https://latex.codecogs.com/png.latex?(n-1)/n"> before using the PPCA formulas; the eigenvectors are unchanged.</p>
<p>Apart from a constant, the log likelihood is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cell(W,%5Csigma%5E2)%0A=-%5Cfrac%20n2%5Cleft%5B%5Clog%5Cdet%20C+%0A%5Coperatorname%7Btr%7D(C%5E%7B-1%7DS_%7B%5Cmathrm%7BML%7D%7D)%5Cright%5D.%0A"></p>
<p>The determinant term penalizes an excessively diffuse distribution. The trace term penalizes observed variation that the model assigns too little variance to.</p>
</section>
<section id="principal-subspace-and-residual-variance" class="level3" data-number="2.2">
<h3 data-number="2.2" class="anchored" data-anchor-id="principal-subspace-and-residual-variance"><span class="header-section-number">2.2</span> Principal subspace and residual variance</h3>
<p>At the maximum, the model aligns its signal subspace with the leading sample eigenvectors. Let <img src="https://latex.codecogs.com/png.latex?c_j"> denote its covariance eigenvalues: the first <img src="https://latex.codecogs.com/png.latex?k"> can exceed <img src="https://latex.codecogs.com/png.latex?%5Csigma%5E2">, while the remaining <img src="https://latex.codecogs.com/png.latex?d-k"> all equal <img src="https://latex.codecogs.com/png.latex?%5Csigma%5E2">.</p>
<p>With that alignment, minimizing the negative likelihood reduces to</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Csum_%7Bj=1%7D%5Ek%5Cleft(%5Clog%20c_j+%5Cfrac%7B%5Clambda_j%7D%7Bc_j%7D%5Cright)%0A+(d-k)%5Clog%5Csigma%5E2+%0A%5Cfrac%7B%5Csum_%7Bj=k+1%7D%5Ed%5Clambda_j%7D%7B%5Csigma%5E2%7D.%0A"></p>
<p>Differentiating gives <img src="https://latex.codecogs.com/png.latex?c_j=%5Clambda_j"> for retained directions and an average for the discarded directions. Thus</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Chat%5Csigma%5E2=%5Cfrac%7B1%7D%7Bd-k%7D%5Csum_%7Bj=k+1%7D%5E%7Bd%7D%5Clambda_j,%0A%5Cqquad%0A%5Chat%20W=V_k(%5CLambda_k-%5Chat%5Csigma%5E2I_k)%5E%7B1/2%7DR.%0A"></p>
<p>Here <img src="https://latex.codecogs.com/png.latex?%5CLambda_k=%5Coperatorname%7Bdiag%7D(%5Clambda_1,%5Cldots,%5Clambda_k)"> and <img src="https://latex.codecogs.com/png.latex?R"> is any orthogonal <img src="https://latex.codecogs.com/png.latex?k%5Ctimes%20k"> matrix. The construction matches retained covariance eigenvalues and replaces discarded ones with their average.</p>
<ul>
<li>If <img src="https://latex.codecogs.com/png.latex?%5Clambda_k%3E%5Chat%5Csigma%5E2%3E0">, the columns of <img src="https://latex.codecogs.com/png.latex?%5Chat%20W"> span the classical principal subspace. The scales of those columns differ from unit PCA axes.</li>
<li>If <img src="https://latex.codecogs.com/png.latex?%5Clambda_k=%5Chat%5Csigma%5E2">, the corresponding loading vanishes, so the effective signal dimension is smaller than the requested <img src="https://latex.codecogs.com/png.latex?k">.</li>
<li>If the discarded variance is zero, the solution lies at a singular zero-noise boundary; it is not a nonsingular Gaussian density.</li>
</ul>
<p>The leading-subspace result follows from the likelihood optimization in Tipping and Bishop. It is agreement between optimizers under a model, not an identity between the likelihood and PCA’s reconstruction error for every <img src="https://latex.codecogs.com/png.latex?W">.</p>
</section>
</section>
<section id="posterior-coordinates-and-reconstruction" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="posterior-coordinates-and-reconstruction"><span class="header-section-number">3</span> Posterior coordinates and reconstruction</h2>
<section id="conditional-distribution" class="level3" data-number="3.1">
<h3 data-number="3.1" class="anchored" data-anchor-id="conditional-distribution"><span class="header-section-number">3.1</span> Conditional distribution</h3>
<p>For a new measurement, we infer <img src="https://latex.codecogs.com/png.latex?z"> with fitted parameters held fixed. Let <img src="https://latex.codecogs.com/png.latex?M=W%5E%5Ctop%20W+%5Csigma%5E2I_k">; completing the square in the Gaussian density gives</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Az%5Cmid%20x%5Csim%20N(m_z,%5CSigma_z),%5Cqquad%0Am_z=M%5E%7B-1%7DW%5E%5Ctop(x-%5Cmu),%5Cqquad%0A%5CSigma_z=%5Csigma%5E2M%5E%7B-1%7D.%0A"></p>
<p>The posterior expresses uncertainty about this observation’s latent coordinates. It does not include uncertainty in the fitted parameters <img src="https://latex.codecogs.com/png.latex?W">, <img src="https://latex.codecogs.com/png.latex?%5Cmu">, or <img src="https://latex.codecogs.com/png.latex?%5Csigma%5E2">.</p>
<p>Choose <img src="https://latex.codecogs.com/png.latex?R=I_k"> to compare directly with classical scores <img src="https://latex.codecogs.com/png.latex?a_j=v_j%5E%5Ctop(x-%5Cmu)">. At the maximum-likelihood fit,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(m_z)_j=%5Cfrac%7B%5Csqrt%7B%5Clambda_j-%5Chat%5Csigma%5E2%7D%7D%7B%5Clambda_j%7Da_j,%0A%5Cqquad%0A(%5CSigma_z)_%7Bjj%7D=%5Cfrac%7B%5Chat%5Csigma%5E2%7D%7B%5Clambda_j%7D.%0A"></p>
<p>Latent coordinates use the model’s unit-variance prior scale. They are not the ordinary PCA scores <img src="https://latex.codecogs.com/png.latex?a_j">.</p>
</section>
<section id="signal-reconstruction" class="level3" data-number="3.2">
<h3 data-number="3.2" class="anchored" data-anchor-id="signal-reconstruction"><span class="header-section-number">3.2</span> Signal reconstruction</h3>
<p>The posterior mean of the noise-free signal <img src="https://latex.codecogs.com/png.latex?s=%5Cmu+Wz"> is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbb%20E%5Bs%5Cmid%20x%5D%0A=%5Cmu+Wm_z%0A=%5Cmu+V_k%5Coperatorname%7Bdiag%7D%5C!%5Cleft(1-%5Cfrac%7B%5Chat%5Csigma%5E2%7D%7B%5Clambda_j%7D%5Cright)%0AV_k%5E%5Ctop(x-%5Cmu).%0A"></p>
<p>Classical PCA retains <img src="https://latex.codecogs.com/png.latex?a_j"> unchanged in the selected subspace. PPCA multiplies it by <img src="https://latex.codecogs.com/png.latex?1-%5Chat%5Csigma%5E2/%5Clambda_j">: when a retained direction has little variance beyond the fitted noise, the model attributes less of an extreme measurement to signal.</p>
<p>For a fixed principal subspace with positive retained eigenvalues, the reconstruction approaches the classical orthogonal projection as <img src="https://latex.codecogs.com/png.latex?%5Csigma%5E2%5Cto0">. The latent mean instead approaches <img src="https://latex.codecogs.com/png.latex?%5CLambda_k%5E%7B-1/2%7DV_k%5E%5Ctop(x-%5Cmu)">, up to the latent rotation; its rescaling does not disappear.</p>
</section>
<section id="worked-calculation" class="level3" data-number="3.3">
<h3 data-number="3.3" class="anchored" data-anchor-id="worked-calculation"><span class="header-section-number">3.3</span> Worked calculation</h3>
<p>Consider a centred sample with eigenvalues <img src="https://latex.codecogs.com/png.latex?(9,4,1)"> under the <img src="https://latex.codecogs.com/png.latex?1/n"> convention and axes equal to the coordinate axes. This is an illustrative covariance, chosen to make the residual average explicit, not an empirical dataset.</p>
<p>For <img src="https://latex.codecogs.com/png.latex?k=1">, we obtain <img src="https://latex.codecogs.com/png.latex?%5Chat%5Csigma%5E2=(4+1)/2=2.5"> and <img src="https://latex.codecogs.com/png.latex?%5Chat%20W=(%5Csqrt%7B6.5%7D,0,0)%5E%5Ctop">. For an observation <img src="https://latex.codecogs.com/png.latex?x=(3,1,0)%5E%5Ctop">, compare the three outputs:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Object</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Classical PCA score</td>
<td><img src="https://latex.codecogs.com/png.latex?a_1=3"></td>
</tr>
<tr class="even">
<td>Posterior latent mean</td>
<td><img src="https://latex.codecogs.com/png.latex?m_z=3%5Csqrt%7B6.5%7D/9%5Capprox0.850"></td>
</tr>
<tr class="odd">
<td>Classical reconstruction</td>
<td><img src="https://latex.codecogs.com/png.latex?(3,0,0)%5E%5Ctop"></td>
</tr>
<tr class="even">
<td>Posterior signal mean</td>
<td><img src="https://latex.codecogs.com/png.latex?(13/6,0,0)%5E%5Ctop%5Capprox(2.167,0,0)%5E%5Ctop"></td>
</tr>
<tr class="odd">
<td>Posterior latent variance</td>
<td><img src="https://latex.codecogs.com/png.latex?2.5/9%5Capprox0.278"></td>
</tr>
</tbody>
</table>
<p>Both reconstructions lie on the first coordinate axis. The posterior signal estimate is closer to the mean, while its latent coordinate is expressed on a different scale.</p>
</section>
</section>
<section id="reproducible-sensor-example" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="reproducible-sensor-example"><span class="header-section-number">4</span> Reproducible sensor example</h2>
<section id="data-and-fit" class="level3" data-number="4.1">
<h3 data-number="4.1" class="anchored" data-anchor-id="data-and-fit"><span class="header-section-number">4.1</span> Data and fit</h3>
<p>We generate 500 synthetic paired sensor readings from <img src="https://latex.codecogs.com/png.latex?z%5Csim%20N(0,1)">, <img src="https://latex.codecogs.com/png.latex?W=(2.2,0.9)%5E%5Ctop">, <img src="https://latex.codecogs.com/png.latex?%5Cmu=(1,-1)%5E%5Ctop">, and independent sensor noise with standard deviation <img src="https://latex.codecogs.com/png.latex?0.8">. The example represents two calibrated sensors responding to one shared quantity with equal noise variance.</p>
<p>There is no external collector: the code generates both the signal and the noise. We ask whether the fitted model recovers the shared direction and changes reconstruction as derived; a real application would need to check the equal-noise assumption before trusting its uncertainty estimates.</p>
<div id="ppca-fit" class="cell" data-execution_count="2">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1">rng <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.random.default_rng(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">19</span>)</span>
<span id="cb1-2">n, d, k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">500</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb1-3">W_true <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.2</span>], [<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.9</span>]])</span>
<span id="cb1-4">mu_true <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>])</span>
<span id="cb1-5">z_true <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rng.normal(size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(n, k))</span>
<span id="cb1-6">observations <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mu_true <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> z_true <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> W_true.T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> rng.normal(scale<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>, size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(n, d))</span>
<span id="cb1-7">mu <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> observations.mean(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb1-8">X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> observations <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> mu</span>
<span id="cb1-9">_, singular, Vt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.svd(X, full_matrices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb1-10">eigenvalues <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> singular<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> n</span>
<span id="cb1-11">V <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Vt[:k].T</span>
<span id="cb1-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Fix a plotting convention without consulting labels or the true latent z.</span></span>
<span id="cb1-13">V <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*=</span> np.where(V[np.argmax(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(V), axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), np.arange(k)] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb1-14">noise <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> eigenvalues[k:].mean()</span>
<span id="cb1-15">W <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> V <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.sqrt(eigenvalues[:k] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> noise)</span>
<span id="cb1-16">covariance <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> W <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> W.T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> noise <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.eye(d)</span>
<span id="cb1-17"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sample covariance eigenvalues:"</span>, eigenvalues)</span>
<span id="cb1-18"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"fitted noise variance: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>noise<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">; generating value: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb1-19">alignment <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(V[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> W_true[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> np.linalg.norm(W_true)</span>
<span id="cb1-20"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"absolute cosine with generating direction: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>alignment<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb1-21"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> np.allclose(np.linalg.eigvalsh(covariance)[::<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], eigenvalues)</span></code></pre></div>
<div class="cell-output cell-output-stdout">
<pre><code>sample covariance eigenvalues: [6.0691 0.6283]
fitted noise variance: 0.6283; generating value: 0.6400
absolute cosine with generating direction: 1.0000</code></pre>
</div>
</div>
<p>The fitted noise variance is 0.6283, compared with the generating value 0.6400. With <img src="https://latex.codecogs.com/png.latex?d=2"> and <img src="https://latex.codecogs.com/png.latex?k=1">, only one eigenvalue is discarded, so this PPCA model can reproduce both eigenvalues of any positive-definite sample covariance.</p>
<p>Covariance agreement here checks the implementation; it is not a goodness-of-fit test for Gaussianity or a proof about the true sensor noise.</p>
</section>
<section id="reconstruction-check" class="level3" data-number="4.2">
<h3 data-number="4.2" class="anchored" data-anchor-id="reconstruction-check"><span class="header-section-number">4.2</span> Reconstruction check</h3>
<p>We compute both estimates for every observation and check the shrinkage equation. A fixed probe, three units along the fitted first axis and one unit perpendicular to it, makes the two reconstructions easy to distinguish in the figure.</p>
<div id="ppca-posterior" class="cell" data-execution_count="3">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">M <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> W.T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> W <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> noise <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.eye(k)</span>
<span id="cb3-2">latent_mean <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.solve(M, W.T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> X.T).T</span>
<span id="cb3-3">latent_cov <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> noise <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.linalg.solve(M, np.eye(k))</span>
<span id="cb3-4">classic <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> V <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> V.T</span>
<span id="cb3-5">signal_mean <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> latent_mean <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> W.T</span>
<span id="cb3-6">shrink <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> noise <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> eigenvalues[:k]</span>
<span id="cb3-7"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> np.allclose(signal_mean, (X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> V <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> shrink) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> V.T)</span>
<span id="cb3-8"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> np.allclose(latent_cov.diagonal(), noise <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> eigenvalues[:k])</span>
<span id="cb3-9"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>((X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> classic)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>((X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> signal_mean)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb3-10"></span>
<span id="cb3-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Verify the hand calculation independently of the simulated fit.</span></span>
<span id="cb3-12">hand_noise <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.mean([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>])</span>
<span id="cb3-13">hand_W <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.sqrt(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">9.0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> hand_noise)</span>
<span id="cb3-14">hand_mean <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> hand_W <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">9.0</span></span>
<span id="cb3-15"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> np.isclose(hand_mean <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> hand_W, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">13</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>)</span>
<span id="cb3-16"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> np.isclose(hand_noise <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">9.0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">18</span>)</span>
<span id="cb3-17"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"hand example: latent mean </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>hand_mean<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">; signal coordinate </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>hand_mean <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> hand_W<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb3-18"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"sensor fit: reconstruction multiplier </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>shrink[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb3-19"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"posterior latent standard deviation: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>sqrt(latent_cov[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>hand example: latent mean 0.8498; signal coordinate 2.1667
sensor fit: reconstruction multiplier 0.8965
posterior latent standard deviation: 0.3218</code></pre>
</div>
</div>
<p>The fitted reconstruction multiplier is 0.8965: the posterior signal mean retains about 90% of the classical score along this axis. Classical PCA has the smaller squared error against the observed measurements, the quantity it optimizes.</p>
<p>PPCA’s posterior mean estimates the unobserved signal under the fitted model; better fit to the noisy observations is not its criterion.</p>
<div id="cell-fig-ppca" class="cell" data-fig-height="3.8" data-fig-width="9" data-execution_count="4">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1">v <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> V[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb5-2">perpendicular <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>v[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], v[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]])</span>
<span id="cb5-3">probe <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> v <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> perpendicular</span>
<span id="cb5-4">probe_classic <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> v</span>
<span id="cb5-5">probe_latent <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.solve(M, W.T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> probe)</span>
<span id="cb5-6">probe_signal <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> W <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> probe_latent</span>
<span id="cb5-7">fig, (ax, density_ax) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.8</span>))</span>
<span id="cb5-8">ax.scatter(X[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], X[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>INK)</span>
<span id="cb5-9">span <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.5</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.5</span>])</span>
<span id="cb5-10">ax.plot(span <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> v[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], span <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> v[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>PURPLE, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"principal axis"</span>)</span>
<span id="cb5-11">ax.plot([probe[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], probe_classic[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]], [probe[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], probe_classic[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]],</span>
<span id="cb5-12">        color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>INK, ls<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">":"</span>, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb5-13">ax.scatter(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>probe, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>INK, marker<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"x"</span>, s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">55</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"probe"</span>)</span>
<span id="cb5-14">ax.scatter(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>probe_classic, facecolors<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"none"</span>, edgecolors<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>TEAL,</span>
<span id="cb5-15">           s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">95</span>, linewidths<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"classical projection"</span>, zorder<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>)</span>
<span id="cb5-16">ax.scatter(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>probe_signal, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ORANGE, marker<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"D"</span>, s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">35</span>,</span>
<span id="cb5-17">           label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"posterior signal mean"</span>, zorder<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>)</span>
<span id="cb5-18">ax.scatter(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>INK, marker<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+"</span>, s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">55</span>)</span>
<span id="cb5-19">ax.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(xlabel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sensor 1, centred"</span>, ylabel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sensor 2, centred"</span>, title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Same axis, different reconstruction"</span>)</span>
<span id="cb5-20">ax.set_aspect(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"equal"</span>)</span>
<span id="cb5-21">ax.legend(frameon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>, loc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"upper left"</span>)</span>
<span id="cb5-22">grid <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linspace(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.5</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.5</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">500</span>)</span>
<span id="cb5-23">sd <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.sqrt(latent_cov[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span>
<span id="cb5-24">normal <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> t, mean, scale: np.exp(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> ((t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> mean) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> scale)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (scale <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.sqrt(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.pi))</span>
<span id="cb5-25">density_ax.plot(grid, normal(grid, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>INK, ls<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--"</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"prior N(0, 1)"</span>)</span>
<span id="cb5-26">density_ax.plot(grid, normal(grid, probe_latent[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], sd), color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ORANGE, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"posterior for probe"</span>)</span>
<span id="cb5-27">density_ax.axvline(probe_latent[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ORANGE, ls<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">":"</span>, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb5-28">density_ax.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(xlabel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"latent coordinate z"</span>, ylabel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"density"</span>, title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Uncertainty conditional on the model"</span>)</span>
<span id="cb5-29">density_ax.legend(frameon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb5-30">fig.tight_layout(pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.2</span>)</span>
<span id="cb5-31">plt.show()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-ppca" class="quarto-float quarto-figure quarto-figure-center anchored" alt="A diagonal cloud of paired readings with a probe off the fitted line. Its PPCA reconstruction is closer to the fitted mean than its classical projection. A second panel compares a broad standard normal prior with a narrower shifted latent posterior.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-ppca-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/probabilistic-pca/index_files/figure-html/fig-ppca-output-1.png" class="img-fluid figure-img" alt="A diagonal cloud of paired readings with a probe off the fitted line. Its PPCA reconstruction is closer to the fitted mean than its classical projection. A second panel compares a broad standard normal prior with a narrower shifted latent posterior.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-ppca-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Left: synthetic sensor readings and a fixed probe, with its classical projection and PPCA posterior signal mean on the fitted axis. Right: the probe’s latent prior and posterior densities, conditional on fitted parameters.
</figcaption>
</figure>
</div>
</div>
</div>
<p>The orange point stays on the principal axis but moves toward the fitted mean. The posterior density describes the latent coordinate’s remaining uncertainty; it is not a confidence interval for the axis itself.</p>
</section>
</section>
<section id="constraints-and-use" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="constraints-and-use"><span class="header-section-number">5</span> Constraints and use</h2>
<ul>
<li><strong>Equal noise is a substantive assumption.</strong> Feature rescaling changes its meaning. Different sensor noise variances call for a different observation model; ordinary standardization does not establish isotropic noise.</li>
<li><strong>Latent uncertainty is conditional.</strong> A narrow posterior can still be misleading if the noise model or fitted parameters are wrong. PPCA with point-estimated parameters is not a fully Bayesian treatment of parameter uncertainty.</li>
<li><strong>Choosing <img src="https://latex.codecogs.com/png.latex?k"> requires a criterion.</strong> The MLE formulas assume <img src="https://latex.codecogs.com/png.latex?k"> is fixed. A larger fitted likelihood alone does not validate a larger dimension; model comparison or held-out performance must account for complexity.</li>
<li><strong>Missing values require a different fitting procedure.</strong> Given parameters, the Gaussian model can condition on observed coordinates. Learning those parameters from incomplete data requires an observed-data likelihood, for example optimized by EM; the complete-data covariance formula does not apply unchanged. The original PPCA paper develops that extension.</li>
</ul>
<p>Use classical PCA when the goal is an orthogonal projection minimizing squared reconstruction error. Use PPCA when a Gaussian observation model is defensible and a density or conditional latent uncertainty is useful.</p>
<p>Model. Signal. Estimate. Noise. Recover. Subspaces. Shrink. Reconstructions. Check. Assumptions.</p>
</section>
<section id="references" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="references"><span class="header-section-number">6</span> References</h2>
<ul>
<li>Tipping, M. E., and Bishop, C. M. (1999). <a href="https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/bishop-ppca-jrss.pdf">Probabilistic principal component analysis</a>. <em>Journal of the Royal Statistical Society, Series B</em> 61(3), 611–622. <a href="https://doi.org/10.1111/1467-9868.00196">DOI</a>.</li>
<li><a href="../six-views-of-pca/">Six Views of PCA</a> — projection geometry, equivalent objectives, and preprocessing.</li>
</ul>


</section>

 ]]></description>
  <category>Linear Algebra</category>
  <category>Machine Learning</category>
  <category>Statistics</category>
  <guid>https://project-delphi.github.io/ml-blog/posts/probabilistic-pca/</guid>
  <pubDate>Sun, 13 Sep 2026 05:00:00 GMT</pubDate>
  <media:content url="https://project-delphi.github.io/ml-blog/posts/probabilistic-pca/cover.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Six Views of PCA</title>
  <dc:creator>Ravi Kalia</dc:creator>
  <link>https://project-delphi.github.io/ml-blog/posts/six-views-of-pca/</link>
  <description><![CDATA[ 





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/six-views-of-pca/cover.png" class="img-fluid figure-img" alt="A centred point cloud projected onto a line, beside complementary curves of projected energy and reconstruction error."></p>
<figcaption>Six Views of PCA</figcaption>
</figure>
</div>
<p>PCA’s maximum-variance and minimum-error definitions choose the same subspace; the projection geometry explains why, and the algebra connects both to truncated SVD.</p>
<p>When we replace a measurement table with fewer coordinates, we want to know what those coordinates preserve. The six formulations answer that question for a fixed matrix; centring and scaling determine which matrix we have asked them to summarize.</p>
<p><a href="../eigendecomposition/">Eigenvectors and eigenvalues</a> and <a href="../svd-rotate-stretch-rotate/">SVD geometry</a> provide the linear-algebra background.</p>
<section id="projection-geometry" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="projection-geometry"><span class="header-section-number">1</span> Projection geometry</h2>
<p>We can compare a line that keeps the most spread with a line that leaves the shortest perpendicular residuals. Both choose the direction called the first principal component, or PC1.</p>
<p>The synthetic cloud used here: 200 draws of a scalar latent <img src="https://latex.codecogs.com/png.latex?t%20%5Csim%20N(0,1)">, mapped to <img src="https://latex.codecogs.com/png.latex?(2.2t,%5C%200.9t)"> and perturbed by independent <img src="https://latex.codecogs.com/png.latex?N(0,%200.45%5E2)"> noise in both coordinates, then column-centred. It stands in for any measurement pair where one underlying quantity drives both readings and the instrument adds isotropic noise — two sensors on one process, two assays of one sample. Two dimensions let us draw the residuals. The example checks an algebraic identity; it does not validate a sensor model on real measurements.</p>
<div id="cell-fig-equivalence" class="cell" data-fig-height="3.5" data-fig-width="9.2" data-execution_count="2">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1">n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">200</span></span>
<span id="cb1-2">latent <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rng.normal(size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>n)</span>
<span id="cb1-3">X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.column_stack([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> latent, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.9</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> latent]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> rng.normal(scale<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.45</span>, size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(n, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>))</span>
<span id="cb1-4">X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> X.mean(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb1-5"></span>
<span id="cb1-6">U, S, Vt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.svd(X, full_matrices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb1-7">pc1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Vt[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb1-8"></span>
<span id="cb1-9">thetas <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linspace(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, np.pi, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3601</span>)</span>
<span id="cb1-10">dirs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.vstack([np.cos(thetas), np.sin(thetas)])</span>
<span id="cb1-11">proj <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> dirs</span>
<span id="cb1-12">energy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (proj<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb1-13">resid <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ((X[:, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, :] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> proj[:, :, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> dirs.T[<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, :, :]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>))</span>
<span id="cb1-14">total <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>((X<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>())</span>
<span id="cb1-15"></span>
<span id="cb1-16">fig, (ax_l, ax_r) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(</span>
<span id="cb1-17">    <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">9.2</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.5</span>), gridspec_kw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"width_ratios"</span>: [<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.15</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>]}</span>
<span id="cb1-18">)</span>
<span id="cb1-19"></span>
<span id="cb1-20">scores <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> pc1</span>
<span id="cb1-21">feet <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.outer(scores, pc1)</span>
<span id="cb1-22">ax_l.scatter(X[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], X[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">14</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>MUTED, alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.45</span>, zorder<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb1-23"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, n, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>):</span>
<span id="cb1-24">    ax_l.plot(</span>
<span id="cb1-25">        [X[i, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], feet[i, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]], [X[i, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], feet[i, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]],</span>
<span id="cb1-26">        color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>CORAL, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>, zorder<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>,</span>
<span id="cb1-27">    )</span>
<span id="cb1-28">ax_l.scatter(feet[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], feet[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ACCENT, zorder<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>)</span>
<span id="cb1-29">span <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([scores.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(), scores.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>()])</span>
<span id="cb1-30">ax_l.plot(span <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> pc1[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], span <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> pc1[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ACCENT, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.8</span>, zorder<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>)</span>
<span id="cb1-31">ax_l.set_aspect(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"equal"</span>)</span>
<span id="cb1-32">ax_l.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"feature 1"</span>)</span>
<span id="cb1-33">ax_l.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"feature 2"</span>)</span>
<span id="cb1-34">ax_l.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Projection onto PC1, and what is left over"</span>)</span>
<span id="cb1-35"></span>
<span id="cb1-36">deg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.degrees(thetas)</span>
<span id="cb1-37">ax_r.plot(deg, energy, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ACCENT, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.8</span>)</span>
<span id="cb1-38">ax_r.plot(deg, resid, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>CORAL, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.8</span>)</span>
<span id="cb1-39">ax_r.plot(deg, energy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> resid, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>TEAL, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.4</span>, ls<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--"</span>)</span>
<span id="cb1-40">ax_r.axvline(deg[energy.argmax()], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>MUTED, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.9</span>, ls<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">":"</span>)</span>
<span id="cb1-41">ax_r.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"direction angle (degrees)"</span>)</span>
<span id="cb1-42">ax_r.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sum of squares"</span>)</span>
<span id="cb1-43">ax_r.set_xlim(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">180</span>)</span>
<span id="cb1-44">ax_r.set_ylim(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, total <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.30</span>)</span>
<span id="cb1-45"></span>
<span id="cb1-46"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Label the curves on the curves: a legend box has nowhere to sit here.</span></span>
<span id="cb1-47">ax_r.text(deg[energy.argmax()], total <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.05</span>, <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"energy $\|Xw\|_F^2$"</span>,</span>
<span id="cb1-48">          color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ACCENT, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"center"</span>)</span>
<span id="cb1-49">ax_r.text(deg[resid.argmax()], total <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.05</span>, <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"residual $\|X-Xww^\top\|_F^2$"</span>,</span>
<span id="cb1-50">          color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>CORAL, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"center"</span>)</span>
<span id="cb1-51">ax_r.text(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">170</span>, total <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.92</span>, <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"sum $=\|X\|_F^2$"</span>,</span>
<span id="cb1-52">          color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>TEAL, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"right"</span>, va<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bottom"</span>)</span>
<span id="cb1-53">ax_r.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"One curve is the other, upside down"</span>)</span>
<span id="cb1-54"></span>
<span id="cb1-55">fig.tight_layout(pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.1</span>)</span>
<span id="cb1-56">plt.show()</span>
<span id="cb1-57"></span>
<span id="cb1-58"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"energy peaks at </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>deg[energy.argmax()]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> deg"</span>)</span>
<span id="cb1-59"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"residual bottoms at </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>deg[resid.argmin()]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> deg"</span>)</span>
<span id="cb1-60"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"sum varies by </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>ptp(energy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> resid)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> across the sweep"</span>)</span>
<span id="cb1-61"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"||X||_F^2 = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>total<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-equivalence" class="quarto-float quarto-figure quarto-figure-center anchored" alt="Two panels. The left shows a 2D elliptical point cloud with a line through its long axis and short perpendicular segments from points to that line. The right shows two mirrored curves against angle, one peaking where the other bottoms out, and a flat line at their sum.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-equivalence-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/six-views-of-pca/index_files/figure-html/fig-equivalence-output-1.png" class="img-fluid figure-img" alt="Two panels. The left shows a 2D elliptical point cloud with a line through its long axis and short perpendicular segments from points to that line. The right shows two mirrored curves against angle, one peaking where the other bottoms out, and a flat line at their sum.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-equivalence-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Left: the centred cloud with PC1 and, for a sample of observations, the perpendicular residual joining each point to its projection. Right: projected energy and residual sum of squares against direction angle, with their constant sum.
</figcaption>
</figure>
</div>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>energy peaks at 22.15 deg
residual bottoms at 22.15 deg
sum varies by 2.84e-12 across the sweep
||X||_F^2 = 903.0973</code></pre>
</div>
</div>
<p>The two curves peak and bottom out at the same angle, 22.15 degrees, and their sum is flat to <img src="https://latex.codecogs.com/png.latex?2.84%5Ctimes10%5E%7B-12%7D"> — floating-point noise on a total of 903.0973. That flat line is the identity of Section&nbsp;4.2. Maximising the purple curve and minimising the orange one are the same search, because the dashed line does not move.</p>
</section>
<section id="notation" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="notation"><span class="header-section-number">2</span> Notation</h2>
<p>All six views use one centred matrix and one orthonormal basis, with <img src="https://latex.codecogs.com/png.latex?n%20%3E%201"> and <img src="https://latex.codecogs.com/png.latex?1%20%5Cle%20k%20%5Cle%20%5Cmin(n,d)">.</p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?X%20%5Cin%20%5Cmathbb%7BR%7D%5E%7Bn%20%5Ctimes%20d%7D"> — the data. Each of the <img src="https://latex.codecogs.com/png.latex?n"> rows is an observation, each of the <img src="https://latex.codecogs.com/png.latex?d"> columns a feature.</li>
<li><img src="https://latex.codecogs.com/png.latex?X"> is <strong>centred</strong>: every column has mean zero. PCA without this step answers a different question, and Section&nbsp;4.8 says which one.</li>
<li><img src="https://latex.codecogs.com/png.latex?W%20%5Cin%20%5Cmathbb%7BR%7D%5E%7Bd%20%5Ctimes%20k%7D"> — the basis we are looking for, with <img src="https://latex.codecogs.com/png.latex?W%5E%5Ctop%20W%20=%20I_k">. Its <img src="https://latex.codecogs.com/png.latex?k"> columns are orthonormal directions in feature space.</li>
<li><img src="https://latex.codecogs.com/png.latex?Z%20=%20XW%20%5Cin%20%5Cmathbb%7BR%7D%5E%7Bn%20%5Ctimes%20k%7D"> — the <strong>projection</strong>, or scores. Row <img src="https://latex.codecogs.com/png.latex?i"> holds the <img src="https://latex.codecogs.com/png.latex?k"> coordinates of observation <img src="https://latex.codecogs.com/png.latex?i"> in the new basis.</li>
<li><img src="https://latex.codecogs.com/png.latex?%5Chat%20X%20=%20XWW%5E%5Ctop%20%5Cin%20%5Cmathbb%7BR%7D%5E%7Bn%20%5Ctimes%20d%7D"> — the <strong>reconstruction</strong>. Back in the original <img src="https://latex.codecogs.com/png.latex?d"> features, but of rank at most <img src="https://latex.codecogs.com/png.latex?k">.</li>
<li><img src="https://latex.codecogs.com/png.latex?P%20=%20WW%5E%5Ctop%20%5Cin%20%5Cmathbb%7BR%7D%5E%7Bd%20%5Ctimes%20d%7D"> — the orthogonal projector onto the span of <img src="https://latex.codecogs.com/png.latex?W">’s columns. <img src="https://latex.codecogs.com/png.latex?P%5E2%20=%20P"> and <img src="https://latex.codecogs.com/png.latex?P%5E%5Ctop%20=%20P">.</li>
</ul>
<p>Scores and reconstructions have different dimensions. <img src="https://latex.codecogs.com/png.latex?Z"> is <img src="https://latex.codecogs.com/png.latex?n%20%5Ctimes%20k"> and lives in the small space. <img src="https://latex.codecogs.com/png.latex?%5Chat%20X"> is <img src="https://latex.codecogs.com/png.latex?n%20%5Ctimes%20d"> and lives back in the big one, on a <img src="https://latex.codecogs.com/png.latex?k">-dimensional flat inside it.</p>
</section>
<section id="the-six-views" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="the-six-views"><span class="header-section-number">3</span> The six views</h2>
<p>Three objectives, a matrix approximation, and two solution methods describe the principal subspace:</p>
<ol type="1">
<li><strong>Maximum variance.</strong> Find the directions along which the projected observations spread out the most.</li>
<li><strong>Maximum projected energy.</strong> Find <img src="https://latex.codecogs.com/png.latex?W"> maximising <img src="https://latex.codecogs.com/png.latex?%5C%7CXW%5C%7C_F%5E2">, the total squared magnitude of the projected representation.</li>
<li><strong>Minimum reconstruction error.</strong> Find the <img src="https://latex.codecogs.com/png.latex?k">-dimensional subspace minimising <img src="https://latex.codecogs.com/png.latex?%5C%7CX%20-%20XWW%5E%5Ctop%5C%7C_F%5E2">, the squared perpendicular distance from the data to the subspace.</li>
<li><strong>Best rank-<img src="https://latex.codecogs.com/png.latex?k"> approximation.</strong> Find the rank-<img src="https://latex.codecogs.com/png.latex?k"> matrix closest to <img src="https://latex.codecogs.com/png.latex?X"> in Frobenius norm.</li>
<li><strong>Covariance eigendecomposition.</strong> Take the leading eigenvectors of the sample covariance matrix.</li>
<li><strong>Truncated SVD.</strong> Take the leading right singular vectors of <img src="https://latex.codecogs.com/png.latex?X">.</li>
</ol>
<p>They are not six independent problems. Views 1, 2, and 3 are one optimization problem written in three notations. Views 5 and 6 are two <em>routes to the solution</em> of that problem, not separate objectives. View 4 is the same optimum read as a statement about matrices rather than about subspaces. Section&nbsp;4 does the work of showing that.</p>
<p>Here <strong>energy</strong> means a sum of squares: <img src="https://latex.codecogs.com/png.latex?%5C%7CX%5C%7C_F%5E2%20=%20%5Csum_%7Bij%7D%20X_%7Bij%7D%5E2">. For centred data it is <img src="https://latex.codecogs.com/png.latex?(n-1)"> times the total sample variance.</p>
<p>The arrows distinguish algebraic identities from the optimization results needed to choose a solution.</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div id="fig-map" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-map-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div>
<pre class="mermaid mermaid-js" data-label="fig-map">flowchart TD
  R["&lt;b&gt;Minimum&lt;br/&gt;reconstruction error&lt;/b&gt;&lt;br/&gt;min ‖X − XWWᵀ‖²_F"]
  E["&lt;b&gt;Maximum&lt;br/&gt;projected energy&lt;/b&gt;&lt;br/&gt;max ‖XW‖²_F"]
  V["&lt;b&gt;Maximum&lt;br/&gt;variance&lt;/b&gt;&lt;br/&gt;max tr(WᵀSW)"]
  G["&lt;b&gt;Covariance&lt;br/&gt;eigenvectors&lt;/b&gt;&lt;br/&gt;S = VΛVᵀ"]
  D["&lt;b&gt;Truncated&lt;br/&gt;SVD&lt;/b&gt;&lt;br/&gt;X = UΣVᵀ"]
  L["&lt;b&gt;Best rank-k&lt;br/&gt;approximation&lt;/b&gt;&lt;br/&gt;X̂ = U_kΣ_kV_kᵀ"]
  R --&gt;|"‖X‖²_F − ‖XW‖²_F&lt;br/&gt;projector identity"| E
  E --&gt;|"‖XW‖²_F = (n−1) tr(WᵀSW)&lt;br/&gt;cyclic trace"| V
  V --&gt;|"stationarity + Ky Fan&lt;br/&gt;W = V_k"| G
  G --&gt;|"XᵀX = VΣ²Vᵀ&lt;br/&gt;λ = σ²/(n−1)"| D
  D --&gt;|"Eckart–Young–Mirsky"| L
</pre>
</div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-map-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: The six views and their mathematical connections. Views 1 to 3 are one objective in three notations; views 5 and 6 are routes to its solution; view 4 is the same optimum read as a statement about matrices.
</figcaption>
</figure>
</div>
</div>
</div>
</section>
<section id="sec-equivalence" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="sec-equivalence"><span class="header-section-number">4</span> Equivalence</h2>
<p>The proof first relates the objectives for any admissible basis, then finds their optimizers. We fix centring, feature scaling, and a complete matrix throughout.</p>
<section id="covariance-and-normalization" class="level3" data-number="4.1">
<h3 data-number="4.1" class="anchored" data-anchor-id="covariance-and-normalization"><span class="header-section-number">4.1</span> Covariance and normalization</h3>
<p>The sample covariance of centred <img src="https://latex.codecogs.com/png.latex?X"> is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS%20=%20%5Cfrac%7BX%5E%5Ctop%20X%7D%7Bn-1%7D%20%5Cin%20%5Cmathbb%7BR%7D%5E%7Bd%20%5Ctimes%20d%7D,%0A"></p>
<p>The denominator <img src="https://latex.codecogs.com/png.latex?n-1"> accounts for estimating the column means. Switching to the maximum-likelihood convention <img src="https://latex.codecogs.com/png.latex?X%5E%5Ctop%20X/n"> multiplies every eigenvalue by <img src="https://latex.codecogs.com/png.latex?(n-1)/n">; it changes neither eigenvectors nor explained-variance ratios.</p>
<p><img src="https://latex.codecogs.com/png.latex?S"> is symmetric positive semi-definite, so it has an orthonormal eigenbasis with non-negative eigenvalues:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS%20=%20V%5CLambda%20V%5E%5Ctop,%20%5Cqquad%20%5CLambda%20=%20%5Coperatorname%7Bdiag%7D(%5Clambda_1%20%5Cge%20%5Cdots%20%5Cge%20%5Clambda_d%20%5Cge%200).%0A"></p>
<p>The principal directions are the leading columns of <img src="https://latex.codecogs.com/png.latex?V">, and <img src="https://latex.codecogs.com/png.latex?%5Clambda_j"> is the variance of the data along direction <img src="https://latex.codecogs.com/png.latex?v_j"> — set <img src="https://latex.codecogs.com/png.latex?W%20=%20v_j"> in the trace objective and read it off. Because <img src="https://latex.codecogs.com/png.latex?%5Coperatorname%7Btr%7D(S)%20=%20%5Csum_j%20%5Clambda_j"> is the total variance across all <img src="https://latex.codecogs.com/png.latex?d"> features, <img src="https://latex.codecogs.com/png.latex?%5Clambda_j%20/%20%5Csum_i%20%5Clambda_i"> is the fraction of variance explained by component <img src="https://latex.codecogs.com/png.latex?j">.</p>
</section>
<section id="sec-projector" class="level3" data-number="4.2">
<h3 data-number="4.2" class="anchored" data-anchor-id="sec-projector"><span class="header-section-number">4.2</span> From reconstruction to energy</h3>
<p><img src="https://latex.codecogs.com/png.latex?P%20=%20WW%5E%5Ctop"> is an orthogonal projector: <img src="https://latex.codecogs.com/png.latex?P%5E%5Ctop%20=%20P">, and <img src="https://latex.codecogs.com/png.latex?P%5E2%20=%20WW%5E%5Ctop%20WW%5E%5Ctop%20=%20W(W%5E%5Ctop%20W)W%5E%5Ctop%20=%20WW%5E%5Ctop%20=%20P"> because <img src="https://latex.codecogs.com/png.latex?W%5E%5Ctop%20W%20=%20I_k">. Expand the reconstruction error:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5C%7CX%20-%20XP%5C%7C_F%5E2%0A=%20%5Coperatorname%7Btr%7D%5C!%5Cleft%5B(X%20-%20XP)%5E%5Ctop%20(X%20-%20XP)%5Cright%5D%0A=%20%5Coperatorname%7Btr%7D(X%5E%5Ctop%20X)%20-%202%5Coperatorname%7Btr%7D(X%5E%5Ctop%20X%20P)%20+%20%5Coperatorname%7Btr%7D(P%20X%5E%5Ctop%20X%20P).%0A"></p>
<p>The last term collapses. Using the cyclic property of the trace and <img src="https://latex.codecogs.com/png.latex?P%5E2%20=%20P">,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Coperatorname%7Btr%7D(PX%5E%5Ctop%20XP)%20=%20%5Coperatorname%7Btr%7D(X%5E%5Ctop%20X%20P%20P)%20=%20%5Coperatorname%7Btr%7D(X%5E%5Ctop%20X%20P),%0A"></p>
<p>so two of the three terms cancel into one:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5C%7CX%20-%20XWW%5E%5Ctop%5C%7C_F%5E2%20=%20%5Coperatorname%7Btr%7D(X%5E%5Ctop%20X)%20-%20%5Coperatorname%7Btr%7D(X%5E%5Ctop%20X%20WW%5E%5Ctop)%20=%20%5C%7CX%5C%7C_F%5E2%20-%20%5C%7CXW%5C%7C_F%5E2%20.%0A"></p>
<p>The last equality is the same cyclic move: <img src="https://latex.codecogs.com/png.latex?%5Coperatorname%7Btr%7D(X%5E%5Ctop%20X%20WW%5E%5Ctop)%20=%20%5Coperatorname%7Btr%7D(W%5E%5Ctop%20X%5E%5Ctop%20X%20W)%20=%20%5C%7CXW%5C%7C_F%5E2">.</p>
<p>This is the Pythagorean split. Every observation’s squared length divides into the part the subspace keeps and the part it loses, with no cross term. And <img src="https://latex.codecogs.com/png.latex?%5C%7CX%5C%7C_F%5E2"> does not contain <img src="https://latex.codecogs.com/png.latex?W">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmin_%7BW%5E%5Ctop%20W%20=%20I%7D%20%5C%7CX%20-%20XWW%5E%5Ctop%5C%7C_F%5E2%0A%5Ciff%0A%5Cmax_%7BW%5E%5Ctop%20W%20=%20I%7D%20%5C%7CXW%5C%7C_F%5E2%20.%0A"></p>
<p>Fitting the data as closely as possible and capturing as much energy as possible are the same instruction, because the two quantities sum to a constant.</p>
</section>
<section id="from-energy-to-variance" class="level3" data-number="4.3">
<h3 data-number="4.3" class="anchored" data-anchor-id="from-energy-to-variance"><span class="header-section-number">4.3</span> From energy to variance</h3>
<p>One line, already used in Section&nbsp;4.2:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5C%7CXW%5C%7C_F%5E2%20=%20%5Coperatorname%7Btr%7D%5C!%5Cleft%5B(XW)%5E%5Ctop(XW)%5Cright%5D%20=%20%5Coperatorname%7Btr%7D(W%5E%5Ctop%20X%5E%5Ctop%20X%20W)%20=%20(n-1)%5Coperatorname%7Btr%7D(W%5E%5Ctop%20S%20W).%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?W%5E%5Ctop%20X%5E%5Ctop%20X%20W"> is the <img src="https://latex.codecogs.com/png.latex?k%20%5Ctimes%20k"> covariance of the scores, up to the factor <img src="https://latex.codecogs.com/png.latex?n-1">. Its trace is the total projected variance. So maximum energy is maximum projected variance, and the <img src="https://latex.codecogs.com/png.latex?(n-1)"> never affects the maximiser.</p>
</section>
<section id="sec-lagrange" class="level3" data-number="4.4">
<h3 data-number="4.4" class="anchored" data-anchor-id="sec-lagrange"><span class="header-section-number">4.4</span> Stationary directions</h3>
<p>Nothing so far mentioned eigenvectors. They arrive as the solution of a constrained maximisation, and the constraint is what puts them there.</p>
<p>Maximising <img src="https://latex.codecogs.com/png.latex?w%5E%5Ctop%20A%20w"> with <img src="https://latex.codecogs.com/png.latex?A%20=%20X%5E%5Ctop%20X"> has no answer on its own: scale <img src="https://latex.codecogs.com/png.latex?w"> up and the objective grows without bound. The constraint <img src="https://latex.codecogs.com/png.latex?w%5E%5Ctop%20w%20=%201"> is what makes the problem well posed, and a constrained stationary point is found by <strong>Lagrange multipliers</strong> — add the constraint to the objective with an unknown coefficient, then look for a point where the combined function is flat in every direction:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7BL%7D(w,%20%5Clambda)%20=%20w%5E%5Ctop%20A%20w%20-%20%5Clambda%5C,(w%5E%5Ctop%20w%20-%201).%0A"></p>
<p>Differentiating in <img src="https://latex.codecogs.com/png.latex?w"> and setting the gradient to zero gives</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cnabla_w%20%5Cmathcal%7BL%7D%20=%202Aw%20-%202%5Clambda%20w%20=%200%0A%5Cqquad%20%5CLongleftrightarrow%20%5Cqquad%0AAw%20=%20%5Clambda%20w%20.%0A"></p>
<p>The eigenvector equation is the first-order condition. It was not assumed, and no eigendecomposition was invoked to get it: <strong>every stationary point of projected variance on the unit sphere is an eigenvector of <img src="https://latex.codecogs.com/png.latex?X%5E%5Ctop%20X"></strong>, and nothing else is.</p>
<p>The multiplier is not bookkeeping either. At a stationary point,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Aw%5E%5Ctop%20A%20w%20=%20w%5E%5Ctop%20(%5Clambda%20w)%20=%20%5Clambda%5C,w%5E%5Ctop%20w%20=%20%5Clambda%20,%0A"></p>
<p>so <img src="https://latex.codecogs.com/png.latex?%5Clambda"> <em>is</em> the value of the objective there — the projected energy, and <img src="https://latex.codecogs.com/png.latex?%5Clambda/(n-1)"> the projected variance. With distinct eigenvalues there are <img src="https://latex.codecogs.com/png.latex?d"> stationary directions, each with two unit-vector signs. Repeated eigenvalues admit every unit vector in the corresponding eigenspace; comparing their objective values selects the largest eigenvalue. That is Rayleigh–Ritz, derived rather than quoted.</p>
<p>For <img src="https://latex.codecogs.com/png.latex?k"> components the constraint <img src="https://latex.codecogs.com/png.latex?W%5E%5Ctop%20W%20=%20I_k"> is <img src="https://latex.codecogs.com/png.latex?k(k+1)/2"> scalar equations, so the multiplier is a symmetric <img src="https://latex.codecogs.com/png.latex?k%20%5Ctimes%20k"> matrix <img src="https://latex.codecogs.com/png.latex?%5CLambda">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7BL%7D(W,%20%5CLambda)%20=%20%5Coperatorname%7Btr%7D(W%5E%5Ctop%20A%20W)%20-%20%5Coperatorname%7Btr%7D%5C!%5Cleft%5B%5CLambda%20(W%5E%5Ctop%20W%20-%20I_k)%5Cright%5D,%0A%5Cqquad%0A%5Cnabla_W%20%5Cmathcal%7BL%7D%20=%202AW%20-%202W%5CLambda%20=%200%20.%0A"></p>
<p>So <img src="https://latex.codecogs.com/png.latex?AW%20=%20W%5CLambda">. A stationary <img src="https://latex.codecogs.com/png.latex?W"> need not hold eigenvectors — but <img src="https://latex.codecogs.com/png.latex?%5CLambda"> is symmetric, so <img src="https://latex.codecogs.com/png.latex?%5CLambda%20=%20RDR%5E%5Ctop"> for an orthogonal <img src="https://latex.codecogs.com/png.latex?R">, and <img src="https://latex.codecogs.com/png.latex?%5Cwidetilde%20W%20=%20WR"> satisfies <img src="https://latex.codecogs.com/png.latex?A%5Cwidetilde%20W%20=%20%5Cwidetilde%20W%20D">. Its columns <em>are</em> eigenvectors, and it spans the same subspace as <img src="https://latex.codecogs.com/png.latex?W">.</p>
<p>For the subspace optimization, the stationarity condition pins the <strong>subspace</strong>, and leaves the basis inside it free up to the orthogonal <img src="https://latex.codecogs.com/png.latex?R"> that diagonalises the multiplier.</p>
</section>
<section id="sec-kyfan" class="level3" data-number="4.5">
<h3 data-number="4.5" class="anchored" data-anchor-id="sec-kyfan"><span class="header-section-number">4.5</span> The maximizing subspace</h3>
<p>Stationarity gives <img src="https://latex.codecogs.com/png.latex?k"> eigenvectors; it does not say which <img src="https://latex.codecogs.com/png.latex?k">. For that, take <img src="https://latex.codecogs.com/png.latex?A"> symmetric with eigenvalues <img src="https://latex.codecogs.com/png.latex?%5Clambda_1%20%5Cge%20%5Cdots%20%5Cge%20%5Clambda_d"> and ask what maximises <img src="https://latex.codecogs.com/png.latex?%5Coperatorname%7Btr%7D(W%5E%5Ctop%20A%20W)"> over <img src="https://latex.codecogs.com/png.latex?W%5E%5Ctop%20W%20=%20I_k">.</p>
<p>Write <img src="https://latex.codecogs.com/png.latex?A%20=%20V%5CLambda%20V%5E%5Ctop"> and set <img src="https://latex.codecogs.com/png.latex?M%20=%20V%5E%5Ctop%20W">, which satisfies <img src="https://latex.codecogs.com/png.latex?M%5E%5Ctop%20M%20=%20I_k">. Then</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Coperatorname%7Btr%7D(W%5E%5Ctop%20A%20W)%20=%20%5Coperatorname%7Btr%7D(M%5E%5Ctop%20%5CLambda%20M)%20=%20%5Csum_%7Bj=1%7D%5E%7Bd%7D%20%5Clambda_j%20%5C,%20c_j,%0A%5Cqquad%20c_j%20=%20%5Csum_%7Bl=1%7D%5E%7Bk%7D%20M_%7Bjl%7D%5E2%20.%0A"></p>
<p>The weights <img src="https://latex.codecogs.com/png.latex?c_j"> are constrained: each <img src="https://latex.codecogs.com/png.latex?c_j%20%5Cin%20%5B0,%201%5D"> because <img src="https://latex.codecogs.com/png.latex?M"> has orthonormal columns, and <img src="https://latex.codecogs.com/png.latex?%5Csum_j%20c_j%20=%20%5C%7CM%5C%7C_F%5E2%20=%20k">. Maximising a weighted sum of the <img src="https://latex.codecogs.com/png.latex?%5Clambda_j"> under “budget <img src="https://latex.codecogs.com/png.latex?k">, at most <img src="https://latex.codecogs.com/png.latex?1"> each” puts all of the budget on the <img src="https://latex.codecogs.com/png.latex?k"> largest eigenvalues. The maximum is <img src="https://latex.codecogs.com/png.latex?%5Clambda_1%20+%20%5Cdots%20+%20%5Clambda_k">, attained when <img src="https://latex.codecogs.com/png.latex?W"> spans the leading <img src="https://latex.codecogs.com/png.latex?k"> eigenvectors. That is the Ky Fan theorem, and at <img src="https://latex.codecogs.com/png.latex?k%20=%201"> it is Rayleigh–Ritz.</p>
<p>Applied to <img src="https://latex.codecogs.com/png.latex?A%20=%20X%5E%5Ctop%20X">: the maximiser is <img src="https://latex.codecogs.com/png.latex?W%20=%20V_k">, the leading eigenvectors of <img src="https://latex.codecogs.com/png.latex?X%5E%5Ctop%20X">, which are the leading eigenvectors of <img src="https://latex.codecogs.com/png.latex?S">.</p>
</section>
<section id="sec-lowrank" class="level3" data-number="4.6">
<h3 data-number="4.6" class="anchored" data-anchor-id="sec-lowrank"><span class="header-section-number">4.6</span> SVD and rank approximation</h3>
<p>For the thin SVD <img src="https://latex.codecogs.com/png.latex?X%20=%20U%5CSigma%20V%5E%5Ctop">, let <img src="https://latex.codecogs.com/png.latex?r=%5Cmin(n,d)">, so <img src="https://latex.codecogs.com/png.latex?U"> is <img src="https://latex.codecogs.com/png.latex?n%5Ctimes%20r">, <img src="https://latex.codecogs.com/png.latex?V"> is <img src="https://latex.codecogs.com/png.latex?d%5Ctimes%20r">, and <img src="https://latex.codecogs.com/png.latex?%5CSigma"> holds the <img src="https://latex.codecogs.com/png.latex?r"> singular values. Zero singular values are included when <img src="https://latex.codecogs.com/png.latex?X"> has smaller rank.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AX%5E%5Ctop%20X%20=%20V%5CSigma%5E2V%5E%5Ctop,%0A%5Cqquad%20%5Clambda_j(S)=%5Cfrac%7B%5Csigma_j%5E2%7D%7Bn-1%7D.%0A"></p>
<p>The right singular vectors provide the covariance eigenvectors represented by the thin decomposition. When <img src="https://latex.codecogs.com/png.latex?d%3En">, a full covariance eigenbasis also contains <img src="https://latex.codecogs.com/png.latex?d-n"> additional null-space directions.</p>
<p>At the optimum, the scores and reconstruction are</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AZ=XV_k=U_k%5CSigma_k,%5Cqquad%0A%5Chat%20X=XV_kV_k%5E%5Ctop=U_k%5CSigma_kV_k%5E%5Ctop.%0A"></p>
<p>The <strong>Eckart–Young–Mirsky theorem</strong> says this reconstruction also minimizes <img src="https://latex.codecogs.com/png.latex?%5C%7CX-B%5C%7C_F%5E2"> over all matrices <img src="https://latex.codecogs.com/png.latex?B"> of rank at most <img src="https://latex.codecogs.com/png.latex?k">. Its error is <img src="https://latex.codecogs.com/png.latex?%5Csum_%7Bj%3Ek%7D%5Csigma_j%5E2">; the theorem joins the subspace problem to the broader matrix-approximation problem.</p>
<p>Direct SVD avoids forming <img src="https://latex.codecogs.com/png.latex?X%5E%5Ctop%20X">, which squares the ratio of largest to smallest nonzero singular value. It also avoids storing a <img src="https://latex.codecogs.com/png.latex?d%5Ctimes%20d"> covariance matrix when the data are wide; <a href="../svd-rotate-stretch-rotate/">the SVD companion</a> develops the numerical argument.</p>
</section>
<section id="sec-chain" class="level3" data-number="4.7">
<h3 data-number="4.7" class="anchored" data-anchor-id="sec-chain"><span class="header-section-number">4.7</span> The chain</h3>
<p>For every <img src="https://latex.codecogs.com/png.latex?W"> satisfying <img src="https://latex.codecogs.com/png.latex?W%5E%5Ctop%20W=I_k">,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cboxed%7B%5C%7CX-XWW%5E%5Ctop%5C%7C_F%5E2%0A=%5C%7CX%5C%7C_F%5E2-%5C%7CXW%5C%7C_F%5E2%0A=%5C%7CX%5C%7C_F%5E2-(n-1)%5Coperatorname%7Btr%7D(W%5E%5Ctop%20S%20W).%7D%0A"></p>
<p>These are identities between objective values. Rayleigh–Ritz and Ky Fan identify a maximizing subspace, and Eckart–Young–Mirsky identifies a best rank-<img src="https://latex.codecogs.com/png.latex?k"> reconstruction.</p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?W=V_k"> is an optimizer; so is <img src="https://latex.codecogs.com/png.latex?V_kR"> for any orthogonal <img src="https://latex.codecogs.com/png.latex?k%5Ctimes%20k"> matrix <img src="https://latex.codecogs.com/png.latex?R">.</li>
<li>Conventional PCA chooses ordered eigenvectors, giving uncorrelated scores with covariance <img src="https://latex.codecogs.com/png.latex?%5Coperatorname%7Bdiag%7D(%5Clambda_1,%5Cldots,%5Clambda_k)">.</li>
<li>An arbitrary rotation within the retained subspace preserves its projector and reconstruction, but generally makes the score covariance nondiagonal. It is an optimal subspace basis without necessarily being a set of principal axes.</li>
</ul>
</section>
<section id="sec-assumptions" class="level3" data-number="4.8">
<h3 data-number="4.8" class="anchored" data-anchor-id="sec-assumptions"><span class="header-section-number">4.8</span> Equivalence, uniqueness, and stability</h3>
<p>These are separate guarantees:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Question</th>
<th>Condition and consequence</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Do the objectives agree?</td>
<td>A complete, fixed matrix, orthogonal projection, and squared Frobenius error give the projector identity. Column centring supplies the variance interpretation.</td>
</tr>
<tr class="even">
<td>Is the optimal subspace unique?</td>
<td>A strict boundary gap <img src="https://latex.codecogs.com/png.latex?%5Clambda_k%3E%5Clambda_%7Bk+1%7D"> fixes the leading <img src="https://latex.codecogs.com/png.latex?k">-dimensional subspace. A positive tie across the boundary permits multiple optimal subspaces and reconstructions; the objective equivalence still holds.</td>
</tr>
<tr class="odd">
<td>Are individual axes unique?</td>
<td>Distinct eigenvalues fix eigenvector directions up to sign. Ties permit rotations inside the tied eigenspace.</td>
</tr>
<tr class="even">
<td>Is the estimated subspace stable?</td>
<td>A small boundary gap relative to sampling or measurement perturbations can make it unstable, even when the sample optimizer is unique. Resampling evaluates sensitivity to a specified sampling process.</td>
</tr>
</tbody>
</table>
<p>If <img src="https://latex.codecogs.com/png.latex?k"> reaches the data rank, reconstruction is <img src="https://latex.codecogs.com/png.latex?X"> itself. Adding directions from the null space can make the chosen basis or subspace nonunique while the reconstruction stays fixed.</p>
</section>
</section>
<section id="numerical-check" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="numerical-check"><span class="header-section-number">5</span> Numerical check</h2>
<p>We can check covariance eigenvalues, the projector identity, and the best-rank reconstruction on the synthetic cloud without hiding the algebra behind a PCA API.</p>
<div id="demo" class="cell" data-execution_count="3">
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">eigenvalues <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.eigvalsh(X.T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))[::<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb3-2">k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb3-3">W <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Vt[:k].T</span>
<span id="cb3-4">scores <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> W</span>
<span id="cb3-5">reconstruction <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> scores <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> W.T</span>
<span id="cb3-6">error <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>((X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> reconstruction) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb3-7">kept <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(scores<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb3-8"></span>
<span id="cb3-9"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> np.allclose(eigenvalues, S<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb3-10"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> np.isclose(error <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> kept, np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(X<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>))</span>
<span id="cb3-11"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> np.isclose(error, np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(S[k:] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>))</span>
<span id="cb3-12"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> np.allclose(reconstruction, (U[:, :k] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> S[:k]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> Vt[:k])</span>
<span id="cb3-13"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"kept </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>kept<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">; error </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>error<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">; total </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>kept <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> error<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
<div class="cell-output cell-output-stdout">
<pre><code>kept 867.4169; error 35.6804; total 903.0973</code></pre>
</div>
</div>
<p>Rank 1 keeps 867.4169 and leaves 35.6804, adding to 903.0973. These are the peak and trough in the projection figure; rank 2 reconstructs this two-dimensional cloud completely.</p>
<p>The extended checks compare directions up to sign and verify that the eigenvector equation is stationary even at a variance minimum.</p>
<div id="demo-lagrange" class="cell" data-execution_count="4">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1">A <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X.T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> X</span>
<span id="cb5-2">w1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Vt[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb5-3">lam1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(w1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> A <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> w1)                   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># the objective at the stationary point</span></span>
<span id="cb5-4"></span>
<span id="cb5-5"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"||grad L|| = ||2Aw - 2*lambda*w|| = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>norm(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> A <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> w1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> lam1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> w1)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb5-6"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"multiplier lambda        = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>lam1<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.6f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb5-7"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"projected energy ||Xw||^2 = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>((X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> w1) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.6f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb5-8"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"lambda / (n-1)           = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>lam1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.6f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  vs eigenvalue </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>eigenvalues[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.6f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb5-9"></span>
<span id="cb5-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># The multiplier IS the objective, and the gradient vanishes at the eigenvector.</span></span>
<span id="cb5-11"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> np.allclose(A <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> w1, lam1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> w1)</span>
<span id="cb5-12"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> np.isclose(lam1, np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>((X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> w1) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>))</span>
<span id="cb5-13"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> np.isclose(lam1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), eigenvalues[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span>
<span id="cb5-14"></span>
<span id="cb5-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Every eigenvector is stationary, not just the leading one -- including the</span></span>
<span id="cb5-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># worst direction, which is the minimum rather than the maximum.</span></span>
<span id="cb5-17"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb5-18">    wj <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Vt[j]</span>
<span id="cb5-19">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> np.allclose(A <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> wj, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(wj <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> A <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> wj) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> wj)</span>
<span id="cb5-20"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"both eigenvectors satisfy Aw = lambda w; only the larger lambda is the maximum"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>||grad L|| = ||2Aw - 2*lambda*w|| = 3.216e-13
multiplier lambda        = 867.416898
projected energy ||Xw||^2 = 867.416898
lambda / (n-1)           = 4.358879  vs eigenvalue 4.358879
both eigenvectors satisfy Aw = lambda w; only the larger lambda is the maximum</code></pre>
</div>
</div>
<div id="demo-sklearn" class="cell" data-execution_count="5">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> sklearn.decomposition <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> PCA</span>
<span id="cb7-2"></span>
<span id="cb7-3">sk <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> PCA(n_components<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>).fit(X)</span>
<span id="cb7-4"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sklearn components (rows)  "</span>, sk.components_)</span>
<span id="cb7-5"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sklearn explained variance "</span>, sk.explained_variance_)</span>
<span id="cb7-6"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"our eigenvalues            "</span>, eigenvalues)</span>
<span id="cb7-7"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> np.allclose(sk.explained_variance_, eigenvalues)</span>
<span id="cb7-8"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> np.isclose(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(sk.components_[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> Vt[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]), <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>sklearn components (rows)   [[ 0.9262  0.3771]
 [-0.3771  0.9262]]
sklearn explained variance  [4.3589 0.1793]
our eigenvalues             [4.3589 0.1793]</code></pre>
</div>
</div>
</section>
<section id="sec-scaling" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="sec-scaling"><span class="header-section-number">6</span> Centring and scaling</h2>
<p>The six formulations agree on the matrix they receive. Preprocessing determines what distances and variances that matrix represents.</p>
<ul>
<li><strong>Column centring</strong> subtracts feature means. In the original coordinates, the fitted affine subspace passes through the mean; add the mean back to reconstruct observations.</li>
<li><strong>Covariance PCA</strong> retains the chosen measurement scales. Changing a length from centimetres to metres divides its variance by <img src="https://latex.codecogs.com/png.latex?10%5E4"> and can change the answer.</li>
<li><strong>Correlation PCA</strong> divides each nonconstant centred feature by its sample standard deviation. It gives features equal variance, including features that contain mostly noise.</li>
<li><strong>Other transforms</strong> encode other measurement choices. A log compresses large positive values; IQR scaling uses the middle half of the observations to set a feature’s scale. Neither guarantees outlier resistance for PCA itself.</li>
</ul>
<p>Scaling also changes the coordinates of a loading vector. For <img src="https://latex.codecogs.com/png.latex?Y=XD%5E%7B-1%7D">, a score <img src="https://latex.codecogs.com/png.latex?Yv"> equals <img src="https://latex.codecogs.com/png.latex?X(D%5E%7B-1%7Dv)">, so <img src="https://latex.codecogs.com/png.latex?v"> alone is not a coefficient vector in the original units. A nonlinear log transform has no global linear conversion back to raw coordinates.</p>
<p>To compare two preprocessing routes on the same observations, we will compare their score vectors using absolute correlation. This is a descriptive agreement measure, invariant to the arbitrary PC sign, not a test of which route is better.</p>
</section>
<section id="sec-movielens" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="sec-movielens"><span class="header-section-number">7</span> MovieLens genre ratings</h2>
<section id="data-and-task" class="level3" data-number="7.1">
<h3 data-number="7.1" class="anchored" data-anchor-id="data-and-task"><span class="header-section-number">7.1</span> Data and task</h3>
<ul>
<li><strong>Source.</strong> GroupLens collected MovieLens 100K through its recommendation site in 1997–1998 for recommender research: 100,000 volunteered ratings from 943 users on 1,682 films. Users chose which films to rate.</li>
<li><strong>Matrix.</strong> We average ratings in the 12 genres with widest user coverage and retain users with at least three films in every retained genre. The result is a complete <img src="https://latex.codecogs.com/png.latex?313%5Ctimes12"> matrix of means on the <img src="https://latex.codecogs.com/png.latex?1">–<img src="https://latex.codecogs.com/png.latex?5"> scale; it represents relatively active, broad-viewing users.</li>
<li><strong>Question.</strong> Does the leading component describe differences between genres or users’ overall rating levels? Confusing the two could produce a misleading user profile; recommendation accuracy is not measured here.</li>
<li><strong>Why aggregate?</strong> The original user-by-film grid is 93.7% unobserved. Genre aggregation supplies a complete matrix for demonstrating classical PCA, while changing the unit of analysis from films to genres.</li>
<li><strong>Measurement uncertainty.</strong> A cell averages between 3 and 410 ratings. The companion counts file preserves that distinction, and films with several genre tags contribute to several cells.</li>
</ul>
<div id="movielens-load" class="cell" data-execution_count="6">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1">genres <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.read_csv(DATA <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"movielens_genres.csv"</span>, index_col<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb9-2">counts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.read_csv(DATA <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"movielens_counts.csv"</span>, index_col<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb9-3"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> genres.index.equals(counts.index)</span>
<span id="cb9-4"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> genres.columns.equals(counts.columns)</span>
<span id="cb9-5">G <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> genres.to_numpy()</span>
<span id="cb9-6">C <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> counts.to_numpy()</span>
<span id="cb9-7"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> np.isfinite(G).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">all</span>()</span>
<span id="cb9-8"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>G<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> users, </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>G<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> genres"</span>)</span>
<span id="cb9-9"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"ratings per cell: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>C<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> to </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>C<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>313 users, 12 genres
ratings per cell: 3 to 410</code></pre>
</div>
</div>
</section>
<section id="covariance-pca" class="level3" data-number="7.2">
<h3 data-number="7.2" class="anchored" data-anchor-id="covariance-pca"><span class="header-section-number">7.2</span> Covariance PCA</h3>
<p>All columns share a rating scale, so we retain that scale for this demonstration. Both SVD and eigendecomposition are inexpensive for twelve columns.</p>
<div id="movielens-pca" class="cell" data-execution_count="7">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1">Gc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> G <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> G.mean(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)                       <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># centre the columns</span></span>
<span id="cb11-2">Ug, Sg, Vtg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.svd(Gc, full_matrices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb11-3">lam_g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Sg<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (Gc.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb11-4">eig_g, vec_g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.eigh(Gc.T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> Gc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (Gc.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb11-5"></span>
<span id="cb11-6"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"cov route top eigenvalue </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>eig_g[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.10f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb11-7"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"svd route sigma^2/(n-1)  </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>lam_g[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.10f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb11-8"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"|cos| between the two PC1s: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(vec_g[:, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> Vtg[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.12f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb11-9"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"explained variance ratio:"</span>, (lam_g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> lam_g.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>())[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>))</span>
<span id="cb11-10"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cumulative              :"</span>, np.cumsum(lam_g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> lam_g.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>())[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>))</span>
<span id="cb11-11"></span>
<span id="cb11-12"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> k <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>):</span>
<span id="cb11-13">    W <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Vtg[:k].T</span>
<span id="cb11-14">    rel <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>((Gc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> Gc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> W <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> W.T) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(Gc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb11-15">    kept <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>((Gc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> W) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(Gc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb11-16">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"  rank </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>k<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">: relative reconstruction error </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>rel<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, energy kept </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>kept<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb11-17"></span>
<span id="cb11-18"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> np.isclose(eig_g[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], lam_g[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span>
<span id="cb11-19"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> np.isclose(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(vec_g[:, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> Vtg[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]), <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>cov route top eigenvalue 2.1906723037
svd route sigma^2/(n-1)  2.1906723037
|cos| between the two PC1s: 1.000000000000
explained variance ratio: [0.6566 0.0813 0.0649 0.0534]
cumulative              : [0.6566 0.7379 0.8028 0.8563]
  rank 1: relative reconstruction error 0.3434, energy kept 0.6566
  rank 2: relative reconstruction error 0.2621, energy kept 0.7379
  rank 3: relative reconstruction error 0.1972, energy kept 0.8028
  rank 4: relative reconstruction error 0.1437, energy kept 0.8563</code></pre>
</div>
</div>
<p>PC1 accounts for 65.66% of the variance. We inspect its loadings before interpreting that fraction as a taste dimension.</p>
<div id="movielens-pc1" class="cell" data-execution_count="8">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1">order <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.argsort(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(Vtg[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]))</span>
<span id="cb13-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PC1 loadings, largest magnitude first:"</span>)</span>
<span id="cb13-3"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> order:</span>
<span id="cb13-4">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"   </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>genres<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>columns[j]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:12s}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>Vtg[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>][j]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:+.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb13-5"></span>
<span id="cb13-6">pc1_scores <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Gc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> Vtg[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb13-7">user_mean <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> G.mean(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb13-8">r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(np.corrcoef(pc1_scores, user_mean)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span>
<span id="cb13-9"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">corr(PC1 score, that user's mean rating over all 12 genres) = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>r<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb13-10"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(r) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.99</span></span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>PC1 loadings, largest magnitude first:
   Adventure    +0.316
   Horror       +0.313
   Action       +0.308
   Children's   +0.306
   Sci-Fi       +0.287
   Thriller     +0.284
   War          +0.281
   Mystery      +0.278
   Romance      +0.276
   Comedy       +0.272
   Crime        +0.269
   Drama        +0.267

corr(PC1 score, that user's mean rating over all 12 genres) = 0.9999</code></pre>
</div>
</div>
<p>The loadings have one sign and nearly equal magnitudes, between 0.267 and 0.316. The absolute correlation between PC1 scores and users’ unweighted mean genre rating is 0.9999: this component measures overall rating level, which we call rating generosity here.</p>
<p>That description does not identify a psychological trait. Film selection and genre overlap also affect the observed averages.</p>
</section>
<section id="sec-dropfirst" class="level3" data-number="7.3">
<h3 data-number="7.3" class="anchored" data-anchor-id="sec-dropfirst"><span class="header-section-number">7.3</span> Removing users’ overall levels</h3>
<p>If the task is to represent relative genre preferences, we can subtract each user’s mean genre rating before column centring. This specifies a nuisance direction in advance; simply dropping PC1 would only remove that direction when PCA happened to rank it first.</p>
<div id="movielens-drop-vs-debias" class="cell" data-execution_count="9">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb15-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> principal_angles(A, B):</span>
<span id="cb15-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Angles in degrees between the column spaces of two matrices."""</span></span>
<span id="cb15-3">    Qa, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.qr(A)</span>
<span id="cb15-4">    Qb, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.qr(B)</span>
<span id="cb15-5">    s <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.svd(Qa.T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> Qb, compute_uv<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb15-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> np.degrees(np.arccos(np.clip(s, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>)))</span>
<span id="cb15-7"></span>
<span id="cb15-8"></span>
<span id="cb15-9">R <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> G <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> G.mean(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, keepdims<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)         <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># remove each user's own level</span></span>
<span id="cb15-10">Rc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> R <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> R.mean(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)                       <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># then centre the columns</span></span>
<span id="cb15-11">Ur, Sr, Vtr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.svd(Rc, full_matrices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb15-12">lam_r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Sr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (Rc.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb15-13"></span>
<span id="cb15-14"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"rank: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>matrix_rank(Rc)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> after debiasing, "</span></span>
<span id="cb15-15">      <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>matrix_rank(Gc)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> before"</span>)</span>
<span id="cb15-16"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"angles between PC2-PC3 of the raw fit and PC1-PC2 of the debiased fit:"</span>,</span>
<span id="cb15-17">      principal_angles(Vtg[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>].T, Vtr[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>].T).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"deg"</span>)</span>
<span id="cb15-18">share <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>((Rc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> Vtg[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]).var(ddof<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> Rc.var(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, ddof<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>())</span>
<span id="cb15-19"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"share of debiased variance lying along the raw PC1: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>share<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4%}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb15-20"></span>
<span id="cb15-21"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> principal_angles(Vtg[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>].T, Vtr[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>].T).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.0</span></span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>rank: 11 after debiasing, 12 before
angles between PC2-PC3 of the raw fit and PC1-PC2 of the debiased fit: [0.2  1.33] deg
share of debiased variance lying along the raw PC1: 0.0453%</code></pre>
</div>
</div>
<p>On this table the two-dimensional spans differ by principal angles of <img src="https://latex.codecogs.com/png.latex?0.20%5E%5Ccirc"> and <img src="https://latex.codecogs.com/png.latex?1.33%5E%5Ccirc">, so removing the mean and dropping raw PC1 produce similar retained spans. They are not identical operations in general.</p>
<p>Row centring reduces the rank from 12 to 11 because all rows become orthogonal to the all-ones vector. The first five variance shares then become 23.5%, 18.8%, 15.8%, 12.9%, and 9.9%: less concentrated than the original spectrum, but not tied eigenvalues.</p>
<div id="cell-fig-movielens" class="cell" data-fig-height="3.5" data-fig-width="8.6" data-execution_count="10">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb17-1">fig, (ax_a, ax_b) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8.6</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.5</span>))</span>
<span id="cb17-2">ax_a.scatter(user_mean, pc1_scores, s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">14</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>TEAL, alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.65</span>)</span>
<span id="cb17-3">ax_a.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(xlabel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user's mean genre rating"</span>, ylabel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"raw PC1 score"</span>,</span>
<span id="cb17-4">         title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Overall rating level (|r| = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(r)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">)"</span>)</span>
<span id="cb17-5"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> values, name, colour <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> ((lam_g, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"column-centred"</span>, ACCENT),</span>
<span id="cb17-6">                             (lam_r, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"row- then column-centred"</span>, CORAL)):</span>
<span id="cb17-7">    ax_b.plot(np.arange(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">13</span>), values <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> values.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"o-"</span>, ms<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>,</span>
<span id="cb17-8">              color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>colour, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>name)</span>
<span id="cb17-9">ax_b.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(xlabel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"component"</span>, ylabel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"share of variance"</span>, title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Changing the question"</span>)</span>
<span id="cb17-10">ax_b.set_xticks([<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>])</span>
<span id="cb17-11">ax_b.legend(frameon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb17-12">fig.tight_layout(pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.1</span>)</span>
<span id="cb17-13">plt.show()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-movielens" class="quarto-float quarto-figure quarto-figure-center anchored" alt="A nearly straight scatter of mean genre rating against PC1 score, beside two component spectra. Raw PC1 explains about 66 percent; after row centring PC1 explains about 24 percent.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-movielens-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/six-views-of-pca/index_files/figure-html/fig-movielens-output-1.png" class="img-fluid figure-img" alt="A nearly straight scatter of mean genre rating against PC1 score, beside two component spectra. Raw PC1 explains about 66 percent; after row centring PC1 explains about 24 percent.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-movielens-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: Left: raw PC1 scores track users’ mean genre ratings. Right: variance shares before and after removing users’ means; each curve is normalized by its own total variance.
</figcaption>
</figure>
</div>
</div>
</div>
</section>
<section id="interpretation-limits" class="level3" data-number="7.4">
<h3 data-number="7.4" class="anchored" data-anchor-id="interpretation-limits"><span class="header-section-number">7.4</span> Interpretation limits</h3>
<ul>
<li><strong>Counts are not an independent-error model.</strong> The approximation <img src="https://latex.codecogs.com/png.latex?%5Coperatorname%7BVar%7D(%5Cbar%20r_%7Bij%7D)=s%5E2/n_%7Bij%7D"> needs sampling assumptions. Genre overlap induces covariance between cell means, so a diagonal noise covariance would omit part of the uncertainty.</li>
<li><strong>This spectrum does not choose a recommender’s dimension.</strong> A stable subspace can still predict poorly. Choosing <img src="https://latex.codecogs.com/png.latex?k"> for recommendations requires held-out ratings and a pipeline that fits preprocessing on training data.</li>
<li><strong>No noise fraction or deployment threshold is inferred here.</strong> Quantifying them would require a rating-level model or resampling scheme that preserves the overlaps, followed by task-specific validation.</li>
</ul>
</section>
</section>
<section id="sec-metabolome" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="sec-metabolome"><span class="header-section-number">8</span> Neutrophil metabolomics</h2>
<section id="data-and-task-1" class="level3" data-number="8.1">
<h3 data-number="8.1" class="anchored" data-anchor-id="data-and-task-1"><span class="header-section-number">8.1</span> Data and task</h3>
<ul>
<li><strong>Source.</strong> Li et al.&nbsp;(2023), Metabolomics Workbench study ST002477, CC BY 4.0: LC-MS peak areas for 285 metabolites in neutrophils from 75 people, labelled Control (19), Mild (30), or Severe (26) COVID-19. The researchers isolated neutrophils to study their metabolic state during disease.</li>
<li><strong>Matrix.</strong> We use the complete <img src="https://latex.codecogs.com/png.latex?75%5Ctimes285"> matrix, with samples as rows. Peak areas measure relative ion intensities, not metabolic fluxes.</li>
<li><strong>Question.</strong> How does preprocessing change the leading component, and are its scores associated with the disease groups? PCA describes variation without using those labels to fit the directions.</li>
<li><strong>Consequence of error.</strong> Mistaking technical variation for a biological association could misdirect follow-up experiments. The supplied metadata contains only sample IDs and disease groups; it cannot establish whether batch or run order is confounded with disease.</li>
</ul>
<div id="metabolome-load" class="cell" data-execution_count="11">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb18-1">abund <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.read_csv(DATA <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"abundances.tsv"</span>, sep<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\t</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>, index_col<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb18-2">meta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.read_csv(DATA <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"samples.tsv"</span>, sep<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\t</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb18-3"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(abund.columns) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(meta[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sample_id"</span>])</span>
<span id="cb18-4"></span>
<span id="cb18-5">Xm_raw <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> abund.to_numpy(dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>np.float64).T</span>
<span id="cb18-6">labels <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> meta[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"group"</span>].to_numpy()</span>
<span id="cb18-7"></span>
<span id="cb18-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Code severity once, here, and refuse anything the coding does not cover: a</span></span>
<span id="cb18-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># fourth group or a stray case variant would otherwise be silently scored as</span></span>
<span id="cb18-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Severe and shift every correlation computed from it.</span></span>
<span id="cb18-11">GROUPS <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Control"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Mild"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Severe"</span>)</span>
<span id="cb18-12"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(labels) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(GROUPS), <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sorted</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(labels))</span>
<span id="cb18-13">severity <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.select([labels <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Control"</span>, labels <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Mild"</span>], [<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>], <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>)</span>
<span id="cb18-14"></span>
<span id="cb18-15">t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.log1p(Xm_raw)</span>
<span id="cb18-16">median <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.median(t, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb18-17">iqr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.subtract(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>np.percentile(t, [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">75</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">25</span>], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>))</span>
<span id="cb18-18">iqr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.where(iqr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, iqr)</span>
<span id="cb18-19">Zm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> median) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> iqr</span>
<span id="cb18-20">Xm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Zm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> Zm.mean(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb18-21"></span>
<span id="cb18-22">n_m, d_m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Xm.shape</span>
<span id="cb18-23"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"matrix: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>n_m<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> samples x </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>d_m<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> metabolites"</span>)</span>
<span id="cb18-24"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"groups:"</span>, {g: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>((labels <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> g).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>()) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> GROUPS})</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>matrix: 75 samples x 285 metabolites
groups: {'Control': 19, 'Mild': 30, 'Severe': 26}</code></pre>
</div>
</div>
</section>
<section id="sec-metabolome-scaling" class="level3" data-number="8.2">
<h3 data-number="8.2" class="anchored" data-anchor-id="sec-metabolome-scaling"><span class="header-section-number">8.2</span> Comparing preprocessing routes</h3>
<p>We compare raw covariance PCA, correlation PCA, and PCA after <img src="https://latex.codecogs.com/png.latex?%5Clog(1+x)"> plus IQR scaling. The log/IQR route matches <a href="../neutrophil-metabolome-axis/">Neutrophil Metabolomes as a Matrix</a>; it compresses large peak areas and reduces the influence of extreme observations on the scale estimate.</p>
<p>The added 1 assumes the numerical units in this file: changing those units changes <img src="https://latex.codecogs.com/png.latex?%5Clog(1+x)">. The transform is a stated analysis choice, not a universal correction for skew or outliers.</p>
<div id="metabolome-scaling" class="cell" data-execution_count="12">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb20" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb20-1">raw_var <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Xm_raw.var(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, ddof<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb20-2">names_m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> abund.index.to_numpy()</span>
<span id="cb20-3"></span>
<span id="cb20-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> fit(M):</span>
<span id="cb20-5">    A <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> M <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> M.mean(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb20-6">    _, singular, Vt_ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.svd(A, full_matrices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb20-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> A, Vt_, singular<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(singular<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb20-8"></span>
<span id="cb20-9">A_cov, Vt_cov_m, r_cov <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fit(Xm_raw)</span>
<span id="cb20-10">A_cor, Vt_cor_m, r_cor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fit(Xm_raw <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> Xm_raw.std(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, ddof<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb20-11">A_log, Vt_log_m, r_log <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fit(Zm)</span>
<span id="cb20-12">routes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"raw"</span>: (A_cov, Vt_cov_m, r_cov),</span>
<span id="cb20-13">          <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"standardized"</span>: (A_cor, Vt_cor_m, r_cor),</span>
<span id="cb20-14">          <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"log/IQR"</span>: (A_log, Vt_log_m, r_log)}</span>
<span id="cb20-15">route_scores <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {name: A <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> Vt_[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name, (A, Vt_, _) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> routes.items()}</span>
<span id="cb20-16"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"largest/smallest raw feature variance: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>raw_var<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> raw_var<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb20-17">biggest <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(np.argmax(raw_var))</span>
<span id="cb20-18"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"largest-variance metabolite: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>names_m[biggest]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb20-19"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"its share of raw squared PC1 loading: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>Vt_cov_m[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, biggest]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1%}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb20-20"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name, (_, _, ratios) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> routes.items():</span>
<span id="cb20-21">    agreement <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(np.corrcoef(route_scores[name], route_scores[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"log/IQR"</span>])[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span>
<span id="cb20-22">    association <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(np.corrcoef(route_scores[name], severity)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span>
<span id="cb20-23">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:12s}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">: PC1 </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>ratios[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1%}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">; |corr with log/IQR scores| "</span></span>
<span id="cb20-24">          <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>agreement<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">; |corr with severity| </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>association<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>largest/smallest raw feature variance: 9.49e+09
largest-variance metabolite: choline
its share of raw squared PC1 loading: 51.9%
raw         : PC1 49.5%; |corr with log/IQR scores| 0.6159; |corr with severity| 0.4561
standardized: PC1 16.1%; |corr with log/IQR scores| 0.9811; |corr with severity| 0.5472
log/IQR     : PC1 20.7%; |corr with log/IQR scores| 1.0000; |corr with severity| 0.5363</code></pre>
</div>
</div>
<p>Raw feature variances span a factor of <img src="https://latex.codecogs.com/png.latex?9.49%5Ctimes10%5E9">. Raw PC1 explains 49.5% of the variance and assigns 51.9% of its squared loading to choline alone, the highest-variance column.</p>
<p>The standardized and log/IQR score vectors have absolute correlation 0.9811; the raw and log/IQR scores have absolute correlation 0.6159. These compare how the routes arrange the same 75 samples, without mixing loading vectors expressed in different coordinates.</p>
<p>Absolute correlations with severity are 0.4561 (raw), 0.5472 (standardized), and 0.5363 (log/IQR). These are descriptive associations with an ordinal label coded 0/1/2, which assumes equal spacing between categories; they do not rank the preprocessing choices.</p>
<p>The correlations share observations and the severity variable. A standard error for one correlation cannot establish whether their difference is meaningful; that requires joint uncertainty, such as a paired resampling analysis that refits preprocessing and PCA.</p>
<div id="cell-fig-scaling" class="cell" data-fig-height="3.4" data-fig-width="8.6" data-execution_count="13">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb22" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb22-1">fig, (ax_v, ax_l) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8.6</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.4</span>))</span>
<span id="cb22-2"></span>
<span id="cb22-3">ax_v.semilogy(np.sort(raw_var)[::<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ACCENT, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.6</span>)</span>
<span id="cb22-4">ax_v.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"metabolite, sorted by variance"</span>)</span>
<span id="cb22-5">ax_v.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"variance as measured (log scale)"</span>)</span>
<span id="cb22-6">ax_v.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Ten orders of magnitude"</span>)</span>
<span id="cb22-7"></span>
<span id="cb22-8">ax_l.semilogy(np.sort(Vt_cov_m[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)[::<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>CORAL, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.6</span>,</span>
<span id="cb22-9">              label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"covariance, as measured"</span>)</span>
<span id="cb22-10">ax_l.semilogy(np.sort(Vt_log_m[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)[::<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>TEAL, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.6</span>,</span>
<span id="cb22-11">              label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"log1p + IQR"</span>)</span>
<span id="cb22-12">ax_l.axhline(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> d_m, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>MUTED, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, ls<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--"</span>)</span>
<span id="cb22-13">ax_l.text(d_m, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> d_m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.4</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"equal weight on every metabolite"</span>,</span>
<span id="cb22-14">          color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>MUTED, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7.5</span>, ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"right"</span>)</span>
<span id="cb22-15">ax_l.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"metabolite, sorted by loading"</span>)</span>
<span id="cb22-16">ax_l.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"squared PC1 loading (log scale)"</span>)</span>
<span id="cb22-17">ax_l.legend(frameon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7.5</span>, loc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lower left"</span>)</span>
<span id="cb22-18">ax_l.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PC1 weight concentration"</span>)</span>
<span id="cb22-19"></span>
<span id="cb22-20">fig.tight_layout(pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.1</span>)</span>
<span id="cb22-21">plt.show()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-scaling" class="quarto-float quarto-figure quarto-figure-center anchored" alt="Two panels. The left is a steeply falling curve of variance against metabolite rank on a log scale spanning ten decades. The right shows two sorted squared-loading curves: the covariance one starts above 0.5 and collapses, the log/IQR one starts near 0.01 and decays gently.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-scaling-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/six-views-of-pca/index_files/figure-html/fig-scaling-output-1.png" class="img-fluid figure-img" alt="Two panels. The left is a steeply falling curve of variance against metabolite rank on a log scale spanning ten decades. The right shows two sorted squared-loading curves: the covariance one starts above 0.5 and collapses, the log/IQR one starts near 0.01 and decays gently.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-scaling-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;4: Left: per-metabolite variance as measured, sorted, over ten orders of magnitude. Right: squared loadings of PC1, sorted, under covariance PCA on the file as measured and under the log/IQR transform.
</figcaption>
</figure>
</div>
</div>
</div>
<p>The squared loadings describe weight concentration within each route’s coordinates. They are not raw-unit effect sizes or evidence that log/IQR has recovered a biological direction.</p>
</section>
<section id="wide-matrices" class="level3" data-number="8.3">
<h3 data-number="8.3" class="anchored" data-anchor-id="wide-matrices"><span class="header-section-number">8.3</span> Wide matrices</h3>
<p>With <img src="https://latex.codecogs.com/png.latex?n=75"> and <img src="https://latex.codecogs.com/png.latex?d=285">, centring caps the rank at <img src="https://latex.codecogs.com/png.latex?n-1=74">. At least 211 covariance eigenvalues must be zero; that arithmetic does not imply that 211 biological processes are absent.</p>
<div id="metabolome-pca" class="cell" data-execution_count="14">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb23" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb23-1">Um, Sm, Vtm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.svd(Xm, full_matrices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb23-2">lam_m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Sm<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (n_m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb23-3"></span>
<span id="cb23-4"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"rank(X) = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>matrix_rank(Xm)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, min(n, d) = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(n_m, d_m)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, n - 1 = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>n_m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb23-5"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"non-negligible singular values: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>((Sm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-9</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> Sm[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>())<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> of </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(Sm)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb23-6"></span>
<span id="cb23-7">cov <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Xm.T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> Xm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (n_m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb23-8">eig_m, vec_m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.eigh(cov)</span>
<span id="cb23-9"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"covariance matrix is </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>cov<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> x </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>cov<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb23-10"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"cov route top eigenvalue </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>eig_m[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.10f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb23-11"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"svd route sigma^2/(n-1)  </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>lam_m[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.10f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb23-12"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"|cos| between the two PC1s: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(vec_m[:, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> Vtm[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.12f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb23-13"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"explained variance ratio:"</span>, (lam_m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> lam_m.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>())[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>))</span>
<span id="cb23-14"></span>
<span id="cb23-15"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> np.linalg.matrix_rank(Xm) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> n_m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb23-16"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> np.isclose(eig_m[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], lam_m[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span>
<span id="cb23-17"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> np.isclose(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(vec_m[:, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> Vtm[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]), <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>rank(X) = 74, min(n, d) = 75, n - 1 = 74
non-negligible singular values: 74 of 75
covariance matrix is 285 x 285
cov route top eigenvalue 47.4693303238
svd route sigma^2/(n-1)  47.4693303238
|cos| between the two PC1s: 1.000000000000
explained variance ratio: [0.2073 0.1136 0.0596 0.055 ]</code></pre>
</div>
</div>
<p>The covariance and SVD routes agree on the leading direction and eigenvalue. Both are feasible at this size; direct SVD avoids the larger covariance matrix and the loss of numerical accuracy from forming it.</p>
<div id="metabolome-scores" class="cell" data-execution_count="15">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb25" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb25-1">scores_m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Xm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> Vtm[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>].T</span>
<span id="cb25-2">corr_sev <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(np.corrcoef(scores_m[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], severity)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span>
<span id="cb25-3"></span>
<span id="cb25-4"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"PC1 </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>lam_m[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> lam_m<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1%}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> of variance, "</span></span>
<span id="cb25-5">      <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"PC2 </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>lam_m[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> lam_m<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1%}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, "</span></span>
<span id="cb25-6">      <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"first two together </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>cumsum(lam_m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> lam_m.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>())[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1%}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb25-7"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> GROUPS:</span>
<span id="cb25-8">    s <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> scores_m[labels <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> g, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb25-9">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"  </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>g<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:8s}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> PC1 mean </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>s<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>mean()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:+6.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  sd </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>s<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>std()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:5.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb25-10"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"corr(PC1 score, severity coded 0/1/2) = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>corr_sev<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb25-11"></span>
<span id="cb25-12"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> k <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">74</span>):</span>
<span id="cb25-13">    W <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Vtm[:k].T</span>
<span id="cb25-14">    rel <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>((Xm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> Xm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> W <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> W.T) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(Xm<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb25-15">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"  rank </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>k<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:3d}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">: relative reconstruction error </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>rel<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>PC1 20.7% of variance, PC2 11.4%, first two together 32.1%
  Control  PC1 mean  -6.95  sd  7.74
  Mild     PC1 mean  +1.75  sd  5.37
  Severe   PC1 mean  +3.06  sd  3.13
corr(PC1 score, severity coded 0/1/2) = 0.5363
  rank   1: relative reconstruction error 0.7927
  rank   2: relative reconstruction error 0.6791
  rank   5: relative reconstruction error 0.5156
  rank  10: relative reconstruction error 0.3571
  rank  74: relative reconstruction error 0.0000</code></pre>
</div>
</div>
<p>PC1 explains 20.7% and PC2 11.4%, for 32.1% together. The group means differ along PC1, but the observations overlap; the sign of the scores is arbitrary.</p>
<div id="cell-fig-metabolome" class="cell" data-fig-height="3.6" data-fig-width="8.6" data-execution_count="16">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb27" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb27-1">fig, (ax_s, ax_p) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8.6</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.6</span>))</span>
<span id="cb27-2"></span>
<span id="cb27-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># All d eigenvalues of the covariance, not the n kept by the thin SVD, so the</span></span>
<span id="cb27-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 211 that are zero for want of samples are on the page.</span></span>
<span id="cb27-5">spectrum <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> eig_m[::<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb27-6">floor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> spectrum[: n_m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-2</span></span>
<span id="cb27-7">ax_s.semilogy(np.arange(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, d_m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), np.maximum(spectrum, floor),</span>
<span id="cb27-8">              color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ACCENT, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span>)</span>
<span id="cb27-9">ax_s.axvline(n_m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>CORAL, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, ls<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--"</span>)</span>
<span id="cb27-10">ax_s.annotate(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"rank </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>n_m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> = n − 1"</span>, xy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(n_m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, spectrum[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.15</span>),</span>
<span id="cb27-11">              color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>CORAL, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb27-12">ax_s.set_ylim(floor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, spectrum[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb27-13">ax_s.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"component index"</span>)</span>
<span id="cb27-14">ax_s.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"eigenvalue (log scale)"</span>)</span>
<span id="cb27-15">ax_s.set_title(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>d_m<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> features, </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>n_m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> directions"</span>)</span>
<span id="cb27-16"></span>
<span id="cb27-17">COLOURS <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Control"</span>: TEAL, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Mild"</span>: GOLD, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Severe"</span>: CORAL}</span>
<span id="cb27-18"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> GROUPS:</span>
<span id="cb27-19">    sel <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> labels <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> g</span>
<span id="cb27-20">    ax_p.scatter(scores_m[sel, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], scores_m[sel, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">26</span>,</span>
<span id="cb27-21">                 color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>COLOURS[g], alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.85</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>g)</span>
<span id="cb27-22">ax_p.set_xlabel(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"PC1 (</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>lam_m[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> lam_m<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1%}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">)"</span>)</span>
<span id="cb27-23">ax_p.set_ylabel(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"PC2 (</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>lam_m[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> lam_m<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1%}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">)"</span>)</span>
<span id="cb27-24">ax_p.legend(frameon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>, framealpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.9</span>, edgecolor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>RULE, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>,</span>
<span id="cb27-25">            loc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lower left"</span>, scatterpoints<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb27-26">ax_p.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Scores, coloured after the fact"</span>)</span>
<span id="cb27-27"></span>
<span id="cb27-28">fig.tight_layout(pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.1</span>)</span>
<span id="cb27-29">plt.show()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-metabolome" class="quarto-float quarto-figure quarto-figure-center anchored" alt="Two panels. The left is a log-scale plot of eigenvalue against index for 285 components, decaying smoothly then dropping off a cliff at index 74 to a flat floor. The right is a scatter of 75 points on two principal components, coloured in three groups that overlap substantially.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-metabolome-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/six-views-of-pca/index_files/figure-html/fig-metabolome-output-1.png" class="img-fluid figure-img" alt="Two panels. The left is a log-scale plot of eigenvalue against index for 285 components, decaying smoothly then dropping off a cliff at index 74 to a flat floor. The right is a scatter of 75 points on two principal components, coloured in three groups that overlap substantially.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-metabolome-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;5: Left: all 285 covariance eigenvalues, cut off at 74 by the sample count. Eigenvalues past 74 are zero and are drawn at a floor so the log axis can hold them. Right: the 75 samples on PC1 and PC2, coloured by COVID label.
</figcaption>
</figure>
</div>
</div>
</div>
</section>
<section id="interpretation-limits-1" class="level3" data-number="8.4">
<h3 data-number="8.4" class="anchored" data-anchor-id="interpretation-limits-1"><span class="header-section-number">8.4</span> Interpretation limits</h3>
<ul>
<li><strong>Association is observable; its source is unresolved.</strong> PC1 is associated with the coded severity label. If processing batch also follows severity, that association could be technical, biological, or both. <a href="https://www.nature.com/articles/nrg2825">Leek et al.&nbsp;(2010)</a> show how this confounding can mislead high-throughput analyses.</li>
<li><strong>This is not a passed QC gate.</strong> Batch and run-order metadata, technical controls, and the experimental design are needed to assess technical effects before differential-abundance analysis. Neither label separation nor its absence supplies that assessment by itself.</li>
<li><strong>Overlap limits prediction claims.</strong> The plot does not validate an individual severity score. It also does not prove that PC1 lacks disease-related information; prediction would need independent evaluation.</li>
<li><strong>Compression has a measurable cost.</strong> Rank 10 leaves 35.71% of the transformed matrix’s squared magnitude unreconstructed. Whether that loss is acceptable depends on the intended task, not on the rank bound alone.</li>
</ul>
</section>
</section>
<section id="constraints" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="constraints"><span class="header-section-number">9</span> Constraints</h2>
<ul>
<li><strong>Missing entries.</strong> With an observation mask, the observed data’s squared magnitude is fixed, but residuals are no longer orthogonal under the masked loss. The unweighted projector split therefore does not transfer to observed-entry fitting. Filling the holes makes classical PCA computable, while making the answer depend on the filling rule.</li>
<li><strong>Loss choice.</strong> The trace and variance identities rely on squared Frobenius error. Truncated SVD also minimizes rank-constrained error in any unitarily invariant norm, including the spectral norm, by Eckart–Young–Mirsky. Entrywise <img src="https://latex.codecogs.com/png.latex?%5Cell_1"> and Huber losses generally require different solutions.</li>
<li><strong>Stability.</strong> A boundary eigenvalue gap concerns uniqueness; its size relative to perturbations concerns estimation stability. Neither a flat-looking scree plot nor an average noise level is a universal cutoff for <img src="https://latex.codecogs.com/png.latex?k">.</li>
<li><strong>Interpretation.</strong> Large variance can reflect measurement scale, overall response level, technical effects, or biology. Explained variance measures reconstruction of the chosen matrix; it does not measure predictive usefulness or causality.</li>
</ul>
</section>
<section id="related-questions" class="level2" data-number="10">
<h2 data-number="10" class="anchored" data-anchor-id="related-questions"><span class="header-section-number">10</span> Related questions</h2>
<p><a href="../probabilistic-pca/">Probabilistic PCA</a> adds a Gaussian model and a posterior distribution for latent coordinates. Its fitted principal subspace agrees with classical PCA under the stated model, while its posterior reconstruction generally shrinks toward the mean.</p>
<p>Tensor PCA is a topic for a separate post.</p>
<p>Centre. Scale. Fix. Geometry. Fit. Subspaces. Check. Stability. Validate. Interpretation.</p>
</section>
<section id="references" class="level2" data-number="11">
<h2 data-number="11" class="anchored" data-anchor-id="references"><span class="header-section-number">11</span> References</h2>
<ul>
<li>Eckart, C., and Young, G. (1936). <a href="https://doi.org/10.1007/BF02288367">The approximation of one matrix by another of lower rank</a>. <em>Psychometrika</em> 1(3), 211–218.</li>
<li>Golub, G. H., and Van Loan, C. F. (2013). <em>Matrix Computations</em>, 4th edition. Johns Hopkins University Press.</li>
<li>Hotelling, H. (1933). <a href="https://doi.org/10.1037/h0071325">Analysis of a complex of statistical variables into principal components</a>. <em>Journal of Educational Psychology</em> 24(6), 417–441.</li>
<li>Leek, J. T., et al.&nbsp;(2010). <a href="https://www.nature.com/articles/nrg2825">Tackling the widespread and critical impact of batch effects in high-throughput data</a>. <em>Nature Reviews Genetics</em> 11, 733–739.</li>
<li>Li, Y., et al.&nbsp;(2023). Neutrophil metabolomics in COVID-19. Metabolomics Workbench study <a href="https://www.metabolomicsworkbench.org/data/DRCCMetadata.php?Mode=Study&amp;StudyID=ST002477">ST002477</a>, CC BY 4.0.</li>
<li>Mirsky, L. (1960). <a href="https://doi.org/10.1093/qmath/11.1.50">Symmetric gauge functions and unitarily invariant norms</a>. <em>The Quarterly Journal of Mathematics</em> 11(1), 50–59.</li>
<li>Pearson, K. (1901). <a href="https://doi.org/10.1080/14786440109462720">On lines and planes of closest fit to systems of points in space</a>. <em>Philosophical Magazine</em> 2(11), 559–572.</li>
<li><a href="https://grouplens.org/datasets/movielens/100k/">MovieLens 100K</a> — GroupLens, University of Minnesota.</li>
<li><a href="../eigendecomposition/">The Directions a Matrix Refuses to Turn</a> — eigenvectors and eigenvalues.</li>
<li><a href="../svd-rotate-stretch-rotate/">The Matrix That Rotates, Stretches, and Rotates Again</a> — SVD geometry and numerical accuracy.</li>
<li><a href="../neutrophil-metabolome-axis/">Neutrophil Metabolomes as a Matrix</a> — clustering the 75 × 285 file.</li>
<li><a href="../probabilistic-pca/">Probabilistic PCA</a> — a Gaussian latent-variable model, its fit, and posterior reconstruction.</li>
</ul>


</section>

 ]]></description>
  <category>Linear Algebra</category>
  <category>Machine Learning</category>
  <category>Mathematics</category>
  <guid>https://project-delphi.github.io/ml-blog/posts/six-views-of-pca/</guid>
  <pubDate>Sun, 13 Sep 2026 05:00:00 GMT</pubDate>
  <media:content url="https://project-delphi.github.io/ml-blog/posts/six-views-of-pca/cover.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>GAPDH as a NET suppressor</title>
  <dc:creator>Ravi Kalia</dc:creator>
  <link>https://project-delphi.github.io/ml-blog/posts/gapdh-net-suppressor/</link>
  <description><![CDATA[ 





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/gapdh-net-suppressor/cover.png" class="img-fluid figure-img" alt="Glycolysis blocked at GAPDH, then intracellular pH rising to neutrophil elastase and DNA extrusion."></p>
<figcaption>GAPDH as a NET suppressor</figcaption>
</figure>
</div>
<p>A labelled metabolome with three COVID groups will classify. That is the easy trick. It does not name the enzyme that lets neutrophils throw nets of DNA into a lung that is already failing.</p>
<p>Li et al.&nbsp;(2023) asked that enzyme question. They measured metabolites in neutrophils from 75 people — severe COVID-19, mild COVID-19, or no infection — then they blocked one glycolytic enzyme in healthy cells and watched nets form. The public spreadsheet is the first experiment. The mechanism is the second. <a href="../neutrophil-metabolome-axis/">Neutrophil Metabolomes as a Matrix</a> is the layout of the spreadsheet: Mild does not sit on the Control–Severe line. This review is the follow-up: what GAPDH is doing, why pH — not extra NADPH — is the trigger, and which array shape those inhibitor tables would need before a tensor factorization is the right tool.</p>
<p>The deposited cohort is Metabolomics Workbench ST002477: 75 people, one snapshot each, 285 unique LC-MS peak-area features, CC BY 4.0. The numbers are chromatogram peak areas, not fluxes and not NET counts. The inhibitor tables — live-cell movies, pH dyes, <img src="https://latex.codecogs.com/png.latex?%5E%7B13%7D">C tracing — were not deposited as a cube.</p>
<section id="neutrophils-throw-dna-nets-and-do-not-come-back" class="level2">
<h2 class="anchored" data-anchor-id="neutrophils-throw-dna-nets-and-do-not-come-back">Neutrophils throw DNA nets and do not come back</h2>
<p>Most white cells in blood are neutrophils. They live hours to a couple of days. Their job is to find bacteria and fungi and kill them in one of three ways: swallow them, dump bleach-like oxidants, or throw out a net of their own DNA, studded with enzymes, that traps microbes outside the cell. Brinkmann et al.&nbsp;(2004) showed those nets kill bacteria. The same structures turn up in lungs that are already failing in severe COVID-19.</p>
<p>Throwing a net is not a reversible state. The nuclear envelope breaks, the chromatin unpacks, and DNA leaves the cell. The cell is gone. Immunologists call that NETosis. A short-lived cell in low-oxygen tissue still has to make ATP — energy — and NADPH, the reducing power for the oxidant burst. It also has to keep the net programme off until something warrants the cost. Metabolism is not only fuel. It is the switch.</p>
<p>What still needs a name is the switch itself. The paper’s claim is that one enzyme in glycolysis is holding it.</p>
</section>
<section id="gapdh-is-a-valve-in-glycolysis-not-a-housekeeping-stamp" class="level2">
<h2 class="anchored" data-anchor-id="gapdh-is-a-valve-in-glycolysis-not-a-housekeeping-stamp">GAPDH is a valve in glycolysis, not a housekeeping stamp</h2>
<p>The enzyme that holds the switch is GAPDH. Cells burn glucose in a chain of enzymes called glycolysis. About halfway down that chain sits glyceraldehyde-3-phosphate dehydrogenase. It takes a three-carbon sugar, oxidises it, and in the same step reduces <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BNAD%7D%5E+"> to NADH and releases a proton, <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BH%7D%5E+">. Textbooks call it housekeeping because every cell has it. In a neutrophil it behaves more like a valve. As long as sugar is flowing through GAPDH, the cell stays on the glycolytic path and keeps making acid. Block the valve and two things happen at once. Unprocessed sugar phosphates pile up upstream. Proton production falls, so the inside of the cell becomes more alkaline.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
A bottleneck, in plain terms
</div>
</div>
<div class="callout-body-container callout-body">
<p>Think of a pipe with a narrow stretch. Slow that stretch and liquid backs up on one side and runs dry on the other. GAPDH is that stretch. Li et al.&nbsp;treat it as a rate-limiting node whose flow keeps nets off. They do not treat it as one more loading on a severity axis.</p>
</div>
</div>
<p>A useful diagnostic of a block is a ratio. DHAP (dihydroxyacetone phosphate) sits just upstream of GAPDH. BPG (1,3-bisphosphoglycerate) sits just downstream. If GAPDH is slow, DHAP rises relative to BPG. Li et al.&nbsp;saw that DHAP:BPG ratio elevated in neutrophils from people with severe COVID-19. That is consistent with reduced GAPDH activity. It is not a proof that reactive oxygen species oxidised the enzyme in those patients. A snapshot ratio is a clue. It is not a knockout.</p>
<p>So they did the knockout the only way primary human neutrophils allow: they added a drug.</p>
</section>
<section id="blocking-gapdh-is-enough-to-make-a-net" class="level2">
<h2 class="anchored" data-anchor-id="blocking-gapdh-is-enough-to-make-a-net">Blocking GAPDH is enough to make a net</h2>
<p>Heptelidic acid — also sold as koningic acid; one molecule, two names — is a covalent GAPDH inhibitor made by the fungus <em>Trichoderma koningii</em>. Iodoacetate is a second, chemically different inhibitor. Either one, added to neutrophils taken from healthy donors, is enough to make NETs.</p>
<p>Watch the traffic after the valve closes. Upper-glycolysis intermediates — DHAP, fructose-1,6-bisphosphate (FBP), fructose-6-phosphate (F6P) — rise. Lower-glycolysis products — pyruvate, BPG — fall. The <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BNAD%7D%5E+">/NADH ratio rises, as expected once GAPDH stops reducing <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BNAD%7D%5E+">.</p>
<p>Glucose-6-phosphate can still peel off into the pentose-phosphate pathway, a shunt that makes NADPH. That shunt gets busier after GAPDH is blocked. It is easy to guess that extra NADPH then drives the oxidant burst, and the oxidants make the net. That is how PMA, a classic lab trigger, makes nets. It is not how heptelidic acid does it.</p>
<p>Neutrophils make a burst of superoxide using NADPH oxidase (NOX). PMA needs that burst. Blocking G6PD — the first enzyme of the oxidative shunt — or blocking NOX with diphenyleneiodonium (DPI) stops PMA nets. The same blockers leave HA nets intact. DPI can even speed HA nets. HA also failed to raise mitochondrial ROS or collapse the GSH/GSSG ratio, the usual fingerprints of an oxidant crisis. So the HA path is not “more NADPH, more oxidants, more nets.”</p>
<p>DHAP that cannot go through GAPDH spontaneously becomes methylglyoxal, a sticky aldehyde. That molecule blunts the respiratory burst. HA-treated cells are <em>worse</em> at making oxidants, not better.</p>
<p>The GAPDH reaction itself produces <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BH%7D%5E+">. Stop the reaction and the cell makes less acid. Intracellular pH rises. Buffer the outside with HEPES, or stop the cell exporting leftover protons with cariporide — an inhibitor of the <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BNa%7D%5E+/%5Cmathrm%7BH%7D%5E+"> exchanger neutrophils use to set pH — and the pH rise is gone, and so are death and NET formation. The metabolic pile-up is not gone. DHAP and BPG stay where HA put them. pH is downstream of the block. The metabolome of an HA-treated cell still looks blocked even when the cell is saved.</p>
<p>Neutrophil elastase is a protease stored in granules. At higher pH it works better. Inhibitors of elastase (AZD9668, BAY-678, GW311616a) do not stop the membrane from becoming leaky. They stop DNA from leaving. The last mechanical step of the net is enzyme cutting, not a passive leak.</p>
<p>Severe COVID-19 serum is full of inflammatory cytokines. TNF-<img src="https://latex.codecogs.com/png.latex?%5Calpha"> pushes glycolysis forward. HA still blocks at GAPDH in that setting. Inflammatory drive and the GAPDH block are separable.</p>
<p>The causal order is linear until elastase:</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart TD
  g6p[G6P] --&gt; upper[Upper glycolysis]
  g6p --&gt; ppp[PPP cycling]
  upper --&gt; dhap[DHAP and FBP]
  dhap --&gt; gapdh[GAPDH blocked]
  dhap --&gt; ppp
  gapdh --&gt; lower[Lower glycolysis falls]
  gapdh --&gt; protons["H+ production falls"]
  protons --&gt; ph[Intracellular pH rises]
  ph --&gt; ne[Neutrophil elastase]
  ne --&gt; dna[DNA extrusion]
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p>pH sits downstream of the metabolic block. Rescuing pH does not restore DHAP or BPG. A metabolome of an HA-treated cell therefore still looks “blocked” even when the cell does not throw a net. NET yield is a separate assay, not a hidden column of the concentration table.</p>
<p>The pathway drawing is the same order as the graph: glycolysis along the chain, pH and elastase on the finish, PPP on the shunt and not on the pH arrow.</p>
<div id="cell-fig-pathway" class="cell" data-execution_count="2">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8.2</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.6</span>))</span>
<span id="cb1-2">w, h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.15</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.62</span></span>
<span id="cb1-3">g6p <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> node_box(ax, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.15</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.55</span>, w, h, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"G6P"</span>, TEAL)</span>
<span id="cb1-4">fbp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> node_box(ax, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.7</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.55</span>, w, h, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"FBP"</span>, TEAL)</span>
<span id="cb1-5">dhap <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> node_box(ax, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.25</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.55</span>, w, h, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"DHAP"</span>, TEAL)</span>
<span id="cb1-6">gapdh <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> node_box(ax, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.8</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.55</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.35</span>, h, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"GAPDH</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">blocked"</span>, CORAL)</span>
<span id="cb1-7">bpg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> node_box(ax, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6.55</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.55</span>, w, h, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"BPG"</span>, MUTED)</span>
<span id="cb1-8">pyr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> node_box(ax, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8.1</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.55</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.2</span>, h, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pyruvate"</span>, MUTED)</span>
<span id="cb1-9">ppp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> node_box(ax, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.15</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.7</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.7</span>, h, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PPP cycling"</span>, GOLD, INK)</span>
<span id="cb1-10">protons <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> node_box(ax, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.55</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.4</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.85</span>, h, <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"H$^{+}$ production falls"</span>, PURPLE)</span>
<span id="cb1-11">ph <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> node_box(ax, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.4</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.7</span>, h, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pH rises"</span>, PURPLE)</span>
<span id="cb1-12">ne <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> node_box(ax, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.55</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.7</span>, h, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"elastase"</span>, CORAL)</span>
<span id="cb1-13">dna <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> node_box(ax, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6.7</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.85</span>, h, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"DNA extrusion"</span>, CORAL)</span>
<span id="cb1-14"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> src, dst <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (</span>
<span id="cb1-15">    (g6p, fbp),</span>
<span id="cb1-16">    (fbp, dhap),</span>
<span id="cb1-17">    (dhap, gapdh),</span>
<span id="cb1-18">    (gapdh, bpg),</span>
<span id="cb1-19">    (bpg, pyr),</span>
<span id="cb1-20">    (g6p, ppp),</span>
<span id="cb1-21">    (dhap, ppp),</span>
<span id="cb1-22">    (gapdh, protons),</span>
<span id="cb1-23">    (protons, ph),</span>
<span id="cb1-24">    (ph, ne),</span>
<span id="cb1-25">    (ne, dna),</span>
<span id="cb1-26">):</span>
<span id="cb1-27">    arrow(ax, src, dst)</span>
<span id="cb1-28">ax.set_xlim(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">9.5</span>)</span>
<span id="cb1-29">ax.set_ylim(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.55</span>)</span>
<span id="cb1-30">ax.set_aspect(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"equal"</span>)</span>
<span id="cb1-31">ax.axis(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"off"</span>)</span>
<span id="cb1-32">fig.tight_layout()</span>
<span id="cb1-33">letterbox_cover(fig)</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-pathway" class="quarto-float quarto-figure quarto-figure-center anchored" alt="Boxes for G6P, FBP, DHAP, blocked GAPDH, BPG, pyruvate, PPP, falling H+, rising pH, elastase, and DNA extrusion, connected by arrows.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-pathway-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/gapdh-net-suppressor/index_files/figure-html/fig-pathway-output-1.png" class="img-fluid figure-img" alt="Boxes for G6P, FBP, DHAP, blocked GAPDH, BPG, pyruvate, PPP, falling H+, rising pH, elastase, and DNA extrusion, connected by arrows.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-pathway-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: GAPDH block in glycolysis. Upper-glycolysis intermediates pile up, proton production falls, intracellular pH rises, and neutrophil elastase finishes DNA extrusion. PPP cycling rises and is not the pH driver.
</figcaption>
</figure>
</div>
</div>
</div>
<p>The open question is no longer the chemistry. It is which table in the paper can carry it.</p>
</section>
<section id="the-patient-table-and-the-inhibitor-work-are-different-arrays" class="level2">
<h2 class="anchored" data-anchor-id="the-patient-table-and-the-inhibitor-work-are-different-arrays">The patient table and the inhibitor work are different arrays</h2>
<p>The instinct, once the mechanism is in view, is to reach for a four-way tensor: people, metabolites, treatments, time. The public file is not that object.</p>
<p>Li et al.&nbsp;did two experiments. In the first, neutrophils from 19 healthy controls, 30 people with mild COVID-19, and 26 with severe disease were extracted once. Each person contributes one row. Group is a tag on that row, the way a train/test tag is a tag on a row. The array is <img src="https://latex.codecogs.com/png.latex?X%20%5Cin%20%5Cmathbb%7BR%7D%5E%7B75%20%5Ctimes%20285%7D">. Peak area is not flux and not a NET. <a href="../neutrophil-metabolome-axis/">Neutrophil Metabolomes as a Matrix</a> fits the line through the three means; Mild sits off it.</p>
<p>In the second, neutrophils from healthy donors were treated in the dish. Conditions include vehicle, heptelidic acid, iodoacetate, HA plus HEPES, HA plus cariporide, and HA plus an elastase inhibitor. PMA and the calcium ionophore A23187 are comparison NET inducers, not GAPDH inhibitors. Live-cell movies track nuclear shape and membrane integrity over minutes. Targeted metabolomics and <img src="https://latex.codecogs.com/png.latex?%5E%7B13%7D">C tracing sit on a subset of those conditions.</p>
<p>A tensor mode is just an index we can vary independently. Donor 3, condition HA, metabolite DHAP can, in principle, all be observed together. The patient file does not have that. Each person sits in one group. We cannot put the same person in Control and Severe. If we pad <code>sample × metabolite × group</code> anyway, two of three slices are empty for every row, and a factorization spends its effort explaining holes we invented.</p>
<p>That is the difference between a nested design and a crossed one. Nested: the group is a partition of people. Crossed: every donor is measured in every condition.</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
  subgraph nested [Patient snapshot]
    people[75 people] --&gt; matrixX["X: 75 x 285"]
    people --&gt; partition[one group each]
  end
  subgraph crossed [Inhibitor design]
    donor[donor] --&gt; cube["donor x condition x metabolite"]
    condition[condition] --&gt; cube
    metabolite[metabolite] --&gt; cube
  end
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p>Two easy mistakes invent modes that were not measured. Koningic acid is heptelidic acid: listing both as separate conditions duplicates a level. Time-lapse microscopy is nuclear shape and membrane leak. It is not a metabolome sampled at <img src="https://latex.codecogs.com/png.latex?0,%2015,%2030,%2060,%20120"> min. The public table is not <img src="https://latex.codecogs.com/png.latex?%5Cboldsymbol%7B%5Cmathcal%7BX%7D%7D%20%5Cin%20%5Cmathbb%7BR%7D%5E%7BN%20%5Ctimes%20M%20%5Ctimes%20C%20%5Ctimes%20T%7D">.</p>
<p>The donor counts in the inhibitor work are also much smaller than the patient cohort: eight donors for Sytox traces, five for glucose tracing. Those are the <img src="https://latex.codecogs.com/png.latex?I"> of a crossed cube, not <img src="https://latex.codecogs.com/png.latex?N%20%5Capprox%2080">.</p>
<p>What a crossed cube <em>would</em> buy is the next question.</p>
</section>
<section id="cp-would-recover-a-gapdh-block-pattern-if-the-cube-existed" class="level2">
<h2 class="anchored" data-anchor-id="cp-would-recover-a-gapdh-block-pattern-if-the-cube-existed">CP would recover a GAPDH-block pattern if the cube existed</h2>
<p>Flatten a fluorescence cube and we throw away a mode that was measured. <a href="../uses-of-tensor-factorizations/">Uses of Tensor Factorizations</a> is that case. Pad a nested table into a cube and we invent a mode that was not. The HA metabolomics sit in between: they <em>could</em> have been deposited as donor <img src="https://latex.codecogs.com/png.latex?%5Ctimes"> treatment <img src="https://latex.codecogs.com/png.latex?%5Ctimes"> metabolite. They were not. If they had been, CANDECOMP/PARAFAC — CP — would be the right compression. Kolda and Bader (2009) is the algebra.</p>
<p>CP writes a three-way array as a sum of rank-1 patterns. Each pattern is one outer product: a donor signature, a metabolite signature, and a condition signature that travel together. A three-way array <img src="https://latex.codecogs.com/png.latex?%5Cboldsymbol%7B%5Cmathcal%7BX%7D%7D%20%5Cin%20%5Cmathbb%7BR%7D%5E%7BI%20%5Ctimes%20J%20%5Ctimes%20K%7D"> with <img src="https://latex.codecogs.com/png.latex?I"> donors, <img src="https://latex.codecogs.com/png.latex?J"> metabolites, <img src="https://latex.codecogs.com/png.latex?K"> conditions factors as</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cboldsymbol%7B%5Cmathcal%7BX%7D%7D%20%5Capprox%20%5Csum_%7Br=1%7D%5E%7BR%7D%20%5Cmathbf%7Ba%7D_r%20%5Ccirc%20%5Cmathbf%7Bb%7D_r%20%5Ccirc%20%5Cmathbf%7Bc%7D_r.%0A"></p>
<p>On the HA design those loadings would have a biological reading:</p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Ba%7D_r">: how strongly each healthy donor shows the pattern. Heterogeneity among people, not a COVID severity coordinate.</li>
<li><img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bb%7D_r">: which metabolites move together. A GAPDH-block factor would load on DHAP and FBP against BPG and pyruvate, with PPP intermediates in the same direction as the pile-up.</li>
<li><img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bc%7D_r">: which treatments fire the pattern. Vehicle low; HA and iodoacetate high and aligned; HA+HEPES still high on that metabolic factor, because pH rescue does not reverse the block.</li>
</ul>
<p>A fourth mode exists only if the same metabolome is sampled in time:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cboldsymbol%7B%5Cmathcal%7BX%7D%7D%20%5Capprox%20%5Csum_%7Br=1%7D%5E%7BR%7D%20%5Cmathbf%7Ba%7D_r%20%5Ccirc%20%5Cmathbf%7Bb%7D_r%20%5Ccirc%20%5Cmathbf%7Bc%7D_r%20%5Ccirc%20%5Cmathbf%7Bd%7D_r.%0A"></p>
<p>Li et al.&nbsp;did not deposit that four-way array. NET yield is a separate assay. Putting NETosis percent into the metabolome cube as a silent time mode mixes a phenotype with a concentration table.</p>
<p>The drawing is synthetic geometry. It is not a fit to ST002477 and not a reconstruction of unpublished HA peak areas. Slab counts follow the paper’s design: eight donors, twelve metabolite slots as a stand-in for a targeted panel, four conditions (vehicle, HA, HA+HEPES, HA+elastase inhibitor). Time is a caption, not an axis.</p>
<div id="cell-fig-cube" class="cell" data-execution_count="3">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1">conditions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (</span>
<span id="cb2-2">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"vehicle"</span>, TEAL),</span>
<span id="cb2-3">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"HA"</span>, CORAL),</span>
<span id="cb2-4">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"HA+HEPES"</span>, GOLD),</span>
<span id="cb2-5">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"HA+NE-i"</span>, PURPLE),</span>
<span id="cb2-6">)</span>
<span id="cb2-7">n_metabolites <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span></span>
<span id="cb2-8">n_donors <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span></span>
<span id="cb2-9">dz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span></span>
<span id="cb2-10">gap <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.28</span></span>
<span id="cb2-11">fig <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.figure(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8.2</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.2</span>))</span>
<span id="cb2-12">ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fig.add_subplot(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">111</span>, projection<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"3d"</span>)</span>
<span id="cb2-13">ax.set_proj_type(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ortho"</span>)</span>
<span id="cb2-14"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, (_name, color) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(conditions):</span>
<span id="cb2-15">    z0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (dz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> gap)</span>
<span id="cb2-16">    faces <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cuboid_faces((<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, z0), (<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(n_metabolites), <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(n_donors), dz))</span>
<span id="cb2-17">    ax.add_collection3d(</span>
<span id="cb2-18">        Poly3DCollection(</span>
<span id="cb2-19">            faces,</span>
<span id="cb2-20">            facecolors<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>color,</span>
<span id="cb2-21">            edgecolors<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>INK,</span>
<span id="cb2-22">            linewidths<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.55</span>,</span>
<span id="cb2-23">            alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.42</span>,</span>
<span id="cb2-24">        )</span>
<span id="cb2-25">    )</span>
<span id="cb2-26">handles <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb2-27">    Patch(facecolor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>color, edgecolor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>INK, alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.55</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>name)</span>
<span id="cb2-28">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name, color <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> conditions</span>
<span id="cb2-29">]</span>
<span id="cb2-30">ax.set_xlim(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, n_metabolites)</span>
<span id="cb2-31">ax.set_ylim(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, n_donors)</span>
<span id="cb2-32">ax.set_zlim(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(conditions) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (dz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> gap))</span>
<span id="cb2-33">ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"metabolite $J$"</span>)</span>
<span id="cb2-34">ax.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"donor $I$"</span>)</span>
<span id="cb2-35">ax.set_zlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"condition $K$"</span>)</span>
<span id="cb2-36">ax.view_init(elev<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">18</span>, azim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">58</span>)</span>
<span id="cb2-37">ax.xaxis.pane.fill <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span></span>
<span id="cb2-38">ax.yaxis.pane.fill <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span></span>
<span id="cb2-39">ax.zaxis.pane.fill <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span></span>
<span id="cb2-40">ax.legend(handles<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>handles, loc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"upper left"</span>, frameon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb2-41">ax.text2D(</span>
<span id="cb2-42">    <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>,</span>
<span id="cb2-43">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.06</span>,</span>
<span id="cb2-44">    <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"Time $T$: live-cell morphology, not a metabolome index"</span>,</span>
<span id="cb2-45">    transform<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ax.transAxes,</span>
<span id="cb2-46">    color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>MUTED,</span>
<span id="cb2-47">    fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>,</span>
<span id="cb2-48">)</span>
<span id="cb2-49">fig.tight_layout()</span>
<span id="cb2-50"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"schematic shape (donor, metabolite, condition) = (</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>n_donors<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>n_metabolites<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(conditions)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">)"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>schematic shape (donor, metabolite, condition) = (8, 12, 4)</code></pre>
</div>
<div class="cell-output cell-output-display">
<div id="fig-cube" class="quarto-float quarto-figure quarto-figure-center anchored" alt="Isometric drawing of four coloured slabs stacked along a condition axis, with metabolite and donor axes labelled.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-cube-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/gapdh-net-suppressor/index_files/figure-html/fig-cube-output-2.png" class="img-fluid figure-img" alt="Isometric drawing of four coloured slabs stacked along a condition axis, with metabolite and donor axes labelled.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-cube-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: Schematic of a crossed inhibitor cube: donor × metabolite × condition. Colours mark vehicle, HA, HA plus HEPES, and HA plus an elastase inhibitor. Time is live-cell morphology, not a fourth metabolome index. Geometry only; no peak areas were loaded.
</figcaption>
</figure>
</div>
</div>
</div>
<p>The print is <img src="https://latex.codecogs.com/png.latex?(8,%2012,%204)">: eight donors, twelve metabolite slots, four conditions. CP on a padded patient cube would spend rank on holes the grouping created. SVD of <img src="https://latex.codecogs.com/png.latex?X"> is already that factorization. The inhibitor cube is the design where a treatment loading can isolate “GAPDH block” without inventing a group mode.</p>
<p>The chemistry, once named, also names the lever.</p>
</section>
<section id="the-useful-lever-is-upstream-of-dna-extrusion" class="level2">
<h2 class="anchored" data-anchor-id="the-useful-lever-is-upstream-of-dna-extrusion">The useful lever is upstream of DNA extrusion</h2>
<p>DNase after lysis digests nets that have already left the cell. That is cleanup. The mechanism Li et al.&nbsp;isolate sits earlier.</p>
<p>Keep GAPDH flowing and the suppressor is still on. Buffer intracellular pH, or block <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BNa%7D%5E+/%5Cmathrm%7BH%7D%5E+"> exchange, once the block is in place, and nets stop without DHAP or BPG returning to baseline. Block elastase at extrusion and the membrane can still become leaky while DNA stays inside.</p>
<p>Those three are not interchangeable with a linear severity score on ST002477. The public table can flag metabolites that leave the Control–Severe line. It cannot say which node, in which cell, at which pH, finished a net.</p>
</section>
<section id="caveat-a-snapshot-is-not-a-knockout-and-ha-is-not-the-patient" class="level2">
<h2 class="anchored" data-anchor-id="caveat-a-snapshot-is-not-a-knockout-and-ha-is-not-the-patient">Caveat: a snapshot is not a knockout, and HA is not the patient</h2>
<p>This is a reading of Li et al.&nbsp;(2023) plus the layout of the public matrix. It is not a biomarker panel and not a fitted CP model.</p>
<p>Static peak area is not flux and not NET formation. An elevated DHAP:BPG ratio in severe neutrophils is consistent with reduced GAPDH activity; it is not a demonstrated ROS-oxidation event in those patients. HA on healthy cells is a causal intervention, not the patient milieu. Bulk pellets mix cell-state composition with within-cell change. pH rescue and elastase blockade were shown <em>ex vivo</em>; they are not a clinical protocol. One public cohort is not a replication.</p>
<p>The opening claim still holds inside those limits. A classifier on the spreadsheet will separate groups. The enzyme that lets the cell throw a net is not on that spreadsheet. It is in the dish, at GAPDH, and the last step is pH, then elastase.</p>
<p>Flux. Suppresses. Nets. pH. Finishes. Elastase. Nested. Groups. Do. Not. Factorize.</p>
</section>
<section id="references" class="level2">
<h2 class="anchored" data-anchor-id="references">References</h2>
<ul>
<li>Brinkmann, V., et al.&nbsp;(2004). Neutrophil extracellular traps kill bacteria. <em>Science</em> 303: 1532–1535. <a href="https://doi.org/10.1126/science.1092385">doi:10.1126/science.1092385</a></li>
<li>Kolda, T. G., and Bader, B. W. (2009). Tensor decompositions and applications. <em>SIAM Review</em> 51: 455–500. <a href="https://doi.org/10.1137/07070111X">doi:10.1137/07070111X</a></li>
<li>Li, Y., et al.&nbsp;(2023). Neutrophil metabolomics in severe COVID-19 reveal GAPDH as a suppressor of neutrophil extracellular trap formation. <em>Nature Communications</em> 14: 2610. <a href="https://doi.org/10.1038/s41467-023-37567-w">doi:10.1038/s41467-023-37567-w</a></li>
<li>Metabolomics Workbench ST002477 / PR001600. <a href="https://doi.org/10.21228/M8W70C">doi:10.21228/M8W70C</a></li>
<li><a href="../neutrophil-metabolome-axis/">Neutrophil Metabolomes as a Matrix</a></li>
<li><a href="../uses-of-tensor-factorizations/">Uses of Tensor Factorizations</a></li>
</ul>


</section>

 ]]></description>
  <category>Bioinformatics</category>
  <category>Machine Learning</category>
  <guid>https://project-delphi.github.io/ml-blog/posts/gapdh-net-suppressor/</guid>
  <pubDate>Thu, 10 Sep 2026 05:00:00 GMT</pubDate>
  <media:content url="https://project-delphi.github.io/ml-blog/posts/gapdh-net-suppressor/cover.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Neutrophil Metabolomes as a Matrix</title>
  <dc:creator>Ravi Kalia</dc:creator>
  <link>https://project-delphi.github.io/ml-blog/posts/neutrophil-metabolome-axis/</link>
  <description><![CDATA[ 





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/neutrophil-metabolome-axis/cover.png" class="img-fluid figure-img" alt="Three PCA panels of 75 points, coloured by k-means, Ward, and GMM cluster, not by COVID label."></p>
<figcaption>Neutrophil Metabolomes as a Matrix</figcaption>
</figure>
</div>
<p>A classifier will separate these three COVID neutrophil groups. That is the easy trick. Clustering, which does not get the labels, does not recover the same partition.</p>
<p>Seventy-five people, 285 metabolites, three tags on rows. The usual next move is to average each tag and draw a line through those three points, then treat the leftover as a second disease direction. That geometry is always defined. It is not a model of the people. One Control profile already sits far from the Control mean. The clouds overlap. A line through the means never sees that.</p>
<p>Li et al.&nbsp;(2023) isolated neutrophils from 75 people, extracted two million cells each, and deposited LC-MS peak areas as Metabolomics Workbench ST002477 (CC BY 4.0). They wanted the cell that kills, not plasma. The numbers are relative ion intensities, not counts and not fluxes. The job that fits the file is whether the 75 rows form three blobs that match the labels.</p>
<section id="neutrophil-metabolomes" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="neutrophil-metabolomes"><span class="header-section-number">1</span> Neutrophil metabolomes</h2>
<p>Plasma metabolomes mix liver, muscle, and lunch. This table is one cell type: the neutrophil, the cell that does the killing.</p>
<p>Neutrophils eat microbes, dump oxidants, and throw DNA nets (NETs). That work runs on glycolysis and the pentose-phosphate pathway (Morrison, Watts, Sadiku, and Walmsley 2022). In severe COVID-19 these cells pile up in failing lungs. If their fuel is off, killing and damage move together. Li et al.&nbsp;saw amino-acid, redox, and central-carbon pools shift, and showed GAPDH can suppress NET formation. Peak area is not that flux. It is still the public readout of the cell.</p>
<p>Erythrose 4-phosphate sits on the pathway that powers the oxidant burst (Britt et al.&nbsp;2022). Hypotaurine and glycine sit near redox handling. Those names only help after the 75 people have been treated as 75 people.</p>
</section>
<section id="source-table" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="source-table"><span class="header-section-number">2</span> Source table</h2>
<p>75 profiles: 19 Control, 30 Mild, 26 Severe. 287 named rows, 285 unique vectors. Chromatogram peak areas from a targeted Q Exactive method, median-normalised per sample. Nonnegative and continuous. Not integer counts.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 11%">
<col style="width: 14%">
<col style="width: 14%">
<col style="width: 14%">
<col style="width: 14%">
<col style="width: 14%">
<col style="width: 14%">
</colgroup>
<thead>
<tr class="header">
<th>metabolite</th>
<th style="text-align: right;">PMN_74 (C)</th>
<th style="text-align: right;">PMN_75 (C)</th>
<th style="text-align: right;">PMN_02 (M)</th>
<th style="text-align: right;">PMN_03 (M)</th>
<th style="text-align: right;">PMN_48 (S)</th>
<th style="text-align: right;">PMN_49 (S)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Hypotaurine</td>
<td style="text-align: right;">6.97e+08</td>
<td style="text-align: right;">3.66e+08</td>
<td style="text-align: right;">5.58e+08</td>
<td style="text-align: right;">1.39e+08</td>
<td style="text-align: right;">9.88e+08</td>
<td style="text-align: right;">6.45e+08</td>
</tr>
<tr class="even">
<td>Glycine</td>
<td style="text-align: right;">1.07e+08</td>
<td style="text-align: right;">8.54e+07</td>
<td style="text-align: right;">7.55e+07</td>
<td style="text-align: right;">8.73e+07</td>
<td style="text-align: right;">9.30e+07</td>
<td style="text-align: right;">1.09e+08</td>
</tr>
<tr class="odd">
<td>Erythrose 4-phosphate</td>
<td style="text-align: right;">1.06e+06</td>
<td style="text-align: right;">5.94e+06</td>
<td style="text-align: right;">2.67e+06</td>
<td style="text-align: right;">3.12e+06</td>
<td style="text-align: right;">3.40e+04</td>
<td style="text-align: right;">1.85e+05</td>
</tr>
<tr class="even">
<td>1,3/ 2,3-Bisphosphoglycerate</td>
<td style="text-align: right;">6.71e+04</td>
<td style="text-align: right;">1.11e+05</td>
<td style="text-align: right;">1.73e+06</td>
<td style="text-align: right;">3.79e+07</td>
<td style="text-align: right;">2.28e+05</td>
<td style="text-align: right;">5.03e+06</td>
</tr>
<tr class="odd">
<td>Creatine</td>
<td style="text-align: right;">9.06e+09</td>
<td style="text-align: right;">6.27e+09</td>
<td style="text-align: right;">9.80e+09</td>
<td style="text-align: right;">7.02e+09</td>
<td style="text-align: right;">8.04e+09</td>
<td style="text-align: right;">1.49e+10</td>
</tr>
<tr class="even">
<td>Uridine monophosphate</td>
<td style="text-align: right;">4.36e+07</td>
<td style="text-align: right;">1.39e+06</td>
<td style="text-align: right;">1.54e+06</td>
<td style="text-align: right;">1.98e+06</td>
<td style="text-align: right;">1.64e+06</td>
<td style="text-align: right;">1.68e+06</td>
</tr>
</tbody>
</table>
<p>Relative peak areas, two samples per group. The range is normal for ion intensity. It is not a count table.</p>
</section>
<section id="matrix-layout" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="matrix-layout"><span class="header-section-number">3</span> Matrix layout</h2>
<p>Each person is one row. Each metabolite is one column. <img src="https://latex.codecogs.com/png.latex?X%20%5Cin%20%5Cmathbb%7BR%7D%5E%7B75%20%5Ctimes%20285%7D">. Control, Mild, and Severe partition those rows. They do not add a third index.</p>
<p>A tensor mode can vary on its own. Here each person sits in one group. <code>sample × metabolite × group</code> leaves two slices empty for every person. CP on that cube would spend rank explaining holes the design made.</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
  subgraph nested [Nested design]
    samples["75 samples"] --&gt; matrixX["X: 75 x 285"]
    samples --&gt; partition["19 Control / 30 Mild / 26 Severe"]
  end
  subgraph crossed [Crossed design]
    donor["donor"] --&gt; cube["donor x condition x metabolite"]
    condition["condition"] --&gt; cube
    metabolite["metabolite"] --&gt; cube
  end
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p>Group would be a third mode if the same people were measured in all three states. This cohort is one snapshot per person. Raw chromatograms could still be <code>sample × retention time × m/z</code>. Those files are not in the public table.</p>
</section>
<section id="clustering" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="clustering"><span class="header-section-number">4</span> Clustering</h2>
<p>The labels are a hypothesis about density: three blobs, 75 points. Clustering tests that without being handed the tags.</p>
<p><img src="https://latex.codecogs.com/png.latex?k=3"> is borrowed from the label count. It is not chosen by silhouette, BIC, or a gap statistic. Those criteria need a held-out split or a stable bootstrap. 75 rows, 285 columns, and groups of 19 / 30 / 26 do not give one. <a href="../clustering-tuning-metrics/">Hyperparameter Tuning in Clustering</a> is the case where a validation split exists. This file is not that case.</p>
<p>Three algorithms, scikit-learn defaults, same log1p/IQR matrix <img src="https://latex.codecogs.com/png.latex?Z">:</p>
<ul>
<li><strong>k-means.</strong> Three spherical averages. This <em>is</em> averaging, without using the COVID tags.</li>
<li><strong>Ward agglomerative.</strong> Merge by increase in within-cluster sum of squares.</li>
<li><strong>Diagonal Gaussian mixture.</strong> One variance per metabolite. A full covariance per component cannot be fit: each would want <img src="https://latex.codecogs.com/png.latex?%5Csim%2040%7B,%7D000"> parameters on 75 rows.</li>
</ul>
<p>Agreement with the COVID labels is the adjusted Rand index (ARI). Chance is 0. A match is 1.</p>
<div id="a9ebb036" class="cell" data-execution_count="3">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"k-means ARI = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>aris[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'k-means'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb1-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Ward ARI = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>aris[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Ward'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb1-3"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"GMM (diag) ARI = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>aris[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'GMM (diag)'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb1-4"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"COVID-label silhouette = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>label_sil<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb1-5"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name, pred <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> clusterings.items():</span>
<span id="cb1-6">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> sizes = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(np.bincount(pred))<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>k-means ARI = 0.14
Ward ARI = 0.18
GMM (diag) ARI = 0.23
COVID-label silhouette = 0.05
k-means sizes = [np.int64(11), np.int64(36), np.int64(28)]
Ward sizes = [np.int64(32), np.int64(4), np.int64(39)]
GMM (diag) sizes = [np.int64(38), np.int64(25), np.int64(12)]</code></pre>
</div>
</div>
<p><strong>k-means</strong></p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>label</th>
<th style="text-align: right;">0</th>
<th style="text-align: right;">1</th>
<th style="text-align: right;">2</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Control</td>
<td style="text-align: right;">9</td>
<td style="text-align: right;">3</td>
<td style="text-align: right;">7</td>
</tr>
<tr class="even">
<td>Mild</td>
<td style="text-align: right;">2</td>
<td style="text-align: right;">13</td>
<td style="text-align: right;">15</td>
</tr>
<tr class="odd">
<td>Severe</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">20</td>
<td style="text-align: right;">6</td>
</tr>
</tbody>
</table>
<p><strong>Ward</strong></p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>label</th>
<th style="text-align: right;">0</th>
<th style="text-align: right;">1</th>
<th style="text-align: right;">2</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Control</td>
<td style="text-align: right;">13</td>
<td style="text-align: right;">3</td>
<td style="text-align: right;">3</td>
</tr>
<tr class="even">
<td>Mild</td>
<td style="text-align: right;">16</td>
<td style="text-align: right;">1</td>
<td style="text-align: right;">13</td>
</tr>
<tr class="odd">
<td>Severe</td>
<td style="text-align: right;">3</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">23</td>
</tr>
</tbody>
</table>
<p><strong>GMM (diag)</strong></p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>label</th>
<th style="text-align: right;">0</th>
<th style="text-align: right;">1</th>
<th style="text-align: right;">2</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Control</td>
<td style="text-align: right;">3</td>
<td style="text-align: right;">5</td>
<td style="text-align: right;">11</td>
</tr>
<tr class="even">
<td>Mild</td>
<td style="text-align: right;">13</td>
<td style="text-align: right;">16</td>
<td style="text-align: right;">1</td>
</tr>
<tr class="odd">
<td>Severe</td>
<td style="text-align: right;">22</td>
<td style="text-align: right;">4</td>
<td style="text-align: right;">0</td>
</tr>
</tbody>
</table>
<p>ARI is 0.14 (k-means), 0.18 (Ward), 0.23 (diagonal GMM). Mild splits across clusters in all three. Ward’s middle cluster has four people. The COVID labels themselves have silhouette 0.05 on <img src="https://latex.codecogs.com/png.latex?Z">: they are not compact blobs. k-means and Ward, which do not use the labels, still only reach silhouette 0.09.</p>
<div id="cell-fig-clusters" class="cell" data-execution_count="5">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">fig, axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.2</span>), sharex<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>, sharey<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb3-2"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> ax, (name, pred) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(axes, clusterings.items()):</span>
<span id="cb3-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> cluster_id <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>):</span>
<span id="cb3-4">        pts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pc_scores[pred <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> cluster_id]</span>
<span id="cb3-5">        ax.scatter(</span>
<span id="cb3-6">            pts[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>],</span>
<span id="cb3-7">            pts[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>],</span>
<span id="cb3-8">            s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">22</span>,</span>
<span id="cb3-9">            color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>CLUSTER_COLOURS[cluster_id],</span>
<span id="cb3-10">            alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.85</span>,</span>
<span id="cb3-11">            label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>cluster_id<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>,</span>
<span id="cb3-12">        )</span>
<span id="cb3-13">    ax.set_title(name)</span>
<span id="cb3-14">    ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PC1"</span>)</span>
<span id="cb3-15">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PC2"</span>)</span>
<span id="cb3-16">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].legend(frameon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cluster"</span>, loc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lower left"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb3-17">fig.tight_layout()</span>
<span id="cb3-18">letterbox_cover(fig)</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-clusters" class="quarto-float quarto-figure quarto-figure-center anchored" alt="Three scatter panels of 75 points on PC1 versus PC2. Point colour is cluster membership from k-means, Ward, and GMM.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-clusters-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/neutrophil-metabolome-axis/index_files/figure-html/fig-clusters-output-1.png" class="img-fluid figure-img" alt="Three scatter panels of 75 points on PC1 versus PC2. Point colour is cluster membership from k-means, Ward, and GMM.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-clusters-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: The same 75 people on the first two principal components, coloured by cluster, not by COVID label. k-means, Ward, and a diagonal GMM, each with k=3 on the log1p/IQR matrix.
</figcaption>
</figure>
</div>
</div>
</div>
<p>A spectral clustering with nearest-neighbour affinity can be pushed toward ARI <img src="https://latex.codecogs.com/png.latex?%5Capprox%200.3"> by changing the neighbour count. That move is tuning. 75 points will not carry it.</p>
</section>
<section id="pca" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="pca"><span class="header-section-number">5</span> PCA</h2>
<p>Do the 75 people separate, or only a labelled mean?</p>
<div id="a772736d" class="cell" data-execution_count="6">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"PC1 variance fraction = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>pc_var[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb4-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"PC2 variance fraction = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>pc_var[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb4-3"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> group <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> GROUPS:</span>
<span id="cb4-4">    centre <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pc_scores[labels <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> group].mean(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb4-5">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>group<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> mean PC1 = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>centre[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, mean PC2 = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>centre[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>PC1 variance fraction = 0.21
PC2 variance fraction = 0.11
Control mean PC1 = -6.95, mean PC2 = 0.13
Mild mean PC1 = 1.75, mean PC2 = -2.23
Severe mean PC1 = 3.06, mean PC2 = 2.48</code></pre>
</div>
</div>
<div id="cell-fig-pca" class="cell" data-execution_count="7">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots()</span>
<span id="cb6-2"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> group <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> GROUPS:</span>
<span id="cb6-3">    pts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pc_scores[labels <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> group]</span>
<span id="cb6-4">    ax.scatter(pts[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], pts[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">28</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>COLOURS[group], alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.85</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>group)</span>
<span id="cb6-5">ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PC1"</span>)</span>
<span id="cb6-6">ax.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PC2"</span>)</span>
<span id="cb6-7">ax.legend(frameon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb6-8">fig.tight_layout()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-pca" class="quarto-float quarto-figure quarto-figure-center anchored" alt="Scatter of 75 points coloured by Control, Mild, and Severe on PC1 versus PC2.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-pca-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/neutrophil-metabolome-axis/index_files/figure-html/fig-pca-output-1.png" class="img-fluid figure-img" alt="Scatter of 75 points coloured by Control, Mild, and Severe on PC1 versus PC2.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-pca-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: First two principal components of the 75 log1p/IQR profiles, coloured by COVID label. PC1 holds 21% of sample variance; PC2 holds 11%.
</figcaption>
</figure>
</div>
</div>
</div>
<p>PC1 holds 21% of sample variance; PC2 holds 11%. Group means on PC1: Control −6.95, Mild 1.75, Severe 3.06. On PC2, Mild (−2.23) sits opposite Severe (2.48). People overlap. One Control sits near PC1 <img src="https://latex.codecogs.com/png.latex?=%20-25">. That tail moves the Control mean. The cluster panels are this same plane, recoloured.</p>
</section>
<section id="volcano" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="volcano"><span class="header-section-number">6</span> Volcano</h2>
<p>Clustering asks whether the <em>rows</em> form blobs. A <a href="../volcano-plots/">volcano plot</a> asks which <em>columns</em> differ. Both can be true at once: many metabolites shift, and the 75 people still overlap.</p>
<p>Welch’s t-test on <img src="https://latex.codecogs.com/png.latex?%5Clog_2(1%20+%20%5Ctext%7Bpeak%20area%7D)">, one contrast at a time. Benjamini–Hochberg <img src="https://latex.codecogs.com/png.latex?q"> within each contrast’s 285 tests. <img src="https://latex.codecogs.com/png.latex?k"> is not involved. The x-axis is the difference of group means on that log2 scale, not a fold-change of the raw averages.</p>
<div id="9f7a48cb" class="cell" data-execution_count="8">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name, (lfc, p_values, q_values) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> volcanoes.items():</span>
<span id="cb7-2">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> q&lt;0.05 = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>((q_values <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</span>).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>())<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> / </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>q_values<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>size<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb7-3">e4p <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(np.flatnonzero(feature_names <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Erythrose 4-phosphate"</span>)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span>
<span id="cb7-4"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name, (lfc, p_values, q_values) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> volcanoes.items():</span>
<span id="cb7-5">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> E4P lfc = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>lfc[e4p]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, q = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>q_values[e4p]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2g}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>Mild vs Control q&lt;0.05 = 108 / 285
Severe vs Control q&lt;0.05 = 134 / 285
Mild vs Control E4P lfc = -0.09, q = 0.92
Severe vs Control E4P lfc = -4.45, q = 1.6e-07</code></pre>
</div>
</div>
<div id="cell-fig-volcano" class="cell" data-execution_count="9">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1">UP <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#C45C26"</span></span>
<span id="cb9-2">DOWN <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#2A9D8F"</span></span>
<span id="cb9-3">fig, axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.6</span>), sharey<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb9-4">annotate <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb9-5">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Mild vs Control"</span>: [(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Fucose 1-phosphate"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)],</span>
<span id="cb9-6">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Severe vs Control"</span>: [(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Erythrose 4-phosphate"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>)],</span>
<span id="cb9-7">}</span>
<span id="cb9-8"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> ax, (name, (lfc, p_values, q_values)) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(axes, volcanoes.items()):</span>
<span id="cb9-9">    y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>np.log10(np.clip(p_values, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-300</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>))</span>
<span id="cb9-10">    hit <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q_values <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</span></span>
<span id="cb9-11">    ax.scatter(lfc[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>hit], y[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>hit], s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">14</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>RULE, alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.85</span>, linewidths<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb9-12">    ax.scatter(lfc[hit <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> (lfc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)], y[hit <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> (lfc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)], s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>UP, linewidths<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb9-13">    ax.scatter(lfc[hit <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> (lfc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)], y[hit <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> (lfc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)], s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>DOWN, linewidths<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb9-14">    ax.axhline(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>np.log10(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</span>), color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>MUTED, ls<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">":"</span>, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>)</span>
<span id="cb9-15">    ax.set_title(name)</span>
<span id="cb9-16">    ax.set_xlabel(<span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"mean $\log_2(1+x)$ difference"</span>)</span>
<span id="cb9-17">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> metabolite, dx, dy <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> annotate[name]:</span>
<span id="cb9-18">        idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(np.flatnonzero(feature_names <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> metabolite)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span>
<span id="cb9-19">        ax.annotate(</span>
<span id="cb9-20">            metabolite,</span>
<span id="cb9-21">            (lfc[idx], y[idx]),</span>
<span id="cb9-22">            textcoords<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"offset points"</span>,</span>
<span id="cb9-23">            xytext<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(dx, dy),</span>
<span id="cb9-24">            fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>,</span>
<span id="cb9-25">            color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>INK,</span>
<span id="cb9-26">        )</span>
<span id="cb9-27">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].set_ylabel(<span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"$-\log_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{10}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;"> p$"</span>)</span>
<span id="cb9-28">fig.tight_layout()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-volcano" class="quarto-float quarto-figure quarto-figure-center anchored" alt="Two volcano plots. Horizontal axis is log2 mean difference; vertical axis is minus log10 p. Coloured points pass q < 0.05.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-volcano-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/neutrophil-metabolome-axis/index_files/figure-html/fig-volcano-output-1.png" class="img-fluid figure-img" alt="Two volcano plots. Horizontal axis is log2 mean difference; vertical axis is minus log10 p. Coloured points pass q < 0.05.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-volcano-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: Welch t-tests on log2(1 + peak area). Points with Benjamini–Hochberg q &lt; 0.05 are coloured; the rest are grey. Left: Mild versus Control. Right: Severe versus Control.
</figcaption>
</figure>
</div>
</div>
</div>
<p>108 metabolites at <img src="https://latex.codecogs.com/png.latex?q%3C0.05"> for Mild vs Control; 134 for Severe vs Control. Erythrose 4-phosphate is a top Severe vs Control hit (<img src="https://latex.codecogs.com/png.latex?%5Clog_2"> difference <img src="https://latex.codecogs.com/png.latex?-4.45">, <img src="https://latex.codecogs.com/png.latex?q=1.6%5Ctimes%2010%5E%7B-7%7D">) and is not called in Mild vs Control (<img src="https://latex.codecogs.com/png.latex?q=0.92">). A line through the three group means would have treated that name as a severity coordinate. It is a Severe-specific drop.</p>
<p>Many columns move. The rows still do not form three blobs. That is why averaging the groups and fitting a line is the wrong summary of this file.</p>
</section>
<section id="group-means" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="group-means"><span class="header-section-number">7</span> Group means</h2>
<p>Three class means in 285 dimensions are three points. They always sit in a plane. A least-squares line through Control and Severe, with Mild parked at some <img src="https://latex.codecogs.com/png.latex?t">, is then a statement about those three points, not about 75 people.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ckappa(t)=%5Cbar%20x_%7B%5Cmathrm%7BMild%7D%7D-%5Cbigl%5B(1-t)%5Cbar%20x_%7B%5Cmathrm%7BControl%7D%7D+t%5Cbar%20x_%7B%5Cmathrm%7BSevere%7D%7D%5Cbigr%5D.%0A"></p>
<p>The midpoint model is <img src="https://latex.codecogs.com/png.latex?t=0.5">. Ordinary least squares on the means puts Mild at <img src="https://latex.codecogs.com/png.latex?t=0.69">, with 28% of the between-group sum of squares off the line. Both numbers are exact for this transform. They do not say the people lie on a severity axis. Control and Severe define the chord. Mild’s residual is what is left once you have already replaced each group by its average. k-means does that replacement without the labels and still does not recover them.</p>
<div id="16533fb8" class="cell" data-execution_count="10">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"t_hat = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>t_hat<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb10-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"off-line fraction of between-group SS = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>off_line<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>t_hat = 0.69
off-line fraction of between-group SS = 0.28</code></pre>
</div>
</div>
<div id="cell-fig-axis" class="cell" data-execution_count="11">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb12-1">chord <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(np.linalg.norm(delta))</span>
<span id="cb12-2">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots()</span>
<span id="cb12-3">ax.plot([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, chord], [<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>LINE, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.8</span>, zorder<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb12-4">ax.plot([t_hat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> chord, t_hat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> chord], [<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(np.linalg.norm(kappa))], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>MUTED, ls<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--"</span>, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.2</span>)</span>
<span id="cb12-5">points <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb12-6">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Control"</span>: (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>),</span>
<span id="cb12-7">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Mild"</span>: (t_hat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> chord, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(np.linalg.norm(kappa))),</span>
<span id="cb12-8">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Severe"</span>: (chord, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>),</span>
<span id="cb12-9">}</span>
<span id="cb12-10"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> group, (x, y) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> points.items():</span>
<span id="cb12-11">    ax.scatter([x], [y], s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">90</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>COLOURS[group], zorder<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>group)</span>
<span id="cb12-12">    ax.annotate(group, (x, y), textcoords<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"offset points"</span>, xytext<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>), color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>INK)</span>
<span id="cb12-13">ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Position on the Control–Severe chord"</span>)</span>
<span id="cb12-14">ax.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Residual"</span>)</span>
<span id="cb12-15">ax.legend(frameon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, loc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"upper left"</span>)</span>
<span id="cb12-16">fig.tight_layout()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-axis" class="quarto-float quarto-figure quarto-figure-center anchored" alt="Scatter of three labelled points. Control at the origin, Severe on the x-axis, Mild above the x-axis with a vertical residual.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-axis-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/neutrophil-metabolome-axis/index_files/figure-html/fig-axis-output-1.png" class="img-fluid figure-img" alt="Scatter of three labelled points. Control at the origin, Severe on the x-axis, Mild above the x-axis with a vertical residual.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-axis-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;4: The three group means in the plane of the Control–Severe chord and Mild’s residual. This is a picture of three averages. It is not a picture of 75 people.
</figcaption>
</figure>
</div>
</div>
</div>
<p>The volcano already named the metabolites that move. The three averages add a <img src="https://latex.codecogs.com/png.latex?t"> and a leftover that the 75 people do not occupy.</p>
<p>Rank one of the three centroids is the line. Rank two is the leftover. Rank three is empty. CP on the 75 × 285 table is SVD. Padding groups into a cube does not change that.</p>
</section>
<section id="downstream-models" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="downstream-models"><span class="header-section-number">8</span> Downstream models</h2>
<p>Use the people. Do not replace them with three averages.</p>
<ul>
<li><strong>Clustering with <img src="https://latex.codecogs.com/png.latex?k=3"></strong>, a few algorithms, no search over <img src="https://latex.codecogs.com/png.latex?k">. The labels are not the blobs.</li>
<li><strong>PCA of people.</strong> The plane the cluster colours sit on. Individuals overlap.</li>
<li><strong>Welch t-tests with Benjamini–Hochberg <img src="https://latex.codecogs.com/png.latex?q"></strong>, one contrast at a time. The volcano in this post. Method notes in <a href="../volcano-plots/">The Anatomy of a Volcano Plot</a>.</li>
<li><strong>Sparse PLS or elastic net</strong>, group-stratified CV, only to rank features. 75 people will not carry a biomarker; see <a href="../dataset-to-biological-signature/">From Dataset to Biological Signature</a>.</li>
<li><strong>CP / PARAFAC</strong> only on a real cube: chromatograms, or the same donors in more than one state. <a href="../uses-of-tensor-factorizations/">Uses of Tensor Factorizations</a> is that case. This file is not.</li>
</ul>
<p>Skip an unregularised 285-feature classifier, a deep net, Poisson models, NMF on Li et al.’s signed Dataset 3, and a severity score built from the three means.</p>
<p>scikit-learn for k-means, Ward, GMM, ARI, and PCA. SciPy for Welch’s t-test. MetaboAnalyst for tables (Pang et al.&nbsp;2021). mixOmics / ropls for PLS. MZmine, XCMS, pyOpenMS, matchms for chromatograms. TensorLy for PARAFAC (Bro 1997).</p>
</section>
<section id="constraints" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="constraints"><span class="header-section-number">9</span> Constraints</h2>
<p>This is one transform of one matrix, not a biomarker panel.</p>
<ul>
<li>Peak area is not flux and not NETs.</li>
<li>A deposited name is not a confirmed structure. Forty lipids are sum-composition labels.</li>
<li>Groups are not time. One cohort is not a replication. Bulk pellets mix cell-state with within-cell change.</li>
<li><img src="https://latex.codecogs.com/png.latex?k=3"> was taken from the labels. ARI 0.14–0.23 and silhouette 0.05 move if the transform moves.</li>
<li><img src="https://latex.codecogs.com/png.latex?q%3C0.05"> counts (108 and 134) are per-contrast Benjamini–Hochberg on 285 Welch tests of <img src="https://latex.codecogs.com/png.latex?%5Clog_2(1+x)">. They move if the transform or the contrast set moves.</li>
<li>A full-covariance GMM, a neighbour count for spectral clustering, and a search over <img src="https://latex.codecogs.com/png.latex?k"> are tuning. 75 rows will not support them.</li>
</ul>
<p>Labels. Are. Not. Clusters. Means. Are. Not. People. Leave. k. Alone.</p>
</section>
<section id="references" class="level2" data-number="10">
<h2 data-number="10" class="anchored" data-anchor-id="references"><span class="header-section-number">10</span> References</h2>
<ul>
<li>Britt, E. C., et al.&nbsp;(2022). Switching to the cyclic pentose phosphate pathway powers the oxidative burst in activated neutrophils. <em>Nature Metabolism</em>. <a href="https://doi.org/10.1038/s42255-022-00550-8">doi:10.1038/s42255-022-00550-8</a></li>
<li>Bro, R. (1997). PARAFAC. Tutorial and applications. <em>Chemometrics and Intelligent Laboratory Systems</em> 38: 149–171. <a href="https://doi.org/10.1016/S0169-7439(97)00032-4">doi:10.1016/S0169-7439(97)00032-4</a></li>
<li>Information retained and lost by a shared linear mean axis in COVID-19 neutrophil metabolomics. Research Square preprint <code>rs-10583501</code>. <a href="https://doi.org/10.21203/rs.3.rs-10583501/v1">doi:10.21203/rs.3.rs-10583501/v1</a></li>
<li>Li, Y., et al.&nbsp;(2023). Neutrophil metabolomics in severe COVID-19 reveal GAPDH as a suppressor of neutrophil extracellular trap formation. <em>Nature Communications</em> 14: 2610. <a href="https://doi.org/10.1038/s41467-023-37567-w">doi:10.1038/s41467-023-37567-w</a></li>
<li>Metabolomics Workbench ST002477 / PR001600. <a href="https://doi.org/10.21228/M8W70C">doi:10.21228/M8W70C</a></li>
<li>Morrison, T., Watts, E. R., Sadiku, P., and Walmsley, S. R. (2022). The emerging role for metabolism in fueling neutrophilic inflammation. <em>Immunological Reviews</em>. <a href="https://doi.org/10.1111/imr.13157">doi:10.1111/imr.13157</a></li>
<li>Pang, Z., et al.&nbsp;(2021). MetaboAnalyst 5.0: narrowing the gap between raw spectra and functional insights. <em>Nucleic Acids Research</em>. <a href="https://doi.org/10.1093/nar/gkab382">doi:10.1093/nar/gkab382</a></li>
<li><a href="../dataset-to-biological-signature/">From Dataset to Biological Signature</a></li>
<li><a href="../clustering-tuning-metrics/">Hyperparameter Tuning in Clustering</a></li>
<li><a href="../volcano-plots/">The Anatomy of a Volcano Plot</a></li>
<li><a href="../uses-of-tensor-factorizations/">Uses of Tensor Factorizations</a></li>
</ul>


</section>

 ]]></description>
  <category>Bioinformatics</category>
  <category>Linear Algebra</category>
  <category>Machine Learning</category>
  <guid>https://project-delphi.github.io/ml-blog/posts/neutrophil-metabolome-axis/</guid>
  <pubDate>Thu, 10 Sep 2026 05:00:00 GMT</pubDate>
  <media:content url="https://project-delphi.github.io/ml-blog/posts/neutrophil-metabolome-axis/cover.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>A NumPy-like Tensor in Pure Python</title>
  <dc:creator>Ravi Kalia</dc:creator>
  <link>https://project-delphi.github.io/ml-blog/posts/numpy-like-tensor/</link>
  <description><![CDATA[ 





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/numpy-like-tensor/cover.png" class="img-fluid figure-img"></p>
<figcaption>A NumPy-like Tensor in Pure Python</figcaption>
</figure>
</div>
<p>Nested Python lists already hold a grid. They do not carry a shape we can reshape. A tiny class that stores a flat list and a shape tuple gives NumPy’s layout methods with no NumPy.</p>
<p>We already write nested lists and call <code>len</code> for the first axis. The other axes are nested loops. NumPy’s <code>.shape</code> is that layout made explicit. <code>shape</code>, <code>ndim</code>, and <code>size</code> are attributes, not calls.</p>
<section id="storage" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="storage"><span class="header-section-number">1</span> Storage</h2>
<p>This post uses a synthetic <img src="https://latex.codecogs.com/png.latex?2%5Ctimes%203%5Ctimes%204"> array of <code>range(24)</code>.</p>
<p>Provenance:</p>
<ul>
<li>The 24 integers are generated, not measured.</li>
<li>The array stands in for a small 3-way table (batch × row × column).</li>
<li>Size 24 was chosen so each entry can keep one colour in the figures.</li>
<li>A measured table of that shape is larger; the layout questions are the same.</li>
</ul>
<p>The constructor flattens nested lists and records a shape. It rejects a shape whose product is not <code>len(data)</code>.</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> math <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> prod</span>
<span id="cb1-2"></span>
<span id="cb1-3"></span>
<span id="cb1-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _flatten_shape(data):</span>
<span id="cb1-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(data, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>):</span>
<span id="cb1-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [data], ()</span>
<span id="cb1-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> data:</span>
<span id="cb1-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [], (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,)</span>
<span id="cb1-9">    parts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [_flatten_shape(item) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> item <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> data]</span>
<span id="cb1-10">    shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> parts[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb1-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">any</span>(s <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> shape <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _, s <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> parts):</span>
<span id="cb1-12">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">ValueError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ragged nested list"</span>)</span>
<span id="cb1-13">    flat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [v <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> f, _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> parts <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> v <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> f]</span>
<span id="cb1-14">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> flat, (<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(data),) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> shape</span>
<span id="cb1-15"></span>
<span id="cb1-16"></span>
<span id="cb1-17"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _unravel(index, shape):</span>
<span id="cb1-18">    out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb1-19">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> size <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">reversed</span>(shape):</span>
<span id="cb1-20">        out.append(index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%</span> size)</span>
<span id="cb1-21">        index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//=</span> size</span>
<span id="cb1-22">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">reversed</span>(out))</span>
<span id="cb1-23"></span>
<span id="cb1-24"></span>
<span id="cb1-25"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _ravel(multi, shape):</span>
<span id="cb1-26">    index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb1-27">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, n <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(multi, shape):</span>
<span id="cb1-28">        index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> i</span>
<span id="cb1-29">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> index</span>
<span id="cb1-30"></span>
<span id="cb1-31"></span>
<span id="cb1-32"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _pad_key(key, ndim):</span>
<span id="cb1-33">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(key, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>):</span>
<span id="cb1-34">        key <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (key,)</span>
<span id="cb1-35">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> key.count(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">Ellipsis</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:</span>
<span id="cb1-36">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">IndexError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"at most one ellipsis"</span>)</span>
<span id="cb1-37">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">Ellipsis</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> key:</span>
<span id="cb1-38">        loc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> key.index(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">Ellipsis</span>)</span>
<span id="cb1-39">        n_fill <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ndim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> (<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(key) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb1-40">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> n_fill <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>:</span>
<span id="cb1-41">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">IndexError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"too many indices"</span>)</span>
<span id="cb1-42">        key <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> key[:loc] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">slice</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>),) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> n_fill <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> key[loc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> :]</span>
<span id="cb1-43">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(key) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> ndim:</span>
<span id="cb1-44">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">IndexError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"too many indices"</span>)</span>
<span id="cb1-45">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(key) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> ndim:</span>
<span id="cb1-46">        key <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> key <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">slice</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>),) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (ndim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(key))</span>
<span id="cb1-47">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> key</span>
<span id="cb1-48"></span>
<span id="cb1-49"></span>
<span id="cb1-50"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _axis_int(i, n):</span>
<span id="cb1-51">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(i, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(i, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>):</span>
<span id="cb1-52">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">TypeError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"integer index expected"</span>)</span>
<span id="cb1-53">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>:</span>
<span id="cb1-54">        i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> n</span>
<span id="cb1-55">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> n:</span>
<span id="cb1-56">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">IndexError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"index out of bounds"</span>)</span>
<span id="cb1-57">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> i</span>
<span id="cb1-58"></span>
<span id="cb1-59"></span>
<span id="cb1-60"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Tensor:</span>
<span id="cb1-61">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, data, shape<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>):</span>
<span id="cb1-62">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> shape <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb1-63">            data, shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> _flatten_shape(data)</span>
<span id="cb1-64">        data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(data)</span>
<span id="cb1-65">        shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(shape)</span>
<span id="cb1-66">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> prod(shape) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(data):</span>
<span id="cb1-67">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">ValueError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"shape does not match size"</span>)</span>
<span id="cb1-68">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data</span>
<span id="cb1-69">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> shape</span></code></pre></div>
</section>
<section id="counts" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="counts"><span class="header-section-number">2</span> Counts</h2>
<p>Four numbers describe the same layout:</p>
<ul>
<li><strong>shape</strong> — length of each axis: <code>(2, 3, 4)</code>.</li>
<li><strong>ndim</strong> — how many axes: <code>3</code>.</li>
<li><strong>size</strong> — product of the shape: <code>24</code>.</li>
<li><strong>len</strong> — first axis only: <code>2</code>, not <code>24</code>.</li>
</ul>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@property</span></span>
<span id="cb2-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> shape(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb2-3">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._shape</span>
<span id="cb2-4"></span>
<span id="cb2-5">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@property</span></span>
<span id="cb2-6">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> ndim(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb2-7">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._shape)</span>
<span id="cb2-8"></span>
<span id="cb2-9">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@property</span></span>
<span id="cb2-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> size(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb2-11">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> prod(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._shape)</span>
<span id="cb2-12"></span>
<span id="cb2-13">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__len__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb2-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span></code></pre></div>
</section>
<section id="reshape" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="reshape"><span class="header-section-number">3</span> Reshape</h2>
<p><code>reshape</code> returns a new <code>Tensor</code> with the same <code>_data</code> and a new shape. The product must equal <code>.size</code>. The list is copied; this is not a NumPy view.</p>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> reshape(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>shape):</span>
<span id="cb3-2">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> prod(shape) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.size:</span>
<span id="cb3-3">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">ValueError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"size mismatch"</span>)</span>
<span id="cb3-4">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> Tensor(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._data, shape)</span></code></pre></div>
<p>Each colour is one entry. The strip is storage order.</p>
<div id="fig-reshape" class="quarto-float quarto-figure quarto-figure-center anchored" alt="Animation. Top panel is the tensor layout; bottom panel is a 24-cell storage strip, one colour per entry. Starts as two 3 by 4 slabs, morphs to a column of 24, a row of 24, then four 3 by 2 slabs. The storage strip remains 0 through 23 in rainbow order.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-reshape-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/numpy-like-tensor/reshape.gif" class="img-fluid figure-img" alt="Animation. Top panel is the tensor layout; bottom panel is a 24-cell storage strip, one colour per entry. Starts as two 3 by 4 slabs, morphs to a column of 24, a row of 24, then four 3 by 2 slabs. The storage strip remains 0 through 23 in rainbow order.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-reshape-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Layout of a <img src="https://latex.codecogs.com/png.latex?2%5Ctimes%203%5Ctimes%204"> tensor after <code>reshape</code> to a column, a row, and a <img src="https://latex.codecogs.com/png.latex?4%5Ctimes%203%5Ctimes%202">. The storage strip stays <code>0</code> through <code>23</code>.
</figcaption>
</figure>
</div>
<p>The column, the row, and the <img src="https://latex.codecogs.com/png.latex?4%5Ctimes%203%5Ctimes%202"> keep that order.</p>
</section>
<section id="transpose" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="transpose"><span class="header-section-number">4</span> Transpose</h2>
<p>Default <code>transpose</code> reverses axes. <img src="https://latex.codecogs.com/png.latex?(2,%203,%204)"> becomes <img src="https://latex.codecogs.com/png.latex?(4,%203,%202)">. Each multi-index is permuted and written into a new flat list.</p>
<div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> transpose(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>axes):</span>
<span id="cb4-2">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> axes:</span>
<span id="cb4-3">            axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ndim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb4-4">        new_shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._shape[a] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> axes)</span>
<span id="cb4-5">        out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.size</span>
<span id="cb4-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, value <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._data):</span>
<span id="cb4-7">            old <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> _unravel(i, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._shape)</span>
<span id="cb4-8">            new <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(old[a] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> axes)</span>
<span id="cb4-9">            out[_ravel(new, new_shape)] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> value</span>
<span id="cb4-10">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> Tensor(out, new_shape)</span></code></pre></div>
<div id="fig-transpose" class="quarto-float quarto-figure quarto-figure-center anchored" alt="Animation. Same start as the reshape GIF: two 3 by 4 slabs and a rainbow storage strip 0 through 23. After transpose the layout is four 3 by 2 slabs with mixed colours, and the storage strip reads 0, 12, 4, 16, and so on.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-transpose-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/numpy-like-tensor/transpose.gif" class="img-fluid figure-img" alt="Animation. Same start as the reshape GIF: two 3 by 4 slabs and a rainbow storage strip 0 through 23. After transpose the layout is four 3 by 2 slabs with mixed colours, and the storage strip reads 0, 12, 4, 16, and so on.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-transpose-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: The same <img src="https://latex.codecogs.com/png.latex?2%5Ctimes%203%5Ctimes%204"> tensor after <code>transpose</code>. Layout is <img src="https://latex.codecogs.com/png.latex?4%5Ctimes%203%5Ctimes%202">; the storage strip is no longer <code>0</code> through <code>23</code>.
</figcaption>
</figure>
</div>
<p>The <img src="https://latex.codecogs.com/png.latex?4%5Ctimes%203%5Ctimes%202"> box matches the last reshape. The strip does not.</p>
</section>
<section id="indexing" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="indexing"><span class="header-section-number">5</span> Indexing</h2>
<p><code>__getitem__</code> reads values. Three forms. Every result is a copy.</p>
<ul>
<li><strong>Simple</strong> — an integer. That axis drops. <code>t[0]</code> has shape <code>(3, 4)</code>. <code>t[0, 2, 3]</code> is <code>11</code>.</li>
<li><strong>Slicing</strong> — a <code>slice</code>. That axis stays. <code>t[:, 1]</code> is the middle row of both batches, shape <code>(2, 4)</code>. <code>t[0, :2, 1:3]</code> has shape <code>(2, 2)</code>.</li>
<li><strong>Fancy</strong> — a list of integers gathers those positions. <code>t[[0, 1]]</code> is both batches. Two lists pick pairs, not a grid: <code>t[[0, 1], [2, 0], 0]</code> is <code>[8, 12]</code>. The <img src="https://latex.codecogs.com/png.latex?2%5Ctimes%202"> block is a slice: <code>t[:2, :2, 0]</code>.</li>
</ul>
<div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__getitem__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, key):</span>
<span id="cb5-2">        key <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> _pad_key(key, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ndim)</span>
<span id="cb5-3">        kinds <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb5-4">        coords <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb5-5">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> k, n <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(key, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._shape):</span>
<span id="cb5-6">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(k, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>):</span>
<span id="cb5-7">                coords.append([_axis_int(i, n) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> k])</span>
<span id="cb5-8">                kinds.append(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fancy"</span>)</span>
<span id="cb5-9">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(k, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">slice</span>):</span>
<span id="cb5-10">                coords.append(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>k.indices(n))))</span>
<span id="cb5-11">                kinds.append(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"slice"</span>)</span>
<span id="cb5-12">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(k, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(k, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>):</span>
<span id="cb5-13">                coords.append([_axis_int(k, n)])</span>
<span id="cb5-14">                kinds.append(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"int"</span>)</span>
<span id="cb5-15">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb5-16">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">TypeError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"index must be an int, a slice, or a list of ints"</span>)</span>
<span id="cb5-17"></span>
<span id="cb5-18">        fancy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [i <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, kind <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(kinds) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> kind <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fancy"</span>]</span>
<span id="cb5-19">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> fancy:</span>
<span id="cb5-20">            length <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(coords[fancy[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]])</span>
<span id="cb5-21">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">any</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(coords[i]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> length <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> fancy):</span>
<span id="cb5-22">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">IndexError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fancy lists must have the same length"</span>)</span>
<span id="cb5-23">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(fancy) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(fancy) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(fancy):</span>
<span id="cb5-24">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">IndexError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fancy axes must be adjacent"</span>)</span>
<span id="cb5-25"></span>
<span id="cb5-26">        out_shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb5-27">        ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb5-28">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">while</span> ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ndim:</span>
<span id="cb5-29">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> fancy <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(fancy):</span>
<span id="cb5-30">                out_shape.append(length)</span>
<span id="cb5-31">                ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(fancy) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb5-32">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">continue</span></span>
<span id="cb5-33">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> kinds[ax] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"slice"</span>:</span>
<span id="cb5-34">                out_shape.append(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(coords[ax]))</span>
<span id="cb5-35">            ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb5-36">        out_shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(out_shape)</span>
<span id="cb5-37"></span>
<span id="cb5-38">        data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb5-39"></span>
<span id="cb5-40">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> fill(ax, p, multi):</span>
<span id="cb5-41">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ndim:</span>
<span id="cb5-42">                data.append(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._data[_ravel(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(multi), <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._shape)])</span>
<span id="cb5-43">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span></span>
<span id="cb5-44">            kind <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> kinds[ax]</span>
<span id="cb5-45">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> kind <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"int"</span>:</span>
<span id="cb5-46">                fill(ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, p, multi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> [coords[ax][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]])</span>
<span id="cb5-47">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> kind <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"slice"</span>:</span>
<span id="cb5-48">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> coords[ax]:</span>
<span id="cb5-49">                    fill(ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, p, multi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> [i])</span>
<span id="cb5-50">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(fancy):</span>
<span id="cb5-51">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> q <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(length):</span>
<span id="cb5-52">                    fill(ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, q, multi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> [coords[ax][q]])</span>
<span id="cb5-53">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb5-54">                fill(ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, p, multi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> [coords[ax][p]])</span>
<span id="cb5-55"></span>
<span id="cb5-56">        fill(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, [])</span>
<span id="cb5-57">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> out_shape:</span>
<span id="cb5-58">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> data[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb5-59">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> Tensor(data, out_shape)</span></code></pre></div>
</section>
<section id="constraints" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="constraints"><span class="header-section-number">6</span> Constraints</h2>
<p>No broadcasting, no boolean mask, no newaxis, no views. Fancy axes must be adjacent. Enough to see why NumPy treats shape as data. <a href="../numpy-to-jax/">NumPy to JAX</a> is the real API.</p>
</section>
<section id="class" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="class"><span class="header-section-number">7</span> Class</h2>
<p>Markdown fences do not join. <code>reshape</code>, <code>transpose</code>, and <code>__getitem__</code> are ordinary methods on <code>Tensor</code>:</p>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> math <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> prod</span>
<span id="cb6-2"></span>
<span id="cb6-3"></span>
<span id="cb6-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _flatten_shape(data):</span>
<span id="cb6-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(data, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>):</span>
<span id="cb6-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [data], ()</span>
<span id="cb6-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> data:</span>
<span id="cb6-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [], (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,)</span>
<span id="cb6-9">    parts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [_flatten_shape(item) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> item <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> data]</span>
<span id="cb6-10">    shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> parts[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb6-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">any</span>(s <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> shape <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _, s <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> parts):</span>
<span id="cb6-12">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">ValueError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ragged nested list"</span>)</span>
<span id="cb6-13">    flat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [v <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> f, _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> parts <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> v <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> f]</span>
<span id="cb6-14">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> flat, (<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(data),) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> shape</span>
<span id="cb6-15"></span>
<span id="cb6-16"></span>
<span id="cb6-17"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _unravel(index, shape):</span>
<span id="cb6-18">    out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb6-19">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> size <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">reversed</span>(shape):</span>
<span id="cb6-20">        out.append(index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%</span> size)</span>
<span id="cb6-21">        index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//=</span> size</span>
<span id="cb6-22">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">reversed</span>(out))</span>
<span id="cb6-23"></span>
<span id="cb6-24"></span>
<span id="cb6-25"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _ravel(multi, shape):</span>
<span id="cb6-26">    index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb6-27">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, n <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(multi, shape):</span>
<span id="cb6-28">        index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> i</span>
<span id="cb6-29">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> index</span>
<span id="cb6-30"></span>
<span id="cb6-31"></span>
<span id="cb6-32"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _pad_key(key, ndim):</span>
<span id="cb6-33">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(key, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>):</span>
<span id="cb6-34">        key <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (key,)</span>
<span id="cb6-35">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> key.count(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">Ellipsis</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:</span>
<span id="cb6-36">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">IndexError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"at most one ellipsis"</span>)</span>
<span id="cb6-37">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">Ellipsis</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> key:</span>
<span id="cb6-38">        loc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> key.index(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">Ellipsis</span>)</span>
<span id="cb6-39">        n_fill <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ndim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> (<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(key) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb6-40">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> n_fill <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>:</span>
<span id="cb6-41">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">IndexError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"too many indices"</span>)</span>
<span id="cb6-42">        key <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> key[:loc] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">slice</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>),) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> n_fill <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> key[loc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> :]</span>
<span id="cb6-43">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(key) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> ndim:</span>
<span id="cb6-44">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">IndexError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"too many indices"</span>)</span>
<span id="cb6-45">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(key) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> ndim:</span>
<span id="cb6-46">        key <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> key <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">slice</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>),) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (ndim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(key))</span>
<span id="cb6-47">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> key</span>
<span id="cb6-48"></span>
<span id="cb6-49"></span>
<span id="cb6-50"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _axis_int(i, n):</span>
<span id="cb6-51">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(i, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(i, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>):</span>
<span id="cb6-52">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">TypeError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"integer index expected"</span>)</span>
<span id="cb6-53">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>:</span>
<span id="cb6-54">        i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> n</span>
<span id="cb6-55">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> n:</span>
<span id="cb6-56">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">IndexError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"index out of bounds"</span>)</span>
<span id="cb6-57">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> i</span>
<span id="cb6-58"></span>
<span id="cb6-59"></span>
<span id="cb6-60"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Tensor:</span>
<span id="cb6-61">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, data, shape<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>):</span>
<span id="cb6-62">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> shape <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb6-63">            data, shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> _flatten_shape(data)</span>
<span id="cb6-64">        data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(data)</span>
<span id="cb6-65">        shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(shape)</span>
<span id="cb6-66">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> prod(shape) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(data):</span>
<span id="cb6-67">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">ValueError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"shape does not match size"</span>)</span>
<span id="cb6-68">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data</span>
<span id="cb6-69">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> shape</span>
<span id="cb6-70"></span>
<span id="cb6-71">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@property</span></span>
<span id="cb6-72">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> shape(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb6-73">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._shape</span>
<span id="cb6-74"></span>
<span id="cb6-75">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@property</span></span>
<span id="cb6-76">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> ndim(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb6-77">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._shape)</span>
<span id="cb6-78"></span>
<span id="cb6-79">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@property</span></span>
<span id="cb6-80">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> size(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb6-81">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> prod(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._shape)</span>
<span id="cb6-82"></span>
<span id="cb6-83">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__len__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb6-84">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb6-85"></span>
<span id="cb6-86">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> reshape(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>shape):</span>
<span id="cb6-87">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> prod(shape) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.size:</span>
<span id="cb6-88">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">ValueError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"size mismatch"</span>)</span>
<span id="cb6-89">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> Tensor(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._data, shape)</span>
<span id="cb6-90"></span>
<span id="cb6-91">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> transpose(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>axes):</span>
<span id="cb6-92">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> axes:</span>
<span id="cb6-93">            axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ndim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb6-94">        new_shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._shape[a] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> axes)</span>
<span id="cb6-95">        out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.size</span>
<span id="cb6-96">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, value <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._data):</span>
<span id="cb6-97">            old <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> _unravel(i, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._shape)</span>
<span id="cb6-98">            new <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(old[a] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> axes)</span>
<span id="cb6-99">            out[_ravel(new, new_shape)] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> value</span>
<span id="cb6-100">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> Tensor(out, new_shape)</span>
<span id="cb6-101"></span>
<span id="cb6-102">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__getitem__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, key):</span>
<span id="cb6-103">        key <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> _pad_key(key, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ndim)</span>
<span id="cb6-104">        kinds <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb6-105">        coords <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb6-106">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> k, n <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(key, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._shape):</span>
<span id="cb6-107">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(k, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>):</span>
<span id="cb6-108">                coords.append([_axis_int(i, n) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> k])</span>
<span id="cb6-109">                kinds.append(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fancy"</span>)</span>
<span id="cb6-110">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(k, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">slice</span>):</span>
<span id="cb6-111">                coords.append(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>k.indices(n))))</span>
<span id="cb6-112">                kinds.append(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"slice"</span>)</span>
<span id="cb6-113">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(k, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(k, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>):</span>
<span id="cb6-114">                coords.append([_axis_int(k, n)])</span>
<span id="cb6-115">                kinds.append(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"int"</span>)</span>
<span id="cb6-116">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb6-117">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">TypeError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"index must be an int, a slice, or a list of ints"</span>)</span>
<span id="cb6-118"></span>
<span id="cb6-119">        fancy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [i <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, kind <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(kinds) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> kind <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fancy"</span>]</span>
<span id="cb6-120">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> fancy:</span>
<span id="cb6-121">            length <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(coords[fancy[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]])</span>
<span id="cb6-122">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">any</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(coords[i]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> length <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> fancy):</span>
<span id="cb6-123">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">IndexError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fancy lists must have the same length"</span>)</span>
<span id="cb6-124">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(fancy) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(fancy) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(fancy):</span>
<span id="cb6-125">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">IndexError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fancy axes must be adjacent"</span>)</span>
<span id="cb6-126"></span>
<span id="cb6-127">        out_shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb6-128">        ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb6-129">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">while</span> ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ndim:</span>
<span id="cb6-130">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> fancy <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(fancy):</span>
<span id="cb6-131">                out_shape.append(length)</span>
<span id="cb6-132">                ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(fancy) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb6-133">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">continue</span></span>
<span id="cb6-134">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> kinds[ax] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"slice"</span>:</span>
<span id="cb6-135">                out_shape.append(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(coords[ax]))</span>
<span id="cb6-136">            ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb6-137">        out_shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(out_shape)</span>
<span id="cb6-138"></span>
<span id="cb6-139">        data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb6-140"></span>
<span id="cb6-141">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> fill(ax, p, multi):</span>
<span id="cb6-142">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ndim:</span>
<span id="cb6-143">                data.append(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._data[_ravel(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(multi), <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._shape)])</span>
<span id="cb6-144">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span></span>
<span id="cb6-145">            kind <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> kinds[ax]</span>
<span id="cb6-146">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> kind <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"int"</span>:</span>
<span id="cb6-147">                fill(ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, p, multi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> [coords[ax][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]])</span>
<span id="cb6-148">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> kind <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"slice"</span>:</span>
<span id="cb6-149">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> coords[ax]:</span>
<span id="cb6-150">                    fill(ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, p, multi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> [i])</span>
<span id="cb6-151">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(fancy):</span>
<span id="cb6-152">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> q <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(length):</span>
<span id="cb6-153">                    fill(ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, q, multi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> [coords[ax][q]])</span>
<span id="cb6-154">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb6-155">                fill(ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, p, multi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> [coords[ax][p]])</span>
<span id="cb6-156"></span>
<span id="cb6-157">        fill(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, [])</span>
<span id="cb6-158">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> out_shape:</span>
<span id="cb6-159">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> data[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb6-160">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> Tensor(data, out_shape)</span>
<span id="cb6-161"></span>
<span id="cb6-162"></span>
<span id="cb6-163">t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Tensor(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">24</span>)), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>))</span>
<span id="cb6-164"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> t.shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>)</span>
<span id="cb6-165"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(t) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb6-166"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> t.reshape(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)._data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> t._data</span>
<span id="cb6-167"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> t.transpose()._data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> t._data</span>
<span id="cb6-168"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> t[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">11</span></span>
<span id="cb6-169"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> t[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>)</span>
<span id="cb6-170"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> t[[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]._data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>]</span>
<span id="cb6-171"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> t[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, :<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span></code></pre></div>
<p>Storage. Order. Survives. Reshape. Transpose. Breaks. It. Indexing. Copies. Out.</p>
</section>
<section id="references" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="references"><span class="header-section-number">8</span> References</h2>
<ul>
<li>NumPy, <a href="https://numpy.org/doc/stable/reference/arrays.ndarray.html">The N-dimensional array (<code>ndarray</code>)</a>.</li>
<li>NumPy, <a href="https://numpy.org/doc/stable/user/basics.indexing.html">Indexing on ndarrays</a>.</li>
<li><a href="../numpy-to-jax/">NumPy to JAX</a> — this blog; <code>shape</code>, <code>ndim</code>, <code>size</code>, and <code>reshape</code> on the real array type.</li>
</ul>


</section>

 ]]></description>
  <category>Python</category>
  <category>Tensors</category>
  <guid>https://project-delphi.github.io/ml-blog/posts/numpy-like-tensor/</guid>
  <pubDate>Thu, 10 Sep 2026 05:00:00 GMT</pubDate>
  <media:content url="https://project-delphi.github.io/ml-blog/posts/numpy-like-tensor/cover.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>What the Immune System Sees in an AAV Vector</title>
  <dc:creator>Ravi Kalia</dc:creator>
  <link>https://project-delphi.github.io/ml-blog/posts/aav-immune-response/</link>
  <description><![CDATA[ 





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/aav-immune-response/cover.png" class="img-fluid figure-img"></p>
<figcaption>What the Immune System Sees in an AAV Vector</figcaption>
</figure>
</div>
<p>A gene therapy arrives as a vector. The body has no category for that. What reaches the bloodstream is a protein shell around 25 nanometres across with a single strand of DNA folded inside, assembled from the same genes a wild virus uses, and the receptors that meet it evolved to find exactly that shape. They cannot read intent.</p>
<p>Four arms of the immune system answer. They read three different things: the capsid shell, the DNA folded inside it, and the protein that DNA goes on to make. The shell is read twice — intact from the outside, and again in fragments from the inside, after a cell has taken a particle up and broken it apart. Which arm dominates decides whether enough genomes reach a nucleus, whether the patient’s liver enzymes climb in week three, and whether a second dose is ever possible.</p>
<div id="fig-route" class="dia column-page quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-route-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<svg class="aav-fig" viewbox="0 0 1240 640" shape-rendering="geometricPrecision" aria-label="An AAV vector travels from the bloodstream through the liver sinusoid, across the hepatocyte membrane, through an endosome and the cytosol, to the nucleus. A tapering ribbon shows the surviving fraction of the dose, falling from the full dose to 7.6 percent. Six numbered barriers are marked at the site each acts: complement settling on the capsid in the bloodstream, binding antibody and complement bringing Kupffer cell clearance at the sinusoid wall, neutralising antibody blocking AAVR at the cell surface, TLR9 reading unmethylated CpG in the endosome, the proteasome and interferon in the cytosol, and episome formation in the nucleus. TLR2 is drawn at the membrane, and a CD8 T cell above the cell is marked as acting weeks later, outside the six stations.">
  <defs>
    <marker id="aav-ah-route" viewbox="0 0 10 10" refx="9" refy="5" markerwidth="7" markerheight="7" orient="auto-start-reverse">
      <path d="M0,0 L10,5 L0,10 z" fill="var(--aav-muted)"></path>
    </marker>
    <marker id="aav-ah-kill" viewbox="0 0 10 10" refx="9" refy="5" markerwidth="7" markerheight="7" orient="auto-start-reverse">
      <path d="M0,0 L10,5 L0,10 z" fill="var(--aav-tcell)"></path>
    </marker>
  </defs>

  <!-- ========== compartments ========== -->
  <rect x="36" y="110" width="470" height="350" rx="22" fill="var(--aav-blood)"></rect>
  <text class="aav-fig-note" x="54" y="136">plasma, sinusoid lumen</text>

  <rect x="500" y="110" width="704" height="350" rx="28" fill="var(--aav-paper)" stroke="var(--aav-ink)" stroke-width="4"></rect>
  <text class="aav-fig-note" x="522" y="136">hepatocyte</text>
  <text class="aav-fig-note aav-fig-end" x="486" y="452">plasma membrane</text>
  <ellipse cx="1080" cy="330" rx="78" ry="86" fill="var(--aav-nucleoplasm)" stroke="var(--aav-ink)" stroke-width="3.5"></ellipse>

  <!-- ========== surviving-dose ribbon: height is the surviving fraction ========== -->
  <path d="M60,270 H139 L161,274.8 H329 L351,290.3 H509 L531,307 H709 L731,319.4 H889 L911,323.7 H991 L1013,325.4 H1124
           L1124,334.6 H1013 L991,336.3 H911 L889,340.6 H731 L709,353.1 H531 L509,369.8 H351 L329,385.2 H161 L139,390 H60 Z" fill="#E8E4F7" stroke="var(--aav-vector)" stroke-width="1.6"></path>
  <text class="aav-fig-note" x="60" y="256">full dose</text>

  <!-- ========== 1 bloodstream: antibody above, complement below ========== -->
  <g stroke="var(--aav-antibody)" stroke-width="4.5" stroke-linecap="round" fill="none">
    <path d="M108 200 v-16 M108 184 l-13 -15 M108 184 l13 -15"></path>
    <path d="M158 182 v-16 M158 166 l-13 -15 M158 166 l13 -15"></path>
    <path d="M208 206 v-16 M208 190 l-13 -15 M208 190 l13 -15"></path>
  </g>
  <text class="aav-fig-note aav-fig-mid fill-antibody" x="158" y="232">IgG in circulation — acts at 2 and 3</text>
  <g fill="var(--aav-complement)">
    <circle cx="112" cy="418" r="7"></circle><circle cx="152" cy="430" r="7"></circle><circle cx="192" cy="416" r="7"></circle>
  </g>
  <text class="aav-fig-note aav-fig-mid fill-complement" x="152" y="452">complement C3</text>

  <!-- ========== 2 sinusoid wall: Kupffer cell eating an opsonised capsid ========== -->
  <path d="M300 186 q0 -46 44 -46 q44 0 44 46 q0 46 -44 46 q-44 0 -44 -46 Z" fill="#FFFFFF" stroke="var(--aav-ink)" stroke-width="3"></path>
  <g transform="translate(344 186) scale(0.46)">
    <path d="M0,-24 L20.8,-12 L20.8,12 L0,24 L-20.8,12 L-20.8,-12 Z" fill="var(--aav-vector)"></path>
  </g>
  <path d="M362 172 v-12 M362 160 l-9 -10 M362 160 l9 -10" stroke="var(--aav-antibody)" stroke-width="4" stroke-linecap="round" fill="none"></path>
  <text class="aav-fig-note aav-fig-mid" x="344" y="246">Kupffer cell</text>
  <path d="M344 286 V 248" stroke="var(--aav-muted)" stroke-width="1.8" stroke-dasharray="4 4" fill="none" marker-end="url(#aav-ah-route)"></path>

  <!-- ========== 3 cell surface: AAVR, a blocking IgG, TLR2 ========== -->
  <path d="M500 306 H466" stroke="var(--aav-antibody)" stroke-width="6" stroke-linecap="round"></path>
  <g fill="var(--aav-antibody)"><circle cx="470" cy="306" r="6"></circle><circle cx="484" cy="306" r="6"></circle></g>
  <text class="aav-fig-note aav-fig-end fill-antibody" x="462" y="282">AAVR</text>
  <path d="M446 306 h-18 M428 306 l-14 -12 M428 306 l-14 12" stroke="var(--aav-antibody)" stroke-width="4.5" stroke-linecap="round" fill="none"></path>
  <path d="M452 288 V 324" stroke="var(--aav-alarm)" stroke-width="5" stroke-linecap="round"></path>
  <text class="aav-fig-note aav-fig-end fill-alarm" x="462" y="344">blocked</text>

  <path d="M492 216 C 480 194, 484 176, 498 172" fill="none" stroke="var(--aav-innate)" stroke-width="6" stroke-linecap="round"></path>
  <path d="M516 216 C 528 194, 524 176, 510 172" fill="none" stroke="var(--aav-innate)" stroke-width="6" stroke-linecap="round"></path>
  <rect x="490" y="212" width="28" height="14" rx="5" fill="var(--aav-innate)"></rect>
  <text class="aav-fig-note fill-innate" x="530" y="196">TLR2</text>

  <!-- ========== 4 endosome with TLR9 and CpG ========== -->
  <circle cx="720" cy="196" r="58" fill="#FFFFFF" stroke="var(--aav-ink)" stroke-width="3.5"></circle>
  <g transform="translate(694 196) scale(0.6)">
    <path d="M0,-24 L20.8,-12 L20.8,12 L0,24 L-20.8,12 L-20.8,-12" fill="none" stroke="var(--aav-vector)" stroke-width="4" stroke-linejoin="round"></path>
  </g>
  <path d="M710 196 q7 -10 14 0 t 14 0" fill="none" stroke="var(--aav-vector)" stroke-opacity="0.75" stroke-width="2.2" stroke-linecap="round"></path>
  <g fill="var(--aav-innate)"><circle cx="717" cy="191" r="2.8"></circle><circle cx="731" cy="191" r="2.8"></circle></g>
  <path d="M770 186 C 758 180, 750 190, 748 196" fill="none" stroke="var(--aav-innate)" stroke-width="6" stroke-linecap="round"></path>
  <path d="M770 206 C 758 212, 750 202, 748 196" fill="none" stroke="var(--aav-innate)" stroke-width="6" stroke-linecap="round"></path>
  <text class="aav-fig-note fill-innate" x="786" y="192">TLR9</text>
  <text class="aav-fig-note aav-fig-end" x="654" y="262">endosome</text>
  <path d="M720 302 V 268" stroke="var(--aav-muted)" stroke-width="1.8" stroke-dasharray="4 4" fill="none" marker-end="url(#aav-ah-route)"></path>

  <!-- ========== 5 cytosol: proteasome, MHC I on the membrane, CD8 outside ========== -->
  <g fill="#FFFFFF" stroke="var(--aav-muted)" stroke-width="2">
    <rect x="866" y="164" width="76" height="13" rx="6"></rect>
    <rect x="866" y="180" width="76" height="13" rx="6"></rect>
    <rect x="866" y="196" width="76" height="13" rx="6"></rect>
    <rect x="866" y="212" width="76" height="13" rx="6"></rect>
  </g>
  <text class="aav-fig-note" x="956" y="202">proteasome</text>
  <path d="M904 314 V 234" stroke="var(--aav-muted)" stroke-width="1.8" stroke-dasharray="4 4" fill="none" marker-end="url(#aav-ah-route)"></path>
  <g fill="var(--aav-vector)">
    <rect x="950" y="160" width="14" height="8" rx="2"></rect>
    <rect x="964" y="142" width="14" height="8" rx="2"></rect>
  </g>
  <path d="M946 176 C 962 158, 978 136, 986 122" fill="none" stroke="var(--aav-muted)" stroke-width="1.8" marker-end="url(#aav-ah-route)"></path>
  <path d="M990 110 L972 86 M990 110 L1008 86" stroke="var(--aav-antibody)" stroke-width="5" stroke-linecap="round" fill="none"></path>
  <rect x="983" y="86" width="14" height="8" rx="2" fill="var(--aav-vector)"></rect>
  <text class="aav-fig-note aav-fig-end fill-antibody" x="946" y="96">MHC I</text>

  <circle cx="1064" cy="62" r="42" fill="#FFFFFF" stroke="var(--aav-tcell)" stroke-width="3.5"></circle>
  <text class="aav-fig-lead aav-fig-mid fill-tcell" x="1064" y="60">CD8⁺</text>
  <text class="aav-fig-note aav-fig-mid fill-tcell" x="1064" y="78">T cell</text>
  <text class="aav-fig-note aav-fig-end fill-tcell" x="1016" y="44">weeks later, not a station</text>
  <path d="M1022 62 h-14 v22 h14" fill="none" stroke="var(--aav-tcell)" stroke-width="3.5"></path>
  <path d="M1078 102 C 1094 138, 1092 168, 1074 194" fill="none" stroke="var(--aav-tcell)" stroke-width="2" stroke-dasharray="5 4" marker-end="url(#aav-ah-kill)"></path>
  <text class="aav-fig-note fill-tcell" x="1092" y="184">perforin,</text>
  <text class="aav-fig-note fill-tcell" x="1092" y="198">granzyme</text>

  <!-- ========== 6 nucleus ========== -->
  <g fill="none" stroke="var(--aav-vector)" stroke-width="2.6">
    <circle cx="1042" cy="300" r="10"></circle><circle cx="1114" cy="358" r="10"></circle>
  </g>
  <text class="aav-fig-note aav-fig-mid" x="1080" y="272">nucleus</text>
  <text class="aav-fig-note aav-fig-mid" x="1080" y="378">episome</text>
  <text class="aav-fig-lead aav-fig-mid fill-vector" x="1080" y="440">7.6% of the dose</text>
  <text class="aav-fig-note aav-fig-mid" x="1080" y="456">with no response running</text>

  <!-- ========== station badges, numbered to the cards ========== -->
  <g class="aav-fig-badge">
    <circle cx="150" cy="330" r="16"></circle><circle cx="340" cy="330" r="16"></circle><circle cx="520" cy="330" r="16"></circle>
    <circle cx="720" cy="330" r="16"></circle><circle cx="900" cy="330" r="16"></circle><circle cx="1080" cy="330" r="16"></circle>
  </g>
  <g class="aav-fig-badge-num">
    <text x="150" y="335">1</text><text x="340" y="335">2</text><text x="520" y="335">3</text>
    <text x="720" y="335">4</text><text x="900" y="335">5</text><text x="1080" y="335">6</text>
  </g>

  <!-- ========== barrier cards: arm colours follow the widget's STATIONS ========== -->
  <g class="aav-fig-card">
    <rect x="16" y="500" width="193" height="118" rx="10"></rect>
    <rect x="219" y="500" width="193" height="118" rx="10"></rect>
    <rect x="422" y="500" width="193" height="118" rx="10"></rect>
    <rect x="625" y="500" width="193" height="118" rx="10"></rect>
    <rect x="828" y="500" width="193" height="118" rx="10"></rect>
    <rect x="1031" y="500" width="193" height="118" rx="10"></rect>
  </g>
  <g class="aav-fig-card-bar">
    <rect x="16" y="500" width="5" height="118" fill="var(--aav-complement)"></rect>
    <rect x="219" y="500" width="5" height="59" fill="var(--aav-antibody)"></rect>
    <rect x="219" y="559" width="5" height="59" fill="var(--aav-complement)"></rect>
    <rect x="422" y="500" width="5" height="118" fill="var(--aav-antibody)"></rect>
    <rect x="625" y="500" width="5" height="118" fill="var(--aav-innate)"></rect>
    <rect x="828" y="500" width="5" height="118" fill="var(--aav-innate)"></rect>
    <rect x="1031" y="500" width="5" height="118" fill="var(--aav-vector)"></rect>
  </g>

  <text class="aav-fig-card-n" x="36" y="524">1</text>
  <text class="aav-fig-card-t" x="52" y="524">Bloodstream</text>
  <text class="aav-fig-card-b" x="36" y="548">Complement settles on the</text>
  <text class="aav-fig-card-b" x="36" y="564">capsid surface and the cascade</text>
  <text class="aav-fig-card-b" x="36" y="580">begins to amplify.</text>
  <text class="aav-fig-card-k" x="36" y="604">MINUTES</text>

  <text class="aav-fig-card-n" x="239" y="524">2</text>
  <text class="aav-fig-card-t" x="255" y="524">Sinusoid wall</text>
  <text class="aav-fig-card-b" x="239" y="548">Binding antibody leaves the Fc</text>
  <text class="aav-fig-card-b" x="239" y="564">tail out and complement coats</text>
  <text class="aav-fig-card-b" x="239" y="580">it; Kupffer cells eat it.</text>
  <text class="aav-fig-card-k" x="239" y="604">MINUTES</text>

  <text class="aav-fig-card-n" x="442" y="524">3</text>
  <text class="aav-fig-card-t" x="458" y="524">Cell surface</text>
  <text class="aav-fig-card-b" x="442" y="548">Neutralising antibody works</text>
  <text class="aav-fig-card-b" x="442" y="564">here and only here: it sits over</text>
  <text class="aav-fig-card-b" x="442" y="580">AAVR, so nothing docks.</text>
  <text class="aav-fig-card-k" x="442" y="604">MINUTES</text>

  <text class="aav-fig-card-n" x="645" y="524">4</text>
  <text class="aav-fig-card-t" x="661" y="524">Endosome</text>
  <text class="aav-fig-card-b" x="645" y="548">The capsid starts to open.</text>
  <text class="aav-fig-card-b" x="645" y="564">TLR9 reaches the CpG and</text>
  <text class="aav-fig-card-b" x="645" y="580">drives type I interferon.</text>
  <text class="aav-fig-card-k" x="645" y="604">HOURS</text>

  <text class="aav-fig-card-n" x="848" y="524">5</text>
  <text class="aav-fig-card-t" x="864" y="524">Cytosol</text>
  <text class="aav-fig-card-b" x="848" y="548">A capsid races the proteasome</text>
  <text class="aav-fig-card-b" x="848" y="564">to the pore. Interferon from</text>
  <text class="aav-fig-card-b" x="848" y="580">station 4 suppresses output.</text>
  <text class="aav-fig-card-k" x="848" y="604">HOURS</text>

  <text class="aav-fig-card-n" x="1051" y="524">6</text>
  <text class="aav-fig-card-t" x="1067" y="524">Nucleus</text>
  <text class="aav-fig-card-b" x="1051" y="548">The genome is released, the</text>
  <text class="aav-fig-card-b" x="1051" y="564">second strand made, the ends</text>
  <text class="aav-fig-card-b" x="1051" y="580">joined into an episome.</text>
  <text class="aav-fig-card-k" x="1051" y="604">DAYS</text>
</svg>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-route-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: The route the rest of this covers. Ribbon height is the surviving fraction of the dose, stepping down at each of the six stations; the end of it is the 7.6% that reaches a nucleus in a patient with no response running. Numbers on the ribbon match the cards.
</figcaption>
</figure>
</div>
<section id="recombinant-aav" class="level2 page-columns page-full" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="recombinant-aav"><span class="header-section-number">1</span> Recombinant AAV</h2>
<p>Adeno-associated virus is a small, non-enveloped parvovirus. It carries about 4.7 kilobases of single-stranded DNA and cannot copy itself without a helper virus. In the wild it causes no known disease, which is most of why it became the delivery vehicle for in-vivo gene therapy.</p>
<p>A recombinant AAV — rAAV — keeps the shell and throws away the contents:</p>
<ul>
<li><strong>The capsid</strong> is unchanged. Sixty copies of three related proteins, VP1, VP2 and VP3, packed into an icosahedron. This is the part the therapy needs, because the outside of the shell is what picks which tissue the particle enters.</li>
<li><strong>The genome</strong> is replaced. Everything between the two inverted terminal repeats — the short hairpin sequences at each end that mark where packaging starts and stops — is swapped for a promoter and the therapeutic gene.</li>
<li><strong>The particle is replication-defective.</strong> The genes for making more virus are gone, so a transduced cell produces the therapeutic protein and no progeny virus.</li>
</ul>
<p>Two features of that construction matter to the immune system, and neither is a design choice anyone would make freely.</p>
<ul>
<li>The shell is a viral protein whether or not it carries viral genes. Around 30 to 60 percent of adults carry antibodies against common serotypes such as AAV2 from ordinary childhood exposure, and those antibodies bind a therapeutic capsid exactly as well.</li>
<li>The cargo is unmethylated bacterial-style DNA. Plasmid DNA grown in <em>E. coli</em> is rich in CpG dinucleotides that the mammalian genome has largely lost, and the manufacturing process leaves that signature in the packaged genome.</li>
</ul>
<div id="fig-anatomy" class="dia column-body-outset quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-anatomy-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<svg class="aav-fig" viewbox="0 0 1000 400" shape-rendering="geometricPrecision" aria-label="Two panels. A: the AAV capsid drawn as an icosahedral shell, schematically, with one facet ringed and enlarged beside it to show the subunits it is built from, sixty in the whole shell, and a proportion bar giving about fifty copies of VP3 and about five each of VP2 and VP1. B: the packaged genome. A wild-type row keeps its two inverted terminal repeats and ghosts out only rep and cap; the recombinant cassette below it runs inverted terminal repeat, promoter, therapeutic gene, polyA, inverted terminal repeat across 4.7 kilobases, with unmethylated CpG marks along it.">
  <!-- ============================ PANEL A ============================ -->
  <text class="aav-fig-letter" x="18" y="30">A</text>
  <text class="aav-fig-title" x="40" y="30">CAPSID — 60 SUBUNITS</text>

  <!-- icosahedral shell, with one face ringed and enlarged beside it -->
  <g transform="translate(110 142)">
    <path d="M0,-68 L58.9,-34 L58.9,34 L0,68 L-58.9,34 L-58.9,-34 Z" fill="var(--aav-vector)"></path>
    <path d="M0,0 L0,-68 M0,0 L58.9,-34 M0,0 L58.9,34 M0,0 L0,68 M0,0 L-58.9,34 M0,0 L-58.9,-34" stroke="#FFFFFF" stroke-opacity="0.34" stroke-width="1.6" fill="none"></path>
    <path d="M0,0 L0,-68 L58.9,-34 Z" fill="none" stroke="#FFFFFF" stroke-width="2.6"></path>
  </g>
  <text class="aav-fig-note aav-fig-mid" x="110" y="234">icosahedral shell</text>

  <!-- zoom wedge onto the ringed face, opened out to its three subunits -->
  <g stroke="var(--aav-muted)" stroke-width="1.3" stroke-dasharray="4 4" fill="none">
    <path d="M110 74 L296 80"></path>
    <path d="M168.9 108 L250 164"></path>
  </g>
  <g fill="var(--aav-vector)" stroke="#FFFFFF" stroke-width="2" stroke-linejoin="round">
    <path d="M296 80 L342 164 L296 136 Z"></path>
    <path d="M342 164 L250 164 L296 136 Z"></path>
    <path d="M250 164 L296 80 L296 136 Z"></path>
  </g>
  <text class="aav-fig-note aav-fig-mid" x="296" y="190">one facet, enlarged:</text>
  <text class="aav-fig-note aav-fig-mid" x="296" y="206">60 subunits in all</text>

  <!-- how those 60 positions are shared out -->
  <g>
    <rect x="40" y="252" width="283" height="26" fill="#4A3AA7"></rect>
    <rect x="323" y="252" width="28" height="26" fill="#6E5FC0"></rect>
    <rect x="351" y="252" width="29" height="26" fill="#9B90D6"></rect>
  </g>
  <text class="aav-fig-note fill-white" x="52" y="269">VP3, about 50 copies</text>
  <g>
    <rect x="40" y="294" width="12" height="12" rx="2" fill="#6E5FC0"></rect>
    <rect x="152" y="294" width="12" height="12" rx="2" fill="#9B90D6"></rect>
  </g>
  <text class="aav-fig-note" x="58" y="304">VP2, about 5</text>
  <text class="aav-fig-note" x="170" y="304">VP1, about 5</text>

  <text class="aav-fig-note" x="40" y="344">Unchanged from the wild virus. The outside of the</text>
  <text class="aav-fig-note" x="40" y="360">shell picks which tissue the particle enters.</text>

  <!-- ============================ divider ============================ -->
  <path d="M414 22 V 386" stroke="var(--aav-rule)" stroke-width="1.5"></path>

  <!-- ============================ PANEL B ============================ -->
  <text class="aav-fig-letter" x="436" y="30">B</text>
  <text class="aav-fig-title" x="458" y="30">PACKAGED GENOME — 4.7 kb ssDNA</text>

  <!-- wild type: the ITRs are the part a recombinant vector keeps, so only the
       middle is ghosted -->
  <text class="aav-fig-note" x="470" y="62">wild-type AAV — only the middle is thrown away</text>
  <g fill="none" stroke="var(--aav-vector)" stroke-width="2.4" stroke-linecap="round">
    <path d="M487 102 V 84 M475 84 H499 M479 84 V 74 M495 84 V 74"></path>
    <path d="M923 102 V 84 M911 84 H935 M915 84 V 74 M931 84 V 74"></path>
  </g>
  <g fill="none" stroke="var(--aav-rule)" stroke-width="1.5" stroke-dasharray="5 4">
    <rect x="516" y="72" width="186" height="30" rx="6"></rect>
    <rect x="714" y="72" width="186" height="30" rx="6"></rect>
  </g>
  <text class="aav-fig-note aav-fig-mid" x="609" y="92">rep</text>
  <text class="aav-fig-note aav-fig-mid" x="807" y="92">cap</text>

  <!-- the recombinant cassette -->
  <g fill="none" stroke="var(--aav-vector)" stroke-width="3" stroke-linecap="round">
    <path d="M487 176 V 150 M472 150 H502 M477 150 V 136 M497 150 V 136"></path>
    <path d="M913 176 V 150 M898 150 H928 M903 150 V 136 M923 150 V 136"></path>
  </g>
  <rect x="512" y="140" width="110" height="36" rx="7" fill="#E8E4F7" stroke="var(--aav-vector)" stroke-width="2"></rect>
  <rect x="630" y="140" width="180" height="36" rx="7" fill="var(--aav-vector)"></rect>
  <rect x="818" y="140" width="70" height="36" rx="7" fill="#E8E4F7" stroke="var(--aav-vector)" stroke-width="2"></rect>
  <text class="aav-fig-note aav-fig-mid" x="567" y="163">promoter</text>
  <text class="aav-fig-note aav-fig-mid fill-white" x="720" y="163">therapeutic gene</text>
  <text class="aav-fig-note aav-fig-mid" x="853" y="163">polyA</text>
  <text class="aav-fig-note aav-fig-mid" x="487" y="196">ITR</text>
  <text class="aav-fig-note aav-fig-mid" x="913" y="196">ITR</text>
  <text class="aav-fig-note aav-fig-end" x="948" y="128">recombinant cassette</text>

  <!-- scale -->
  <path d="M470 224 H930" stroke="var(--aav-muted)" stroke-width="1.5"></path>
  <g stroke="var(--aav-muted)" stroke-width="1.5">
    <path d="M470 224 v6 M568 224 v6 M666 224 v6 M764 224 v6 M862 224 v6 M930 224 v6"></path>
  </g>
  <text class="aav-fig-note aav-fig-mid" x="470" y="246">0</text>
  <text class="aav-fig-note aav-fig-mid" x="568" y="246">1</text>
  <text class="aav-fig-note aav-fig-mid" x="666" y="246">2</text>
  <text class="aav-fig-note aav-fig-mid" x="764" y="246">3</text>
  <text class="aav-fig-note aav-fig-mid" x="862" y="246">4</text>
  <text class="aav-fig-note" x="944" y="246">kb</text>

  <!-- CpG left by manufacture -->
  <g stroke="var(--aav-innate)" stroke-width="2.4" stroke-linecap="round">
    <path d="M522 272 v14 M548 272 v14 M566 272 v14 M604 272 v14 M628 272 v14 M652 272 v14
             M678 272 v14 M700 272 v14 M736 272 v14 M758 272 v14 M786 272 v14 M812 272 v14
             M844 272 v14 M868 272 v14"></path>
  </g>
  <text class="aav-fig-note fill-innate" x="470" y="312">unmethylated CpG, left by growing the plasmid in <tspan font-style="italic">E. coli</tspan></text>
  <text class="aav-fig-note" x="470" y="328">The mammalian genome has largely lost this signature, so an endosome</text>
  <text class="aav-fig-note" x="470" y="344">that finds it reads the cargo as foreign.</text>
  <text class="aav-fig-note" x="470" y="376">No rep, no cap: a transduced cell makes the protein and no progeny virus.</text>
</svg>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-anatomy-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: <strong>A</strong> The shell a recombinant vector keeps. <strong>B</strong> The cargo it replaces: everything between the two ITRs is swapped for a promoter, the therapeutic gene and a polyA, and the CpG marks are left by the manufacturing route rather than chosen.
</figcaption>
</figure>
</div>
</section>
<section id="what-the-host-can-sense" class="level2 page-columns page-full" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="what-the-host-can-sense"><span class="header-section-number">2</span> What the host can sense</h2>
<p>The four responses look like four separate topics. They collapse to one question — which part of the particle is exposed, and when.</p>
<div class="aav-cards column-page">
<div class="aav-card arm-innate">
<p><span class="aav-card-h">Innate sensing</span></p>
<p><span class="aav-card-k">Sensor</span> TLR2 on the cell surface, TLR9 inside the endosome</p>
<p><span class="aav-card-k">Clock</span> Minutes to hours</p>
</div>
<div class="aav-card arm-antibody">
<p><span class="aav-card-h">Antibodies</span></p>
<p><span class="aav-card-k">Sensor</span> Neutralising antibody blocks entry, binding antibody opsonises</p>
<p><span class="aav-card-k">Clock</span> Any time, and for years afterwards</p>
</div>
<div class="aav-card arm-tcell">
<p><span class="aav-card-h">CD8<sup>+</sup> T cells</span></p>
<p><span class="aav-card-k">Sensor</span> Capsid peptides displayed on MHC class I</p>
<p><span class="aav-card-k">Clock</span> Four to eight weeks</p>
</div>
<div class="aav-card arm-complement">
<p><span class="aav-card-h">Complement</span></p>
<p><span class="aav-card-k">Sensor</span> C3 settling on the capsid surface, then the cascade</p>
<p><span class="aav-card-k">Clock</span> Minutes</p>
</div>
</div>
<p>These four colours carry through every figure and through the widget’s four switches.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th style="text-align: left;">Exposed</th>
<th style="text-align: left;">Where</th>
<th style="text-align: left;">Sensor</th>
<th style="text-align: left;">Arm</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: left;">Capsid, outside surface</td>
<td style="text-align: left;">Blood, cell surface</td>
<td style="text-align: left;">Pre-existing antibody, TLR2, complement C3</td>
<td style="text-align: left;">Humoral, innate, complement</td>
</tr>
<tr class="even">
<td style="text-align: left;">Capsid ssDNA, unmethylated CpG</td>
<td style="text-align: left;">Endosome, during uncoating</td>
<td style="text-align: left;">TLR9</td>
<td style="text-align: left;">Innate</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Capsid peptides</td>
<td style="text-align: left;">Cell surface, on MHC class I</td>
<td style="text-align: left;">CD8<img src="https://latex.codecogs.com/png.latex?%5E+"> T cell receptor</td>
<td style="text-align: left;">Cellular</td>
</tr>
<tr class="even">
<td style="text-align: left;">Transgene product</td>
<td style="text-align: left;">Cell surface, on MHC class I</td>
<td style="text-align: left;">CD8<img src="https://latex.codecogs.com/png.latex?%5E+"> T cell receptor</td>
<td style="text-align: left;">Cellular</td>
</tr>
</tbody>
</table>
<p>Reading the table down is reading the route. The shell is exposed first and is sensed first; the DNA is exposed only after the particle is inside a compartment where it can begin to open; the peptides appear only after a capsid has been broken up, which takes hours, and the T cells that recognise them take days to weeks to expand.</p>
</section>
<section id="timing" class="level2 page-columns page-full" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="timing"><span class="header-section-number">3</span> Timing</h2>
<ul>
<li><strong>Minutes.</strong> Complement activation and TLR2 engagement. Both act on the intact particle in circulation, so both scale directly with how many particles are in circulation.</li>
<li><strong>Hours.</strong> TLR9 sensing inside the endosome, and the type I interferon response it drives. The particle has to be taken up first.</li>
<li><strong>Days.</strong> Innate signalling licenses antigen-presenting cells, which prime naive T cells.</li>
<li><strong>Weeks.</strong> Capsid-specific CD8<img src="https://latex.codecogs.com/png.latex?%5E+"> T cells expand and kill transduced cells. This is the arm that takes away expression the patient already had.</li>
<li><strong>Any time.</strong> Pre-existing neutralising antibodies act before the first two, and a post-dose antibody response closes the door on a second administration for years.</li>
</ul>
<div id="fig-timeline" class="dia column-body-outset quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-timeline-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<svg class="aav-fig" viewbox="0 0 1080 430" shape-rendering="geometricPrecision" aria-label="A timeline running from minutes to years. Complement and TLR2 act within minutes, TLR9 and the type I interferon response within hours, antigen-presenting cell licensing and T-cell priming over days, capsid-specific CD8 T-cell killing over weeks, and antibody acts at any time: pre-existing before the dose and post-dose for years. Each lane names the clinical readout it produces.">
  <defs>
    <marker id="aav-ah-time" viewbox="0 0 10 10" refx="9" refy="5" markerwidth="7" markerheight="7" orient="auto-start-reverse">
      <path d="M0,0 L10,5 L0,10 z" fill="var(--aav-muted)"></path>
    </marker>
  </defs>

  <!-- axis -->
  <path d="M148 96 H1054" stroke="var(--aav-muted)" stroke-width="1.8" marker-end="url(#aav-ah-time)"></path>
  <g stroke="var(--aav-muted)" stroke-width="1.8">
    <path d="M192 88 v14 M250 90 v12 M420 90 v12 M590 90 v12 M760 90 v12 M940 90 v12"></path>
  </g>
  <text class="aav-fig-title aav-fig-mid" x="250" y="76">MINUTES</text>
  <text class="aav-fig-title aav-fig-mid" x="420" y="76">HOURS</text>
  <text class="aav-fig-title aav-fig-mid" x="590" y="76">DAYS</text>
  <text class="aav-fig-title aav-fig-mid" x="760" y="76">WEEKS</text>
  <text class="aav-fig-title aav-fig-mid" x="940" y="76">YEARS</text>
  <text class="aav-fig-note aav-fig-end" x="186" y="114">infusion</text>
  <g stroke="var(--aav-rule)" stroke-width="1.2" stroke-dasharray="3 5">
    <path d="M250 110 V 400 M420 110 V 400 M590 110 V 400 M760 110 V 400 M940 110 V 400"></path>
  </g>

  <!-- lane 1: complement -->
  <text class="aav-fig-lead aav-fig-end fill-complement" x="140" y="140">Complement</text>
  <rect x="192" y="126" width="286" height="22" rx="11" fill="var(--aav-complement)" fill-opacity="0.2" stroke="var(--aav-complement)" stroke-width="1.6"></rect>
  <text class="aav-fig-note" x="206" y="141">C3a, C5a, then C5b-9</text>
  <text class="aav-fig-note" x="490" y="141">falling platelets, haemolysis, renal impairment</text>

  <!-- lane 2: TLR2 -->
  <text class="aav-fig-lead aav-fig-end fill-innate" x="140" y="186">TLR2</text>
  <rect x="192" y="172" width="248" height="22" rx="11" fill="var(--aav-innate)" fill-opacity="0.24" stroke="var(--aav-innate)" stroke-width="1.6"></rect>
  <text class="aav-fig-note" x="206" y="187">reads the intact shell</text>
  <text class="aav-fig-note" x="452" y="187">fever, raised TNF-α and IL-6</text>

  <!-- lane 3: TLR9 -->
  <text class="aav-fig-lead aav-fig-end fill-innate" x="140" y="232">TLR9</text>
  <rect x="352" y="218" width="268" height="22" rx="11" fill="var(--aav-innate)" fill-opacity="0.24" stroke="var(--aav-innate)" stroke-width="1.6" stroke-dasharray="4 3"></rect>
  <text class="aav-fig-note" x="366" y="233">reads CpG once uncoating starts</text>
  <text class="aav-fig-note" x="632" y="233">raised type I interferon signature</text>

  <!-- lane 4: licensing -->
  <text class="aav-fig-lead aav-fig-end fill-innate" x="140" y="278">APC licensing</text>
  <rect x="520" y="264" width="230" height="22" rx="11" fill="var(--aav-innate)" fill-opacity="0.16" stroke="var(--aav-innate)" stroke-width="1.6" stroke-dasharray="4 3"></rect>
  <text class="aav-fig-note" x="534" y="279">interferon matures the presenting cells</text>
  <text class="aav-fig-note" x="762" y="279">nothing measured yet</text>

  <!-- lane 5: CD8 -->
  <text class="aav-fig-lead aav-fig-end fill-tcell" x="140" y="324">CD8⁺ T cells</text>
  <rect x="700" y="310" width="222" height="22" rx="11" fill="var(--aav-tcell)" fill-opacity="0.2" stroke="var(--aav-tcell)" stroke-width="1.6"></rect>
  <text class="aav-fig-note" x="714" y="325">expand, kill transduced cells</text>
  <text class="aav-fig-note" x="934" y="325">ALT and AST rise</text>

  <!-- lane 6: antibody, before and after -->
  <text class="aav-fig-lead aav-fig-end fill-antibody" x="140" y="370">Antibody</text>
  <rect x="152" y="356" width="902" height="22" rx="11" fill="var(--aav-antibody)" fill-opacity="0.22" stroke="var(--aav-antibody)" stroke-width="1.6"></rect>
  <text class="aav-fig-note fill-antibody" x="206" y="371">pre-existing in 30 to 60% of adults — acts before every other arm</text>
  <text class="aav-fig-note aav-fig-end fill-antibody" x="1042" y="371">second dose fails</text>
  <path d="M192 356 V 100" stroke="var(--aav-muted)" stroke-width="1.4" stroke-dasharray="4 4"></path>
  <text class="aav-fig-note" x="200" y="404">Pre-existing titre is the one number available before the infusion, which is why it is</text>
  <text class="aav-fig-note" x="200" y="420">an enrolment criterion. The other four arms only show themselves after the dose.</text>
</svg>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-timeline-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: Bar position is when an arm acts, not how strong it is. The readout on the right of each lane is what a clinician measures on that clock.
</figcaption>
</figure>
</div>
</section>
<section id="the-route" class="level2 page-columns page-full" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="the-route"><span class="header-section-number">4</span> The route</h2>
<p>Six barriers stand between the syringe and a working episome (Figure&nbsp;1). Each removes a fraction of the dose. The widget runs that attrition as an explicit chain: switch an arm on, and the particles it removes drop out at the station where that arm acts.</p>
<div class="column-page">
<div class="widget-container">
<div class="widget-header">
<p><span class="widget-title">From bloodstream to nucleus</span> <span class="widget-badge">runs in your browser</span></p>
</div>
<div id="aav-route">

</div>
<div class="widget-note">
Click a station to pin its explanation. The dots are vector genomes, scaled so the full panel is the injected dose.
</div>
</div>
</div>
<p>Things to try, and what each should show:</p>
<ul>
<li><strong>Turn everything off.</strong> About one genome in thirteen reaches a nucleus — 7.6% of the dose. Almost all of that loss is trafficking, not immunity. The endosome is the single most expensive step, with entry across the plasma membrane and the race against the proteasome close behind, and all three are expensive in a patient with no response to the vector at all.</li>
<li><strong>Turn on antibodies with the titre at 1:1000.</strong> The figure falls to 0.15%, about one genome in 660. Almost the whole dose goes before a particle is ever inside a cell: opsonised capsids cleared at the sinusoid, then neutralising antibody blocking entry at the cell surface. The inflammatory lamps stay dark, because the particles never got far enough to be sensed from inside.</li>
<li><strong>Turn on CD8<img src="https://latex.codecogs.com/png.latex?%5E+"> T cells alone.</strong> The nucleus count does not move at all. Expression at week 8 falls to 44 anyway. This arm does not stop delivery; it removes the cells that already received it.</li>
<li><strong>Turn on complement and drag the dose from <img src="https://latex.codecogs.com/png.latex?10%5E%7B13%7D"> to <img src="https://latex.codecogs.com/png.latex?10%5E%7B14%7D"> vg/kg.</strong> The share reaching a nucleus falls from 5.4% to 4.0% while the genomes delivered rise roughly sevenfold, and the complement lamp goes from amber to red. Both halves of the dose problem are in one picture.</li>
</ul>
<div class="aav-metrics">
<div class="aav-metric">
<p><span class="aav-metric-v">7.6%</span><span class="aav-metric-l">Reaches a nucleus</span><span class="aav-metric-h">No response running, 10<sup>13</sup> vg/kg</span></p>
</div>
<div class="aav-metric">
<p><span class="aav-metric-v">0.15%</span><span class="aav-metric-l">At a titre of 1:1000</span><span class="aav-metric-h">About one genome in 660</span></p>
</div>
<div class="aav-metric">
<p><span class="aav-metric-v">30–60%</span><span class="aav-metric-l">Of adults are seropositive</span><span class="aav-metric-h">From ordinary childhood exposure</span></p>
</div>
<div class="aav-metric">
<p><span class="aav-metric-v">4–8 wk</span><span class="aav-metric-l">ALT and AST rise</span><span class="aav-metric-h">When CD8<sup>+</sup> T cells find the cells</span></p>
</div>
</div>
<section id="model" class="level3" data-number="4.1">
<h3 data-number="4.1" class="anchored" data-anchor-id="model"><span class="header-section-number">4.1</span> Model</h3>
<div class="callout callout-style-simple callout-note">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p>The numbers are synthetic and illustrative. They are not fitted to any trial, and no patient produced them.</p>
</div>
</div>
</div>
<p>The generating process is a product of survival fractions, one per station:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AN_%7B%5Ctext%7Bnucleus%7D%7D%20=%20N_0%20%5Cprod_%7Bs=1%7D%5E%7B6%7D%20p_s(%5Ctext%7Bdose%7D,%5C%20%5Ctext%7Btitre%7D,%5C%20%5Ctext%7Barms%20on%7D).%0A"></p>
<p>Each <img src="https://latex.codecogs.com/png.latex?p_s"> is a baseline trafficking fraction multiplied by a penalty for whichever arms act at station <img src="https://latex.codecogs.com/png.latex?s">. The baselines are set so a seronegative patient with no active response lands at 7.6% of the dose reaching a nucleus, the right order for the few percent that reach target-cell nuclei in practice, and the dose and titre terms move in the direction the literature reports. Expression at week 8 multiplies the nucleus count by a T-cell survival term.</p>
<p>What it stands in for: a single intravenous, liver-directed dose in an adult. It is a teaching device for the <em>ordering and coupling</em> of the four arms — which one acts where, and what happens when two act together. It is not a predictor of any individual outcome.</p>
</section>
</section>
<section id="innate-sensing" class="level2 page-columns page-full" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="innate-sensing"><span class="header-section-number">5</span> Innate sensing</h2>
<div class="aav-eyebrow">
<p>Minutes to hours</p>
</div>
<p>Toll-like receptors are pattern receptors: each one binds a molecular shape that is common in pathogens and rare in the host, and converts binding into a signalling cascade. Two of them see AAV, at different places, and they drive different cytokines.</p>
<div id="fig-tlr" class="dia column-body-outset quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-tlr-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<svg class="aav-fig" viewbox="0 0 1000 460" shape-rendering="geometricPrecision" aria-label="Two panels. A: TLR2 sits in the plasma membrane of a Kupffer cell and binds the intact capsid, signalling through MyD88 to NF-kappa-B and the cytokines TNF-alpha, IL-6 and IL-1 beta. B: TLR9 sits in the endosomal membrane of a plasmacytoid dendritic cell facing the lumen, binds unmethylated CpG in the released single-stranded DNA, and signals through MyD88 to IRF7 and the type I interferons.">
  <defs>
    <marker id="aav-ah-tlr" viewbox="0 0 10 10" refx="9" refy="5" markerwidth="7" markerheight="7" orient="auto-start-reverse">
      <path d="M0,0 L10,5 L0,10 z" fill="var(--aav-muted)"></path>
    </marker>
  </defs>

  <!-- ============================ PANEL A ============================ -->
  <text class="aav-fig-letter" x="18" y="30">A</text>
  <text class="aav-fig-title" x="40" y="30">TLR2 — CELL SURFACE</text>

  <!-- blood above, cell below; the boundary between them is the membrane -->
  <rect x="16" y="46" width="468" height="108" rx="24" fill="var(--aav-blood)"></rect>
  <rect x="16" y="150" width="468" height="286" rx="22" fill="var(--aav-paper)" stroke="var(--aav-ink)" stroke-width="4"></rect>
  <text class="aav-fig-note" x="32" y="70">blood, sinusoid lumen</text>
  <text class="aav-fig-note aav-fig-end" x="468" y="142">plasma membrane</text>
  <text class="aav-fig-note" x="32" y="176">cytoplasm — Kupffer cell, endothelium</text>

  <!-- intact capsid, held by the receptor -->
  <g transform="translate(250 76)">
    <path d="M0,-24 L20.8,-12 L20.8,12 L0,24 L-20.8,12 L-20.8,-12 Z" fill="var(--aav-vector)"></path>
    <path d="M0,0 L0,-24 M0,0 L20.8,12 M0,0 L-20.8,12" stroke="#FFFFFF" stroke-opacity="0.42" stroke-width="1.8" fill="none"></path>
  </g>
  <text class="aav-fig-note" x="292" y="72">intact capsid</text>
  <text class="aav-fig-note" x="292" y="88">VP1 / VP2 / VP3</text>

  <!-- TLR2 ectodomain cupping the capsid; TIR stub inside the cell -->
  <path d="M236 152 C 224 128, 228 108, 242 102" fill="none" stroke="var(--aav-innate)" stroke-width="7" stroke-linecap="round"></path>
  <path d="M264 152 C 276 128, 272 108, 258 102" fill="none" stroke="var(--aav-innate)" stroke-width="7" stroke-linecap="round"></path>
  <rect x="234" y="148" width="32" height="16" rx="5" fill="var(--aav-innate)"></rect>
  <text class="aav-fig-lead aav-fig-end fill-innate" x="204" y="122">TLR2</text>
  <text class="aav-fig-note aav-fig-end" x="204" y="140">binds capsid protein</text>

  <path d="M250 168 V 190" stroke="var(--aav-muted)" stroke-width="2" marker-end="url(#aav-ah-tlr)"></path>

  <rect x="192" y="194" width="116" height="38" rx="8" fill="#FFFFFF" stroke="var(--aav-muted)" stroke-width="2"></rect>
  <text class="aav-fig-lead aav-fig-mid" x="250" y="218">MyD88</text>
  <text class="aav-fig-note" x="318" y="218">adaptor</text>

  <path d="M250 234 V 258" stroke="var(--aav-muted)" stroke-width="2" marker-end="url(#aav-ah-tlr)"></path>

  <rect x="192" y="262" width="116" height="38" rx="8" fill="#FFFFFF" stroke="var(--aav-ink)" stroke-width="2.5"></rect>
  <text class="aav-fig-lead aav-fig-mid" x="250" y="286">NF-κB</text>

  <path d="M250 302 V 326" stroke="var(--aav-muted)" stroke-width="2" marker-end="url(#aav-ah-tlr)"></path>

  <g class="aav-fig-cyto">
    <rect x="116" y="330" width="88" height="30" rx="15"></rect>
    <rect x="212" y="330" width="76" height="30" rx="15"></rect>
    <rect x="296" y="330" width="88" height="30" rx="15"></rect>
  </g>
  <text class="aav-fig-chip aav-fig-mid" x="160" y="350">TNF-α</text>
  <text class="aav-fig-chip aav-fig-mid" x="250" y="350">IL-6</text>
  <text class="aav-fig-chip aav-fig-mid" x="340" y="350">IL-1β</text>
  <text class="aav-fig-note aav-fig-mid" x="250" y="388">inflammatory alarm, raised by the</text>
  <text class="aav-fig-note aav-fig-mid" x="250" y="404">outside of the particle</text>

  <!-- ============================ divider ============================ -->
  <path d="M500 22 V 436" stroke="var(--aav-rule)" stroke-width="1.5"></path>

  <!-- ============================ PANEL B ============================ -->
  <text class="aav-fig-letter" x="522" y="30">B</text>
  <text class="aav-fig-title" x="544" y="30">TLR9 — ENDOSOME</text>

  <rect x="518" y="46" width="468" height="390" rx="26" fill="var(--aav-paper)" stroke="var(--aav-ink)" stroke-width="4"></rect>
  <text class="aav-fig-note" x="536" y="70">plasmacytoid dendritic cell</text>

  <!-- endosome -->
  <circle cx="672" cy="152" r="76" fill="#FFFFFF" stroke="var(--aav-ink)" stroke-width="4"></circle>
  <text class="aav-fig-note aav-fig-mid" x="672" y="102">endosome lumen</text>

  <!-- uncoating capsid: same hexagon, stroked not filled -->
  <g transform="translate(634 154) scale(0.78)">
    <path d="M0,-24 L20.8,-12 L20.8,12 L0,24 L-20.8,12 L-20.8,-12" fill="none" stroke="var(--aav-vector)" stroke-width="3.2" stroke-linejoin="round"></path>
  </g>

  <!-- released ssDNA carrying unmethylated CpG -->
  <path d="M654 154 q 9 -13 18 0 t 18 0 t 18 0" fill="none" stroke="var(--aav-vector)" stroke-opacity="0.75" stroke-width="2.5" stroke-linecap="round"></path>
  <g fill="var(--aav-innate)">
    <circle cx="663" cy="148" r="3.4"></circle>
    <circle cx="681" cy="148" r="3.4"></circle>
    <circle cx="699" cy="148" r="3.4"></circle>
  </g>
  <text class="aav-fig-note aav-fig-mid" x="636" y="250">capsid opens, ssDNA released</text>
  <text class="aav-fig-note aav-fig-mid fill-innate" x="636" y="266">unmethylated CpG</text>

  <!-- TLR9: ectodomain in the lumen, TIR stub in the cytosol -->
  <path d="M746 140 C 726 132, 714 144, 710 152" fill="none" stroke="var(--aav-innate)" stroke-width="7" stroke-linecap="round"></path>
  <path d="M746 164 C 726 172, 714 160, 710 152" fill="none" stroke="var(--aav-innate)" stroke-width="7" stroke-linecap="round"></path>
  <rect x="744" y="136" width="16" height="32" rx="5" fill="var(--aav-innate)"></rect>
  <text class="aav-fig-lead fill-innate" x="772" y="148">TLR9</text>
  <text class="aav-fig-note" x="772" y="166">faces the lumen</text>

  <path d="M752 172 C 752 214, 752 226, 752 264" stroke="var(--aav-muted)" stroke-width="2" fill="none" marker-end="url(#aav-ah-tlr)"></path>

  <rect x="694" y="268" width="116" height="38" rx="8" fill="#FFFFFF" stroke="var(--aav-muted)" stroke-width="2"></rect>
  <text class="aav-fig-lead aav-fig-mid" x="752" y="292">MyD88</text>

  <path d="M752 310 V 332" stroke="var(--aav-muted)" stroke-width="2" marker-end="url(#aav-ah-tlr)"></path>

  <rect x="694" y="336" width="116" height="38" rx="8" fill="#FFFFFF" stroke="var(--aav-ink)" stroke-width="2.5"></rect>
  <text class="aav-fig-lead aav-fig-mid" x="752" y="360">IRF7</text>

  <path d="M752 376 V 394" stroke="var(--aav-muted)" stroke-width="2" marker-end="url(#aav-ah-tlr)"></path>

  <g class="aav-fig-cyto">
    <rect x="658" y="398" width="88" height="30" rx="15"></rect>
    <rect x="754" y="398" width="88" height="30" rx="15"></rect>
  </g>
  <text class="aav-fig-chip aav-fig-mid" x="702" y="418">IFN-α</text>
  <text class="aav-fig-chip aav-fig-mid" x="798" y="418">IFN-β</text>
</svg>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-tlr-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;4: <strong>A</strong> TLR2 reads the intact shell at the plasma membrane of a Kupffer cell. <strong>B</strong> TLR9 reads the released DNA inside the endosome of a plasmacytoid dendritic cell. Both route through MyD88 and split at the transcription factor.
</figcaption>
</figure>
</div>
<section id="tlr2" class="level3" data-number="5.1">
<h3 data-number="5.1" class="anchored" data-anchor-id="tlr2"><span class="header-section-number">5.1</span> TLR2</h3>
<p>TLR2 sits on the cell surface, and on the endosomal membrane, and binds the structural capsid proteins of the intact particle. Human endothelial cells and Kupffer cells — the resident macrophages lining the liver’s blood channels — carry it, which puts it exactly where an intravenous dose goes first.</p>
<p>Binding recruits MyD88, an adaptor protein, which activates the transcription factor NF-<img src="https://latex.codecogs.com/png.latex?%5Ckappa">B. NF-<img src="https://latex.codecogs.com/png.latex?%5Ckappa">B turns on the pro-inflammatory cytokine genes: TNF-<img src="https://latex.codecogs.com/png.latex?%5Calpha">, IL-6 and IL-1<img src="https://latex.codecogs.com/png.latex?%5Cbeta">. The consequence is a general inflammatory alarm, raised by the outside of the particle, with no reference to what the particle carries.</p>
</section>
<section id="tlr9" class="level3" data-number="5.2">
<h3 data-number="5.2" class="anchored" data-anchor-id="tlr9"><span class="header-section-number">5.2</span> TLR9</h3>
<p>TLR9 is inside the endosome, facing the compartment’s interior, and it binds unmethylated CpG dinucleotides in single-stranded DNA. That is a bacterial and viral signature: the mammalian genome depletes CpG and methylates most of what is left, so unmethylated CpG in an endosome means something foreign has begun to open.</p>
<ul>
<li>The capsid must start uncoating for the DNA to become visible. TLR9 sensing therefore lags TLR2 sensing, and it reports on particles that got at least as far as an endosome.</li>
<li>Signalling goes through MyD88 again, but in plasmacytoid dendritic cells it branches to IRF7 and drives type I interferons, IFN-<img src="https://latex.codecogs.com/png.latex?%5Calpha"> and IFN-<img src="https://latex.codecogs.com/png.latex?%5Cbeta">, at high levels.</li>
<li>Interferon does two things at once. It suppresses transgene expression directly, and it matures the antigen-presenting cells that go on to prime the T-cell response. The innate arm is what licenses the adaptive one.</li>
</ul>
<p>CpG content is a manufacturing variable, not a fixed property of the vector. Codon choices and regulatory sequences can be redesigned to strip CpG motifs out of the expression cassette without changing the protein, and CpG-depleted vectors are one of the levers the field pulls on this response.</p>
</section>
</section>
<section id="antibodies" class="level2 page-columns page-full" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="antibodies"><span class="header-section-number">6</span> Antibodies</h2>
<div class="aav-eyebrow">
<p>Any time, and for years afterwards</p>
</div>
<p>B cells make antibodies against the capsid — often long before the therapy, from ordinary exposure to wild AAV. They act on the particle while it is still outside a cell, and they split into two functionally different classes.</p>
<div id="fig-humoral" class="dia column-body-outset quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-humoral-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<svg class="aav-fig" viewbox="0 0 1000 400" shape-rendering="geometricPrecision" aria-label="Two panels. A: a neutralising antibody binds the capsid over the receptor footprint, so the particle stays intact but cannot dock with AAVR and does not enter the hepatocyte. B: a binding antibody attaches away from the receptor site and leaves its Fc tail exposed; an Fc gamma receptor on a Kupffer cell grips it and the particle is pulled in and degraded.">
  <defs>
    <marker id="aav-ah-hum" viewbox="0 0 10 10" refx="9" refy="5" markerwidth="7" markerheight="7" orient="auto-start-reverse">
      <path d="M0,0 L10,5 L0,10 z" fill="var(--aav-muted)"></path>
    </marker>
  </defs>

  <!-- ============================ PANEL A ============================ -->
  <text class="aav-fig-letter" x="18" y="30">A</text>
  <text class="aav-fig-title" x="40" y="30">NEUTRALISING — STERIC BLOCK</text>

  <rect x="16" y="46" width="468" height="232" rx="24" fill="var(--aav-blood)"></rect>
  <rect x="16" y="272" width="468" height="120" rx="22" fill="var(--aav-paper)" stroke="var(--aav-ink)" stroke-width="4"></rect>
  <text class="aav-fig-note" x="32" y="70">plasma</text>
  <text class="aav-fig-note" x="32" y="298">hepatocyte — nothing arrives</text>

  <!-- capsid, intact -->
  <g transform="translate(250 118)">
    <path d="M0,-30 L26,-15 L26,15 L0,30 L-26,15 L-26,-15 Z" fill="var(--aav-vector)"></path>
    <path d="M0,0 L0,-30 M0,0 L26,15 M0,0 L-26,15" stroke="#FFFFFF" stroke-opacity="0.42" stroke-width="1.8" fill="none"></path>
  </g>
  <text class="aav-fig-note" x="292" y="112">intact capsid</text>
  <text class="aav-fig-note" x="292" y="128">undamaged, just stuck</text>

  <!-- neutralising IgG: Fab arms on the capsid, sitting over the receptor site -->
  <g stroke="var(--aav-antibody)" stroke-width="6" stroke-linecap="round" fill="none">
    <path d="M250 180 L228 154 M250 180 L272 154 M250 180 V 212"></path>
  </g>
  <text class="aav-fig-note fill-antibody" x="292" y="180">neutralising IgG</text>
  <text class="aav-fig-note" x="292" y="196">covers the receptor footprint</text>

  <!-- the block -->
  <path d="M214 230 H286" stroke="var(--aav-alarm)" stroke-width="5" stroke-linecap="round"></path>
  <text class="aav-fig-note fill-alarm" x="294" y="234">cannot dock</text>

  <!-- AAVR and a glycan on the membrane -->
  <path d="M250 272 V 244" stroke="var(--aav-antibody)" stroke-width="7" stroke-linecap="round"></path>
  <g fill="var(--aav-antibody)">
    <circle cx="250" cy="266" r="6"></circle><circle cx="250" cy="254" r="6"></circle><circle cx="250" cy="244" r="6"></circle>
  </g>
  <text class="aav-fig-note aav-fig-end fill-antibody" x="230" y="256">AAVR</text>
  <g stroke="var(--aav-muted)" stroke-width="3" stroke-linecap="round" fill="none">
    <path d="M150 272 V 256 M150 256 L140 246 M150 256 L160 246"></path>
  </g>
  <text class="aav-fig-note aav-fig-mid" x="150" y="238">glycan</text>

  <!-- ============================ divider ============================ -->
  <path d="M500 22 V 386" stroke="var(--aav-rule)" stroke-width="1.5"></path>

  <!-- ============================ PANEL B ============================ -->
  <text class="aav-fig-letter" x="516" y="30">B</text>
  <text class="aav-fig-title" x="538" y="30">BINDING — OPSONISATION</text>

  <rect x="516" y="46" width="468" height="346" rx="24" fill="var(--aav-blood)"></rect>
  <text class="aav-fig-note" x="532" y="70">plasma</text>

  <!-- Kupffer cell -->
  <path d="M750 120 q30 -34 84 -30 q70 6 96 60 q24 52 -6 108 q-34 62 -104 56 q-72 -6 -92 -70
           q-18 -60 22 -124 Z" fill="var(--aav-paper)" stroke="var(--aav-ink)" stroke-width="4"></path>
  <text class="aav-fig-note aav-fig-mid" x="828" y="126">Kupffer cell</text>

  <!-- capsid with a binding antibody, Fc pointing at the receptor -->
  <g transform="translate(596 176)">
    <path d="M0,-30 L26,-15 L26,15 L0,30 L-26,15 L-26,-15 Z" fill="var(--aav-vector)"></path>
    <path d="M0,0 L0,-30 M0,0 L26,15 M0,0 L-26,15" stroke="#FFFFFF" stroke-opacity="0.42" stroke-width="1.8" fill="none"></path>
  </g>
  <g stroke="var(--aav-antibody)" stroke-width="6" stroke-linecap="round" fill="none">
    <path d="M652 176 L626 162 M652 176 L626 190 M652 176 H718"></path>
  </g>
  <text class="aav-fig-note aav-fig-mid" x="596" y="228">receptor site still free</text>
  <text class="aav-fig-note aav-fig-mid fill-antibody" x="596" y="244">binding IgG, Fc exposed</text>

  <!-- Fc gamma receptor gripping the Fc tail -->
  <g stroke="var(--aav-antibody)" stroke-width="6" stroke-linecap="round" fill="none">
    <path d="M754 150 C 740 158, 732 170, 728 176 M754 202 C 740 194, 732 182, 728 176"></path>
  </g>
  <text class="aav-fig-note fill-antibody" x="762" y="158">FcγR</text>

  <!-- pulled in and degraded -->
  <path d="M766 192 C 792 202, 810 210, 820 216" fill="none" stroke="var(--aav-muted)" stroke-width="2" marker-end="url(#aav-ah-hum)"></path>
  <circle cx="856" cy="228" r="42" fill="#FFFFFF" stroke="var(--aav-ink)" stroke-width="3.5"></circle>
  <g fill="var(--aav-vector)">
    <path d="M840 214 l14 6 l-4 12 Z"></path><path d="M866 220 l13 8 l-11 8 Z"></path><path d="M844 238 l14 4 l-7 11 Z"></path>
  </g>
  <text class="aav-fig-note aav-fig-mid" x="856" y="288">capsid degraded</text>
  <text class="aav-fig-note" x="532" y="352">Coating a particle marks it for eating: the dose leaves circulation faster, and</text>
  <text class="aav-fig-note" x="532" y="368">the liver does most of the clearing. A neutralisation assay does not see it.</text>
</svg>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-humoral-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;5: <strong>A</strong> Neutralising antibody covers the receptor footprint, so an undamaged particle cannot dock. <strong>B</strong> Binding antibody attaches elsewhere and leaves the Fc tail out for a Kupffer cell to grip. Either way the particle never enters a hepatocyte, and complexes like these also start the classical complement pathway.
</figcaption>
</figure>
</div>
<section id="neutralising-antibodies" class="level3" data-number="6.1">
<h3 data-number="6.1" class="anchored" data-anchor-id="neutralising-antibodies"><span class="header-section-number">6.1</span> Neutralising antibodies</h3>
<p>Neutralising antibodies bind the outer surface of the capsid at or near the sites the particle uses to attach. The blocking is steric — the antibody is physically in the way of the glycan the capsid binds and of AAVR, the receptor that carries it into the cell. The particle is intact and undamaged and simply cannot dock.</p>
<ul>
<li>Neutralisation is close to all-or-nothing at the level of a single particle, so titre translates into dose loss steeply.</li>
<li>Titres are reported as the reciprocal dilution at which neutralisation is still measurable, so 1:1000 is a high titre and 1:5 is a low one. Many trials exclude patients above a threshold as low as 1:5, because a small amount of circulating antibody removes a large fraction of the dose.</li>
<li>This is the reason a second dose usually fails. The first dose raises a durable neutralising response against the capsid it was delivered in, and re-dosing with the same serotype years later meets a titre far above any enrolment threshold.</li>
</ul>
</section>
<section id="binding-antibodies" class="level3" data-number="6.2">
<h3 data-number="6.2" class="anchored" data-anchor-id="binding-antibodies"><span class="header-section-number">6.2</span> Binding antibodies</h3>
<p>Binding antibodies attach to the capsid without covering a receptor site. The particle could still dock, but it does not get the chance: the antibody’s Fc tail is now sticking out, and Fc<img src="https://latex.codecogs.com/png.latex?%5Cgamma"> receptors on macrophages and Kupffer cells grip it.</p>
<p>The effect is opsonisation — coating a particle to mark it for eating. A capsid tagged this way is pulled into a phagocyte and degraded. The dose is cleared from circulation faster, and the liver’s resident macrophages do most of that clearing, which is one reason an intravenous dose is a liver problem before it is anything else.</p>
<p>An assay that only measures neutralisation misses this entirely. A patient can be seronegative by a neutralisation assay and still clear a dose quickly through binding antibodies alone. Total-antibody assays are run alongside for that reason.</p>
</section>
</section>
<section id="capsid-specific-t-cells" class="level2 page-columns page-full" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="capsid-specific-t-cells"><span class="header-section-number">7</span> Capsid-specific T cells</h2>
<div class="aav-eyebrow">
<p>Four to eight weeks</p>
</div>
<p>The first three arms act on the particle. This one acts on the patient’s own cells, and it is the arm that takes away expression that was already working.</p>
<div id="fig-ctl" class="dia column-body-outset quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-ctl-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<svg class="aav-fig" viewbox="0 0 1000 400" shape-rendering="geometricPrecision" aria-label="Inside a transduced hepatocyte, a capsid that never reached the nucleus is tagged with ubiquitin and fed to the proteasome, which cuts it into peptides. The peptides are loaded onto MHC class I and displayed on the cell surface, where a capsid-specific CD8 T cell recognises the complex and releases perforin and granzyme, killing the cell and taking its expression with it.">
  <defs>
    <marker id="aav-ah-ctl" viewbox="0 0 10 10" refx="9" refy="5" markerwidth="7" markerheight="7" orient="auto-start-reverse">
      <path d="M0,0 L10,5 L0,10 z" fill="var(--aav-muted)"></path>
    </marker>
    <marker id="aav-ah-ctl-kill" viewbox="0 0 10 10" refx="9" refy="5" markerwidth="7" markerheight="7" orient="auto-start-reverse">
      <path d="M0,0 L10,5 L0,10 z" fill="var(--aav-tcell)"></path>
    </marker>
  </defs>

  <!-- transduced hepatocyte; its right edge is the plasma membrane -->
  <rect x="16" y="70" width="620" height="310" rx="24" fill="var(--aav-paper)" stroke="var(--aav-ink)" stroke-width="4"></rect>
  <text class="aav-fig-note" x="36" y="96">transduced hepatocyte</text>

  <!-- the nucleus the capsid never reached -->
  <ellipse cx="112" cy="150" rx="62" ry="42" fill="var(--aav-nucleoplasm)" stroke="var(--aav-muted)" stroke-width="2"></ellipse>
  <text class="aav-fig-note aav-fig-mid" x="112" y="155">nucleus</text>
  <g transform="translate(112 268)">
    <path d="M0,-24 L20.8,-12 L20.8,12 L0,24 L-20.8,12 L-20.8,-12 Z" fill="var(--aav-vector)"></path>
  </g>
  <path d="M112 240 V 206" stroke="var(--aav-traffic)" stroke-width="2" stroke-dasharray="5 4" fill="none" marker-end="url(#aav-ah-ctl)"></path>
  <path d="M88 224 H136" stroke="var(--aav-alarm)" stroke-width="4.5" stroke-linecap="round"></path>
  <text class="aav-fig-note aav-fig-mid" x="112" y="312">never arrives — most do not</text>

  <!-- ubiquitin tag -->
  <g fill="var(--aav-traffic)">
    <circle cx="140" cy="248" r="5.5"></circle><circle cx="152" cy="256" r="5.5"></circle><circle cx="162" cy="266" r="5.5"></circle>
  </g>
  <text class="aav-fig-note" x="146" y="238">ubiquitin</text>

  <!-- proteasome -->
  <g fill="#FFFFFF" stroke="var(--aav-muted)" stroke-width="2">
    <rect x="232" y="238" width="90" height="14" rx="7"></rect>
    <rect x="232" y="255" width="90" height="14" rx="7"></rect>
    <rect x="232" y="272" width="90" height="14" rx="7"></rect>
    <rect x="232" y="289" width="90" height="14" rx="7"></rect>
  </g>
  <path d="M277 236 V 305" stroke="var(--aav-rule)" stroke-width="2"></path>
  <text class="aav-fig-lead aav-fig-mid" x="277" y="226">proteasome</text>
  <path d="M180 268 H226" stroke="var(--aav-muted)" stroke-width="2" marker-end="url(#aav-ah-ctl)"></path>

  <!-- peptides -->
  <g fill="var(--aav-vector)">
    <rect x="360" y="244" width="15" height="9" rx="2"></rect>
    <rect x="384" y="258" width="15" height="9" rx="2"></rect>
    <rect x="358" y="274" width="15" height="9" rx="2"></rect>
    <rect x="386" y="288" width="15" height="9" rx="2"></rect>
  </g>
  <text class="aav-fig-note aav-fig-mid" x="380" y="322">capsid peptides</text>
  <path d="M328 268 H352" stroke="var(--aav-muted)" stroke-width="2" marker-end="url(#aav-ah-ctl)"></path>

  <!-- MHC class I on the membrane, groove facing out -->
  <path d="M414 262 C 500 244, 556 238, 626 246" fill="none" stroke="var(--aav-muted)" stroke-width="2" marker-end="url(#aav-ah-ctl)"></path>
  <path d="M636 250 L690 232 M636 250 L690 268" stroke="var(--aav-antibody)" stroke-width="5" stroke-linecap="round" fill="none"></path>
  <rect x="678" y="242" width="16" height="9" rx="2" fill="var(--aav-vector)"></rect>
  <text class="aav-fig-lead fill-antibody" x="638" y="304">MHC class I</text>

  <!-- CD8 T cell with its receptor engaging the complex -->
  <circle cx="800" cy="250" r="84" fill="#FFFFFF" stroke="var(--aav-tcell)" stroke-width="3.5"></circle>
  <text class="aav-fig-lead aav-fig-mid fill-tcell" x="806" y="246">CD8⁺</text>
  <text class="aav-fig-note aav-fig-mid fill-tcell" x="806" y="266">T cell</text>
  <path d="M718 228 H698 V 272 H718" fill="none" stroke="var(--aav-tcell)" stroke-width="4"></path>
  <path d="M708 172 V 224" fill="none" stroke="var(--aav-tcell)" stroke-width="1.4" stroke-dasharray="3 3"></path>
  <text class="aav-fig-note fill-tcell" x="706" y="164">T-cell receptor</text>

  <!-- the kill -->
  <path d="M744 320 C 700 352, 640 362, 590 356" fill="none" stroke="var(--aav-tcell)" stroke-width="2" stroke-dasharray="5 4" marker-end="url(#aav-ah-ctl-kill)"></path>
  <text class="aav-fig-lead fill-tcell" x="756" y="352">perforin, granzyme</text>
  <text class="aav-fig-lead aav-fig-mid" x="330" y="360">apoptosis — expression lost</text>

  <!-- when this happens -->
  <rect x="700" y="58" width="284" height="60" rx="10" fill="var(--aav-paper)" stroke="var(--aav-rule)" stroke-width="1.5"></rect>
  <text class="aav-fig-lead" x="718" y="82">4 to 8 weeks after infusion</text>
  <text class="aav-fig-note" x="718" y="100">ALT and AST rise as expression falls</text>
</svg>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-ctl-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;6: A capsid that never reached the nucleus becomes the antigen that destroys the cell that took it up.
</figcaption>
</figure>
</div>
<p>The chain has four links, and the first one is a trafficking failure rather than an immune event:</p>
<ol type="1">
<li><strong>A capsid fails to reach the nucleus.</strong> Most do. It is tagged with ubiquitin and fed to the proteasome, the cell’s general-purpose protein shredder.</li>
<li><strong>The proteasome cuts it into short peptides.</strong> These are the ordinary raw material of the class I presentation pathway — every cell continuously advertises samples of its own protein contents this way.</li>
<li><strong>Peptides are loaded onto MHC class I and displayed</strong> on the cell surface. A hepatocyte that took up a vector is now showing capsid fragments to anything that walks past.</li>
<li><strong>A capsid-specific CD8<img src="https://latex.codecogs.com/png.latex?%5E+"> T cell recognises the complex</strong> and releases perforin, which opens the target membrane, and granzymes, which enter and trigger apoptosis.</li>
</ol>
<p>The observable signature is specific and well documented: a rise in the liver enzymes ALT and AST at roughly four to eight weeks after infusion, coinciding with a fall in transgene expression. It looks like liver toxicity and it is really the immune system finding cells that took up the dose. Prophylactic corticosteroids, started before the enzymes rise, are the standard countermeasure, and their timing is set by that four-to-eight-week window.</p>
<p>Two points are easy to get backwards.</p>
<ul>
<li><strong>The antigen is usually the capsid, not the transgene.</strong> The capsid arrived by the thousand per cell and is presented immediately; the transgene product is a protein the patient may already be tolerant to. A transgene-directed response does happen, and it matters most when the patient’s own gene is deleted, so the therapeutic protein is foreign to them in the way a viral protein is.</li>
<li><strong>The dose is already delivered when this arm acts.</strong> Steroids cannot recover killed cells. They prevent the killing of cells that are still expressing.</li>
</ul>
</section>
<section id="complement" class="level2 page-columns page-full" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="complement"><span class="header-section-number">8</span> Complement</h2>
<div class="aav-eyebrow">
<p>Minutes</p>
</div>
<p>Complement is a cascade of plasma proteins that amplifies: a small trigger cleaves a protein, the fragments cleave more, and the output climbs steeply with input. Both the classical pathway, triggered by antibody bound to a capsid, and the alternative pathway, which turns over continuously and settles on surfaces, activate on AAV.</p>
<div id="fig-complement" class="dia column-body-outset quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-complement-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<svg class="aav-fig" viewbox="0 0 1000 380" shape-rendering="geometricPrecision" aria-label="The complement cascade on AAV. Both the classical pathway, triggered by antibody-capsid complexes, and the alternative pathway converge on C3. C3 is cleaved into the soluble anaphylatoxin C3a and the surface-bound C3b; C3b drives C5, which splits into C5a and C5b-9, the membrane attack complex. C5b-9 opens pores in vascular endothelium.">
  <defs>
    <marker id="aav-ah-c3" viewbox="0 0 10 10" refx="9" refy="5" markerwidth="7" markerheight="7" orient="auto-start-reverse">
      <path d="M0,0 L10,5 L0,10 z" fill="var(--aav-muted)"></path>
    </marker>
  </defs>

  <!-- the two triggers -->
  <rect x="20" y="132" width="196" height="44" rx="9" fill="#FFFFFF" stroke="var(--aav-rule)" stroke-width="1.8"></rect>
  <text class="aav-fig-lead fill-complement" x="34" y="152">classical</text>
  <text class="aav-fig-note" x="34" y="168">antibody bound to a capsid</text>
  <rect x="20" y="188" width="196" height="44" rx="9" fill="#FFFFFF" stroke="var(--aav-rule)" stroke-width="1.8"></rect>
  <text class="aav-fig-lead fill-complement" x="34" y="208">alternative</text>
  <text class="aav-fig-note" x="34" y="224">continuous turnover on surfaces</text>
  <path d="M216 154 H234 V 182 H248" fill="none" stroke="var(--aav-muted)" stroke-width="2"></path>
  <path d="M216 210 H234 V 182 H248" fill="none" stroke="var(--aav-muted)" stroke-width="2" marker-end="url(#aav-ah-c3)"></path>

  <!-- C3 and its split -->
  <rect x="256" y="156" width="104" height="52" rx="10" fill="#FFFFFF" stroke="var(--aav-complement)" stroke-width="3"></rect>
  <text class="aav-fig-mid fill-complement" x="308" y="190" font-size="20" font-weight="700">C3</text>

  <path d="M360 172 H382 V 78 H396" fill="none" stroke="var(--aav-muted)" stroke-width="2" marker-end="url(#aav-ah-c3)"></path>
  <path d="M360 192 H396" fill="none" stroke="var(--aav-muted)" stroke-width="2" marker-end="url(#aav-ah-c3)"></path>

  <rect x="400" y="58" width="110" height="40" rx="20" fill="#FDF0E4" stroke="var(--aav-alarm)" stroke-width="2"></rect>
  <text class="aav-fig-mid fill-alarm" x="455" y="84" font-size="16" font-weight="700">C3a</text>
  <rect x="400" y="172" width="110" height="40" rx="20" fill="#FFFFFF" stroke="var(--aav-complement)" stroke-width="2"></rect>
  <text class="aav-fig-mid fill-complement" x="455" y="198" font-size="16" font-weight="700">C3b</text>

  <!-- C5 and its split -->
  <path d="M510 192 H540" fill="none" stroke="var(--aav-muted)" stroke-width="2" marker-end="url(#aav-ah-c3)"></path>
  <rect x="544" y="170" width="96" height="44" rx="10" fill="#FFFFFF" stroke="var(--aav-complement)" stroke-width="2.5"></rect>
  <text class="aav-fig-mid fill-complement" x="592" y="200" font-size="18" font-weight="700">C5</text>

  <path d="M640 182 H662 V 78 H682" fill="none" stroke="var(--aav-muted)" stroke-width="2" marker-end="url(#aav-ah-c3)"></path>
  <path d="M640 202 H682" fill="none" stroke="var(--aav-muted)" stroke-width="2" marker-end="url(#aav-ah-c3)"></path>

  <rect x="686" y="58" width="110" height="40" rx="20" fill="#FDF0E4" stroke="var(--aav-alarm)" stroke-width="2"></rect>
  <text class="aav-fig-mid fill-alarm" x="741" y="84" font-size="16" font-weight="700">C5a</text>
  <rect x="686" y="182" width="180" height="44" rx="10" fill="#FFFFFF" stroke="var(--aav-complement)" stroke-width="3"></rect>
  <text class="aav-fig-mid fill-complement" x="776" y="212" font-size="16" font-weight="700">C5b-9 (MAC)</text>

  <!-- the soluble branch -->
  <text class="aav-fig-title aav-fig-mid fill-alarm" x="570" y="38">SOLUBLE ALARM — ANAPHYLATOXINS</text>
  <text class="aav-fig-note aav-fig-mid" x="516" y="122">neutrophils and mast cells recruited;</text>
  <text class="aav-fig-note aav-fig-mid" x="516" y="138">systemic inflammation within hours</text>

  <!-- the membrane branch -->
  <path d="M776 230 V 268" fill="none" stroke="var(--aav-muted)" stroke-width="2" marker-end="url(#aav-ah-c3)"></path>
  <path d="M120 296 H940" stroke="var(--aav-ink)" stroke-width="6" stroke-linecap="round"></path>
  <g stroke="var(--aav-complement)" stroke-width="4.5" stroke-linecap="round">
    <path d="M756 284 v24 M776 284 v24 M796 284 v24"></path>
  </g>
  <text class="aav-fig-note" x="136" y="328">vascular endothelium</text>
  <text class="aav-fig-lead aav-fig-end fill-complement" x="940" y="328">pores, thrombosis, platelet loss</text>
  <text class="aav-fig-note" x="136" y="360">The cascade amplifies, so what it produces climbs faster than the dose that starts it.</text>
</svg>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-complement-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;7: Every step splits into a soluble alarm signal and a surface-bound fragment. Only the second branch reaches a membrane.
</figcaption>
</figure>
</div>
<p>The cascade produces two kinds of output, and they cause different problems.</p>
<ul>
<li><strong>Anaphylatoxins.</strong> Cleaving C3 and C5 releases the small fragments C3a and C5a into the plasma. These recruit and activate neutrophils and mast cells and drive systemic inflammation. This is the fast, whole-body reaction, and it is the one that follows an infusion within hours.</li>
<li><strong>The membrane attack complex.</strong> The larger fragments assemble C5b-9, a ring that inserts into a lipid membrane and opens a pore. On vascular endothelial cells, that damage exposes the surfaces that start clotting, and the clinical consequence is microvascular thrombosis with falling platelets — the picture reported as thrombotic microangiopathy after high-dose systemic AAV.</li>
</ul>
<p>Complement is the arm most tightly coupled to dose, for two reasons that compound. The cascade is an amplifier, so it responds non-linearly to how much surface is presented to it; and circulating antibody-capsid complexes trigger the classical pathway, so a patient with any pre-existing titre starts the cascade with a much lower threshold. Dose and serostatus are not independent risks.</p>
</section>
<section id="dose" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="dose"><span class="header-section-number">9</span> Dose</h2>
<p>Every arm is dose-sensitive, and none is dose-sensitive in the same way.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th style="text-align: left;">Arm</th>
<th style="text-align: left;">What rises with dose</th>
<th style="text-align: left;">Shape</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: left;">Complement</td>
<td style="text-align: left;">Surface presented to an amplifying cascade</td>
<td style="text-align: left;">Steeply non-linear</td>
</tr>
<tr class="even">
<td style="text-align: left;">TLR2, TLR9</td>
<td style="text-align: left;">Particles sensed per cell</td>
<td style="text-align: left;">Roughly proportional</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Antibody clearance</td>
<td style="text-align: left;">Saturates once titre is exhausted</td>
<td style="text-align: left;">Flattens at high dose</td>
</tr>
<tr class="even">
<td style="text-align: left;">CD8<img src="https://latex.codecogs.com/png.latex?%5E+"> T cells</td>
<td style="text-align: left;">Capsid peptides displayed per cell</td>
<td style="text-align: left;">Proportional, then a threshold for killing</td>
</tr>
</tbody>
</table>
<p>The trap is that the reason to raise the dose and the reason not to are the same number. Delivery is inefficient, so a therapeutic effect needs a large dose; a large dose is what turns the amplifying arms on. Systemic doses in the <img src="https://latex.codecogs.com/png.latex?10%5E%7B13%7D"> to <img src="https://latex.codecogs.com/png.latex?10%5E%7B14%7D"> vg/kg range are where the severe complement and liver events have been reported, and they are also where enough genomes arrive for some indications to work at all.</p>
</section>
<section id="clinical-signatures" class="level2" data-number="10">
<h2 data-number="10" class="anchored" data-anchor-id="clinical-signatures"><span class="header-section-number">10</span> Clinical signatures</h2>
<p>Each arm shows up as something a clinician measures, on its own clock. Reading backwards from the observation to the mechanism is most of how these events are managed.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th style="text-align: left;">Observation</th>
<th style="text-align: left;">Timing</th>
<th style="text-align: left;">Arm</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: left;">Fever, raised IL-6 and TNF-<img src="https://latex.codecogs.com/png.latex?%5Calpha"></td>
<td style="text-align: left;">Hours</td>
<td style="text-align: left;">TLR2, innate</td>
</tr>
<tr class="even">
<td style="text-align: left;">Raised type I interferon signature</td>
<td style="text-align: left;">Hours to days</td>
<td style="text-align: left;">TLR9, innate</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Falling platelets, haemolysis, renal impairment</td>
<td style="text-align: left;">Hours to days</td>
<td style="text-align: left;">Complement, MAC</td>
</tr>
<tr class="even">
<td style="text-align: left;">No expression at all despite a full dose</td>
<td style="text-align: left;">Immediate</td>
<td style="text-align: left;">Pre-existing neutralising antibody</td>
</tr>
<tr class="odd">
<td style="text-align: left;">ALT and AST rise with falling expression</td>
<td style="text-align: left;">4 to 8 weeks</td>
<td style="text-align: left;">Capsid-specific CD8<img src="https://latex.codecogs.com/png.latex?%5E+"> T cells</td>
</tr>
<tr class="even">
<td style="text-align: left;">Second dose has no effect</td>
<td style="text-align: left;">Years</td>
<td style="text-align: left;">Post-dose neutralising antibody</td>
</tr>
</tbody>
</table>
<p>The countermeasures line up with the same table: pre-screening for neutralising titre before enrolment, CpG depletion in cassette design, prophylactic corticosteroids over the T-cell window, and complement inhibition where the cascade is the presenting problem.</p>
</section>
<section id="constraints" class="level2" data-number="11">
<h2 data-number="11" class="anchored" data-anchor-id="constraints"><span class="header-section-number">11</span> Constraints</h2>
<ul>
<li><strong>The four arms are not independent.</strong> Innate signalling licenses the adaptive response, antibody-capsid complexes trigger complement, and complement fragments opsonise capsids for the same phagocytes that binding antibodies recruit. The widget treats the arms as separable because that is what makes the ordering visible, and the separability is the simplification.</li>
<li><strong>Serotype changes the numbers, not the mechanisms.</strong> AAV8 and AAV9 differ from AAV2 in tropism, in seroprevalence, and in how strongly each arm engages. The route is the same.</li>
<li><strong>Route of administration changes the exposure.</strong> Delivery to the eye, the central nervous system or a single muscle presents far less capsid to the circulation than an intravenous dose, and the complement and antibody arms weaken accordingly.</li>
<li><strong>Preclinical models under-report this.</strong> Mice have different TLR9 distribution and different complement handling from humans, and no colony has the pre-existing anti-AAV antibody that a third to a half of adult patients arrive with.</li>
</ul>
<p>Shell. Sensed. DNA. Sensed. Peptides. Presented. Dose. Amplifies. Screen. Titre. First.</p>
</section>
<section id="references" class="level2" data-number="12">
<h2 data-number="12" class="anchored" data-anchor-id="references"><span class="header-section-number">12</span> References</h2>
<ul>
<li>Manno, C. S. et al.&nbsp;(2006). <a href="https://doi.org/10.1038/nm1358">Successful transduction of liver in hemophilia by AAV-Factor IX and limitations imposed by the host immune response</a>. <em>Nature Medicine</em>. The trial that identified the capsid-specific CD8<img src="https://latex.codecogs.com/png.latex?%5E+"> T-cell response and the ALT rise.</li>
<li>Zhu, J., Huang, X. and Yang, Y. (2009). <a href="https://doi.org/10.1172/JCI37607">The TLR9-MyD88 pathway is critical for adaptive immune responses to adeno-associated virus gene therapy vectors in mice</a>. <em>Journal of Clinical Investigation</em>. The CpG-TLR9 link.</li>
<li>Faust, S. M. et al.&nbsp;(2013). <a href="https://doi.org/10.1172/JCI68205">CpG-depleted adeno-associated virus vectors evade immune detection</a>. <em>Journal of Clinical Investigation</em>. CpG content as a manufacturing lever.</li>
<li>Hösel, M. et al.&nbsp;(2012). <a href="https://doi.org/10.1002/hep.25801">Toll-like receptor 2-mediated innate immune response in human nonparenchymal liver cells toward adeno-associated viral vectors</a>. <em>Hepatology</em>. TLR2 sensing of the capsid.</li>
<li>Pillay, S. et al.&nbsp;(2016). <a href="https://doi.org/10.1038/nature16465">An essential receptor for adeno-associated virus infection</a>. <em>Nature</em>. Identification of AAVR, the entry receptor neutralising antibodies block access to.</li>
<li>Mingozzi, F. and High, K. A. (2013). <a href="https://doi.org/10.1182/blood-2013-01-306647">Immune responses to AAV vectors: overcoming barriers to successful gene therapy</a>. <em>Blood</em>. Review covering the humoral and cellular arms together.</li>
<li>Verdera, H. C., Kuranda, K. and Mingozzi, F. (2020). <a href="https://doi.org/10.1016/j.ymthe.2020.02.010">AAV vector immunogenicity in humans: a long journey to successful gene transfer</a>. <em>Molecular Therapy</em>. Clinical synthesis, including complement and dose.</li>
</ul>


</section>

 ]]></description>
  <category>Biology</category>
  <guid>https://project-delphi.github.io/ml-blog/posts/aav-immune-response/</guid>
  <pubDate>Fri, 04 Sep 2026 05:00:00 GMT</pubDate>
  <media:content url="https://project-delphi.github.io/ml-blog/posts/aav-immune-response/cover.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Why So Many Matrix Factorizations</title>
  <dc:creator>Ravi Kalia</dc:creator>
  <link>https://project-delphi.github.io/ml-blog/posts/why-so-many-matrix-factorizations/</link>
  <description><![CDATA[ 





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/why-so-many-matrix-factorizations/cover.png" class="img-fluid figure-img"></p>
<figcaption>Why So Many Matrix Factorizations</figcaption>
</figure>
</div>
<p>A matrix is like a complicated 3D object: what you see depends entirely on where you stand. Just as rewriting a polynomial can instantly reveal its roots, factoring a matrix isolates the exact properties you need to solve a specific problem. We don’t choose between Cholesky, QR, or SVD because one is inherently superior to the others. We choose them because the jobs differ wildly. One day you need to solve least squares; the next, you need a rank-<img src="https://latex.codecogs.com/png.latex?k"> summary or a spectrum analysis. Each factorization is simply a different mathematical lens, perfectly ground to bring a specific application into focus.</p>
<section id="quadratic-forms" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="quadratic-forms"><span class="header-section-number">1</span> Quadratic forms</h2>
<p>The same parabola has four writings. Each writing makes one geometric fact cheap to read.</p>
<ul>
<li><strong>Standard</strong> <img src="https://latex.codecogs.com/png.latex?y%20=%20ax%5E2%20+%20bx%20+%20c"> — the <img src="https://latex.codecogs.com/png.latex?y">-intercept is <img src="https://latex.codecogs.com/png.latex?c">.</li>
<li><strong>Vertex</strong> <img src="https://latex.codecogs.com/png.latex?y%20=%20a(x-h)%5E2%20+%20k"> — the min or max is <img src="https://latex.codecogs.com/png.latex?(h,k)">.</li>
<li><strong>Factored</strong> <img src="https://latex.codecogs.com/png.latex?y%20=%20a(x-r_1)(x-r_2)"> — the roots are <img src="https://latex.codecogs.com/png.latex?r_1,r_2">.</li>
<li><strong>Matrix</strong> <img src="https://latex.codecogs.com/png.latex?y%20=%20%5Cbegin%7Bbmatrix%7Dx%20&amp;%201%5Cend%7Bbmatrix%7D%5Cbegin%7Bbmatrix%7Da%20&amp;%20b/2%20%5C%5C%20b/2%20&amp;%20c%5Cend%7Bbmatrix%7D%5Cbegin%7Bbmatrix%7Dx%20%5C%5C%201%5Cend%7Bbmatrix%7D"> — the <img src="https://latex.codecogs.com/png.latex?2%5Ctimes%202"> already holds <img src="https://latex.codecogs.com/png.latex?(a,b,c)">. Cholesky of that <img src="https://latex.codecogs.com/png.latex?2%5Ctimes%202"> is completing the square.</li>
</ul>
<p>The curve in the widget is synthetic: <img src="https://latex.codecogs.com/png.latex?a,b,c"> are slider values, not fitted data. It stands in for any 1-D quadratic whose job changes with the writing.</p>
<div id="quad-widget">

</div>
<p>Pick <strong>Vertex</strong> from the form menu and the widget marks the turning point <img src="https://latex.codecogs.com/png.latex?(h,k)">. Pick <strong>Factored</strong> and it marks the roots <img src="https://latex.codecogs.com/png.latex?r_1,%20r_2"> when the curve crosses the axis, and says so when it does not. Pick <strong>Standard</strong> and it marks the intercept <img src="https://latex.codecogs.com/png.latex?c">; pick <strong>Matrix</strong> and it shows the <img src="https://latex.codecogs.com/png.latex?2%5Ctimes%202"> itself. Each writing makes one feature the thing you can read off.</p>
</section>
<section id="which-factorization" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="which-factorization"><span class="header-section-number">2</span> Which factorization</h2>
<p>The benefit is not the smallest leading term. Cholesky is the cheapest factor of an SPD covariance and is not defined on the photograph. We pick a factorization for the object it hands to the next step: a coefficient, a draw, a weight, a rank-<img src="https://latex.codecogs.com/png.latex?k"> image, a spectrum, or an inverse that exists.</p>
<div id="tbl-usecase" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-tbl figure">
<figcaption class="quarto-float-caption-top quarto-float-caption quarto-float-tbl" id="tbl-usecase-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Table&nbsp;1: Use case, not a ranking.
</figcaption>
<div aria-describedby="tbl-usecase-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Factorization</th>
<th>Matrix it needs</th>
<th>Downstream job</th>
<th>What we take</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>QR</td>
<td>tall <img src="https://latex.codecogs.com/png.latex?X">, full column rank</td>
<td>OLS</td>
<td><img src="https://latex.codecogs.com/png.latex?%5Chat%5Cbeta"> from <img src="https://latex.codecogs.com/png.latex?R%5Cbeta=Q%5ETy">, without <img src="https://latex.codecogs.com/png.latex?X%5ETX"></td>
</tr>
<tr class="even">
<td>Cholesky</td>
<td>SPD <img src="https://latex.codecogs.com/png.latex?%5CSigma"></td>
<td>correlated draws; repeated SPD solves</td>
<td><img src="https://latex.codecogs.com/png.latex?x=Lz">, or two triangular solves</td>
</tr>
<tr class="odd">
<td>LU</td>
<td>square, not SPD</td>
<td>general <img src="https://latex.codecogs.com/png.latex?Ax=b"> (KKT, any invertible <img src="https://latex.codecogs.com/png.latex?A">)</td>
<td>the solve</td>
</tr>
<tr class="even">
<td>SVD</td>
<td>any shape, any rank</td>
<td>compression; <img src="https://latex.codecogs.com/png.latex?A%5E+"></td>
<td>rank-<img src="https://latex.codecogs.com/png.latex?k"> summary, or the pseudoinverse</td>
</tr>
<tr class="odd">
<td>Eigen</td>
<td>square symmetric, modest <img src="https://latex.codecogs.com/png.latex?%5Ckappa"></td>
<td>spectrum / PCA of a covariance we trust</td>
<td><img src="https://latex.codecogs.com/png.latex?Q"> and <img src="https://latex.codecogs.com/png.latex?%5CLambda"></td>
</tr>
<tr class="even">
<td>NMF</td>
<td><img src="https://latex.codecogs.com/png.latex?V%5Cge%200"></td>
<td>parts or topics</td>
<td><img src="https://latex.codecogs.com/png.latex?W,H%5Cge%200"></td>
</tr>
</tbody>
</table>
</div>
</figure>
</div>
<p>NMF is iterative, <img src="https://latex.codecogs.com/png.latex?O(t%5C,k%5C,mn)">. The Golub leukemia matrix is worked in <a href="../matrix-factorizations/">Matrix Factorizations as Optimization Problems</a>. For a Gram matrix <img src="https://latex.codecogs.com/png.latex?X%5ETX">, SVD of <img src="https://latex.codecogs.com/png.latex?X"> is the stable writing of the same spectrum. Flop-count models are Golub &amp; Van Loan / Trefethen &amp; Bau for dense <img src="https://latex.codecogs.com/png.latex?n%5Ctimes%20n">, not wall-clock.</p>
</section>
<section id="qr" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="qr"><span class="header-section-number">3</span> QR</h2>
<p><img src="https://latex.codecogs.com/png.latex?%0AA%20=%20QR,%20%5Cqquad%20O(mn%5E2)%5C%20%5Ctext%7Bfor%20tall%20%7D%20m%5Ctimes%20n%20%5Ctext%7B%20(square:%20%7D%204n%5E3/3%5Ctext%7B)%7D.%0A"></p>
<p>Householder <img src="https://latex.codecogs.com/png.latex?Q"> is orthogonal. Back-substitution on <img src="https://latex.codecogs.com/png.latex?R%5Cbeta%20=%20Q%5ETy"> solves</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmin_%5Cbeta%20%5C%7Cy%20-%20X%5Cbeta%5C%7C_2%5E2%0A"></p>
<p>without forming <img src="https://latex.codecogs.com/png.latex?X%5ETX">. <a href="../matrix-factorizations/">Matrix Factorizations as Optimization Problems</a> treats the same factorization on the wheat markers, where <img src="https://latex.codecogs.com/png.latex?%5Ckappa(X)"> is large. The design here is ordinary OLS and well conditioned.</p>
<section id="diabetes" class="level3" data-number="3.1">
<h3 data-number="3.1" class="anchored" data-anchor-id="diabetes"><span class="header-section-number">3.1</span> Diabetes</h3>
<ul>
<li><strong>Source:</strong> Efron, Hastie, Johnstone, Tibshirani (2004), <em>Least Angle Regression</em>; the copy shipped with <code>sklearn.datasets.load_diabetes</code>.</li>
<li><strong>Measurements:</strong> 442 patients × 10 baseline variables (age, sex, BMI, blood pressure, six serum assays), quantitative disease-progression response. sklearn standardises each column to mean 0 and squared length 1.</li>
<li><strong>This post asks:</strong> recover <img src="https://latex.codecogs.com/png.latex?%5Chat%5Cbeta"> two ways — normal equations vs QR — and report the coefficient gap.</li>
<li><strong>A wrong <img src="https://latex.codecogs.com/png.latex?%5Chat%5Cbeta">:</strong> a progression score that ranks patients in the wrong order.</li>
<li><strong>Why QR:</strong> the job is tall OLS. <img src="https://latex.codecogs.com/png.latex?%5Ckappa(X)%5Capprox%2022"> here, so the two solves will agree. QR is the factorization that remains accurate on a later design whose <img src="https://latex.codecogs.com/png.latex?%5Ckappa"> is large.</li>
</ul>
<div id="diabetes-qr" class="cell" data-execution_count="2">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1">diabetes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> load_diabetes()</span>
<span id="cb1-2">X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> diabetes.data</span>
<span id="cb1-3">y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> diabetes.target</span>
<span id="cb1-4"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"diabetes design: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>X<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> patients x </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>X<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> variables"</span>)</span>
<span id="cb1-5"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"cond(X)     = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>cond(X)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb1-6"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"cond(X.T@X) = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>cond(X.T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> X)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb1-7"></span>
<span id="cb1-8">beta_ne <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.solve(X.T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> X, X.T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> y)</span>
<span id="cb1-9">Q, R <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> qr(X, mode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"economic"</span>)</span>
<span id="cb1-10">beta_qr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> solve_triangular(R, Q.T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> y)</span>
<span id="cb1-11"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"||beta_ne - beta_qr|| = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>norm(beta_ne <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> beta_qr)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb1-12"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"RSS (both)            = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>norm(y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> beta_qr)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>diabetes design: 442 patients x 10 variables
cond(X)     = 2.168e+01
cond(X.T@X) = 4.701e+02
||beta_ne - beta_qr|| = 1.820e-10
RSS (both)            = 3390.2651</code></pre>
</div>
</div>
<p><code>cond(X) ≈ 22</code> and <code>cond(X.T@X) ≈ 470 ≈ 22^2</code>. The two <img src="https://latex.codecogs.com/png.latex?%5Chat%5Cbeta"> agree to <img src="https://latex.codecogs.com/png.latex?1.8%5Ctimes%2010%5E%7B-10%7D">. On this design the methods coincide. QR is the one we keep when a later <img src="https://latex.codecogs.com/png.latex?X"> has <img src="https://latex.codecogs.com/png.latex?%5Ckappa(X)"> near <img src="https://latex.codecogs.com/png.latex?10%5E8">, as on the wheat markers.</p>
</section>
</section>
<section id="cholesky" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="cholesky"><span class="header-section-number">4</span> Cholesky</h2>
<p><img src="https://latex.codecogs.com/png.latex?%0AA%20=%20LL%5ET,%20%5Cqquad%20O(n%5E3/3),%5Cquad%20A%20%5Ctext%7B%20SPD%7D.%0A"></p>
<p>If <img src="https://latex.codecogs.com/png.latex?z%5Csim%5Cmathcal%20N(0,I)"> then <img src="https://latex.codecogs.com/png.latex?x%20=%20Lz%5Csim%5Cmathcal%20N(0,%5CSigma)"> when <img src="https://latex.codecogs.com/png.latex?A=%5CSigma">. That is how we draw a correlated return vector from an SPD covariance.</p>
<section id="returns-2022-09-01-to-2026-09-01" class="level3" data-number="4.1">
<h3 data-number="4.1" class="anchored" data-anchor-id="returns-2022-09-01-to-2026-09-01"><span class="header-section-number">4.1</span> Returns, 2022-09-01 to 2026-09-01</h3>
<ul>
<li><strong>Source:</strong> Nasdaq.com historical <code>Close/Last</code> for <code>META</code>, <code>AAPL</code>, <code>AMZN</code>, <code>NFLX</code>, <code>GOOGL</code>, <code>NVDA</code>, and <code>AZN</code> (AstraZeneca), fetched once by <code>src/fetch_prices.py</code> into the committed <code>prices.csv</code>.</li>
<li><strong>Window:</strong> 2022-09-01 through 2026-09-01 (1,003 common trading days; 1,002 log returns).</li>
<li><strong>Collector:</strong> the listing exchange, via Nasdaq’s split-adjusted close. The 10-for-1 NVDA split of 2024-06-10 is already in that series.</li>
<li><strong>This post asks:</strong> a <img src="https://latex.codecogs.com/png.latex?7%5Ctimes%207"> sample covariance of daily log returns, equal-weight draws <img src="https://latex.codecogs.com/png.latex?x=Lz">, and a 300-day fan fitted on all but the last 300 days, with the held-out equal-weight path overlaid. AZN is a FTSE 100 pharmaceutical, included so the book is not one US-tech factor: its daily correlation with GOOGL is <img src="https://latex.codecogs.com/png.latex?0.006">.</li>
<li><strong>A wrong covariance:</strong> a simulated book whose day-to-day risk does not match the seven names.</li>
<li><strong>Why Cholesky:</strong> a return covariance is SPD (ridge it if a sample is only semidefinite). Leading term <img src="https://latex.codecogs.com/png.latex?n%5E3/3">, half of LU.</li>
</ul>
<div id="load-prices" class="cell" data-execution_count="3">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">prices <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.read_csv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"prices.csv"</span>, parse_dates<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"date"</span>], index_col<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"date"</span>)</span>
<span id="cb3-2">rets <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.log(prices).diff().dropna()</span>
<span id="cb3-3">names <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(prices.columns)</span>
<span id="cb3-4">R <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rets.to_numpy()</span>
<span id="cb3-5">n_names <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> R.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb3-6">Sigma <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.cov(R, rowvar<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb3-7">L <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cholesky(Sigma, lower<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb3-8">w_eq <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.ones(n_names) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> n_names</span>
<span id="cb3-9"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"log returns: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>R<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> days x </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>n_names<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> names (</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>rets<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>index<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>date()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> .. </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>rets<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>index<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>date()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">)"</span>)</span>
<span id="cb3-10"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"corr with GOOGL:"</span>, {k: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(np.corrcoef(R[:, names.index(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"GOOGL"</span>)], R[:, i])[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, k <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(names)})</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>log returns: 1002 days x 7 names (2022-09-02 .. 2026-09-01)
corr with GOOGL: {'META': 0.4833464519128782, 'AAPL': 0.44328985346547295, 'AMZN': 0.5984381147607905, 'NFLX': 0.29901306102125685, 'GOOGL': 1.0, 'NVDA': 0.41303817913126784, 'AZN': 0.005784626769348884}</code></pre>
</div>
</div>
<div id="cell-fig-performance" class="cell" data-execution_count="4">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1">indexed <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100.0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> prices <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> prices.iloc[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb5-2">palette <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb5-3">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"META"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#4A3AA7"</span>,</span>
<span id="cb5-4">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AAPL"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#1D6E6E"</span>,</span>
<span id="cb5-5">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AMZN"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#9A5B00"</span>,</span>
<span id="cb5-6">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"NFLX"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#7A3B6B"</span>,</span>
<span id="cb5-7">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"GOOGL"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#1D5C6E"</span>,</span>
<span id="cb5-8">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"NVDA"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#C45C26"</span>,</span>
<span id="cb5-9">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AZN"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#5F6672"</span>,</span>
<span id="cb5-10">}</span>
<span id="cb5-11">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7.4</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.2</span>))</span>
<span id="cb5-12"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> col <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> indexed.columns:</span>
<span id="cb5-13">    lw <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.4</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> col <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"GOOGL"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AZN"</span>) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.3</span></span>
<span id="cb5-14">    ax.plot(indexed.index, indexed[col], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>palette[col], lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>lw, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>col)</span>
<span id="cb5-15">ax.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"index (100 = 2022-09-01)"</span>)</span>
<span id="cb5-16">ax.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Seven names, same window"</span>)</span>
<span id="cb5-17">ax.legend(ncol<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, loc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"upper left"</span>)</span>
<span id="cb5-18">plt.tight_layout()</span>
<span id="cb5-19">plt.show()</span>
<span id="cb5-20"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>((prices.iloc[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> prices.iloc[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>).to_dict())</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-performance" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-performance-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/why-so-many-matrix-factorizations/index_files/figure-html/fig-performance-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-performance-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Split-adjusted closes indexed to 100 on 2022-09-01. AZN is the nearly flat British pharmaceutical; NVDA is the steep US-tech line.
</figcaption>
</figure>
</div>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>{'META': 2.499, 'AAPL': 1.058, 'AMZN': 0.994, 'NFLX': 2.513, 'GOOGL': 2.053, 'NVDA': 14.602, 'AZN': 0.376}</code></pre>
</div>
</div>
<p>Indexed to 100 at the first print, NVDA ends near <img src="https://latex.codecogs.com/png.latex?1%7B,%7D560"> and GOOGL near <img src="https://latex.codecogs.com/png.latex?305">. AZN ends near <img src="https://latex.codecogs.com/png.latex?138">. The British name barely tracks the US-tech block over the window.</p>
<div id="cell-fig-corr" class="cell" data-execution_count="5">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1">corr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.corrcoef(R, rowvar<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb7-2"></span>
<span id="cb7-3">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.6</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.6</span>))</span>
<span id="cb7-4">im <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ax.imshow(corr, cmap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"RdBu_r"</span>, vmin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, vmax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb7-5">ax.set_xticks(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n_names), names, rotation<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">45</span>, ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"right"</span>)</span>
<span id="cb7-6">ax.set_yticks(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n_names), names)</span>
<span id="cb7-7">fig.colorbar(im, ax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ax, fraction<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.046</span>, pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.04</span>)</span>
<span id="cb7-8">ax.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Return correlations"</span>)</span>
<span id="cb7-9">ax.grid(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb7-10">plt.tight_layout()</span>
<span id="cb7-11">plt.show()</span>
<span id="cb7-12"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(corr, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>))</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-corr" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-corr-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/why-so-many-matrix-factorizations/index_files/figure-html/fig-corr-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-corr-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: Sample correlations of daily log returns, seven names, 2022-09-01 to 2026-09-01.
</figcaption>
</figure>
</div>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>[[ 1.     0.396  0.571  0.328  0.483  0.423 -0.052]
 [ 0.396  1.     0.423  0.343  0.443  0.383  0.059]
 [ 0.571  0.423  1.     0.388  0.598  0.459 -0.009]
 [ 0.328  0.343  0.388  1.     0.299  0.344  0.032]
 [ 0.483  0.443  0.598  0.299  1.     0.413  0.006]
 [ 0.423  0.383  0.459  0.344  0.413  1.    -0.006]
 [-0.052  0.059 -0.009  0.032  0.006 -0.006  1.   ]]</code></pre>
</div>
</div>
<p>The six US-tech names correlate between <img src="https://latex.codecogs.com/png.latex?0.30"> and <img src="https://latex.codecogs.com/png.latex?0.60"> with each other. AZN vs GOOGL is <img src="https://latex.codecogs.com/png.latex?0.006">. The book is no longer one factor plus residuals.</p>
<div id="chol-sample" class="cell" data-execution_count="6">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"||L L^T - Sigma||_F = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>norm(L <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> L.T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> Sigma)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb9-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"cond(Sigma) = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>cond(Sigma)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb9-3"></span>
<span id="cb9-4">hist_port <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> R <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> w_eq</span>
<span id="cb9-5">Z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rng.normal(size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10_000</span>, n_names))</span>
<span id="cb9-6">sim_port <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (Z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> L.T) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> w_eq</span>
<span id="cb9-7"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"equal-weight daily std  hist=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>hist_port<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>std(ddof<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.5f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  sim=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>sim_port<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>std(ddof<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.5f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>||L L^T - Sigma||_F = 1.437e-19
cond(Sigma) = 11.125
equal-weight daily std  hist=0.01463  sim=0.01474</code></pre>
</div>
</div>
<div id="cell-fig-portfolio" class="cell" data-execution_count="7">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6.4</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.8</span>))</span>
<span id="cb11-2">ax.hist(sim_port, bins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">40</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ACCENT, alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.85</span>, density<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Lz draws"</span>)</span>
<span id="cb11-3">grid <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linspace(sim_port.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(), sim_port.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">200</span>)</span>
<span id="cb11-4">mu_h, sd_h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> hist_port.mean(), hist_port.std(ddof<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb11-5">ax.plot(</span>
<span id="cb11-6">    grid,</span>
<span id="cb11-7">    np.exp(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> ((grid <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> mu_h) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> sd_h) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (sd_h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.sqrt(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.pi)),</span>
<span id="cb11-8">    color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>AMBER,</span>
<span id="cb11-9">    lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>,</span>
<span id="cb11-10">    label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"N(hist mean, hist var)"</span>,</span>
<span id="cb11-11">)</span>
<span id="cb11-12">ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"equal-weight daily log return"</span>)</span>
<span id="cb11-13">ax.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"density"</span>)</span>
<span id="cb11-14">ax.legend()</span>
<span id="cb11-15">plt.tight_layout()</span>
<span id="cb11-16">plt.show()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-portfolio" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-portfolio-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/why-so-many-matrix-factorizations/index_files/figure-html/fig-portfolio-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-portfolio-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: Equal-weight next-day portfolio return. Histogram: 10,000 Cholesky draws. Curve: kernel-free normal with the historical mean and variance.
</figcaption>
</figure>
</div>
</div>
</div>
<p><img src="https://latex.codecogs.com/png.latex?%5C%7CLL%5ET-%5CSigma%5C%7C_F"> is on the order of <img src="https://latex.codecogs.com/png.latex?10%5E%7B-19%7D">. Equal-weight daily volatility on the historical book and on the <img src="https://latex.codecogs.com/png.latex?Lz"> draws will match to a few basis points. Independent normals would treat every off-diagonal of <img src="https://latex.codecogs.com/png.latex?%5CSigma"> as zero and understate the US-tech block.</p>
<p>The histogram is one day. The same generator, fitted on returns through 2025-06-23, is then run for the 300 days that follow. The held-out equal-weight book is the actual path.</p>
<div id="cell-fig-forecast-fan" class="cell" data-execution_count="8">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb12-1">horizon <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">300</span></span>
<span id="cb12-2">n_paths <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5_000</span></span>
<span id="cb12-3">ew <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100.0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.exp(np.cumsum(R <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> w_eq))</span>
<span id="cb12-4">fit_R <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> R[:<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>horizon]</span>
<span id="cb12-5">L_fit <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cholesky(np.cov(fit_R, rowvar<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>), lower<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb12-6">mu_fit <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fit_R.mean(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb12-7">rng_fan <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.random.default_rng(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>)</span>
<span id="cb12-8">Z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rng_fan.normal(size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(n_paths, horizon, n_names))</span>
<span id="cb12-9">port_fwd <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (mu_fit <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> Z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> L_fit.T) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> w_eq</span>
<span id="cb12-10">origin <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ew[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>horizon <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb12-11">wealth <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> origin <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.exp(np.cumsum(port_fwd, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb12-12">q <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.quantile(wealth, [<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.50</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.75</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.95</span>], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb12-13">fwd_idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rets.index[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>horizon:]</span>
<span id="cb12-14">actual <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ew[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>horizon:]</span>
<span id="cb12-15">rank <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>((wealth[:, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span> actual[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]).mean())</span>
<span id="cb12-16"></span>
<span id="cb12-17">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7.4</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.0</span>))</span>
<span id="cb12-18">ax.fill_between(fwd_idx, q[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], q[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ACCENT, alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.15</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"5-95%"</span>)</span>
<span id="cb12-19">ax.fill_between(fwd_idx, q[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], q[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ACCENT, alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.30</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"25-75%"</span>)</span>
<span id="cb12-20">ax.plot(fwd_idx, q[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>AMBER, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.8</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"median"</span>)</span>
<span id="cb12-21">ax.plot(rets.index, ew, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>INK, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.6</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"equal-weight actual"</span>, zorder<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>)</span>
<span id="cb12-22">ax.axvline(rets.index[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>horizon], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>GREY, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, ls<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--"</span>)</span>
<span id="cb12-23">ax.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"index (100 = 2022-09-01)"</span>)</span>
<span id="cb12-24">ax.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"300-day holdout fan"</span>)</span>
<span id="cb12-25">ax.legend(ncol<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, loc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"upper left"</span>)</span>
<span id="cb12-26">plt.tight_layout()</span>
<span id="cb12-27">plt.show()</span>
<span id="cb12-28"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(</span>
<span id="cb12-29">    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"origin=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>origin<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  actual=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>actual[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  "</span></span>
<span id="cb12-30">    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"median=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>q[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  p5=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>q[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  p95=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>q[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  "</span></span>
<span id="cb12-31">    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"actual_pct=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>rank<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb12-32">)</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-forecast-fan" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-forecast-fan-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/why-so-many-matrix-factorizations/index_files/figure-html/fig-forecast-fan-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-forecast-fan-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;4: Equal-weight book, indexed to 100 on 2022-09-01. Fan starts at the dashed line: 5,000 Cholesky paths from a mean and covariance fitted on the earlier window. The dark line through the fan is the held-out actual. Bands are 5-95 and 25-75 percentiles.
</figcaption>
</figure>
</div>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>origin=264.1  actual=318.3  median=396.3  p5=253.8  p95=626.8  actual_pct=0.21</code></pre>
</div>
</div>
<p>Fit <img src="https://latex.codecogs.com/png.latex?%5Cmu"> and <img src="https://latex.codecogs.com/png.latex?L"> on 702 days; hold out 2025-06-24 through 2026-09-01. Wealth at the split is <img src="https://latex.codecogs.com/png.latex?264">. Day 300: actual <img src="https://latex.codecogs.com/png.latex?318">, median <img src="https://latex.codecogs.com/png.latex?396">, <img src="https://latex.codecogs.com/png.latex?5">–<img src="https://latex.codecogs.com/png.latex?95"> band <img src="https://latex.codecogs.com/png.latex?254">–<img src="https://latex.codecogs.com/png.latex?627">. The actual path stays inside the band and finishes below the median (21st percentile of the paths). The band is the object Cholesky is for. The median is not a hit.</p>
</section>
</section>
<section id="lu" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="lu"><span class="header-section-number">5</span> LU</h2>
<p><img src="https://latex.codecogs.com/png.latex?%0APA%20=%20LU,%20%5Cqquad%20O(2n%5E3/3).%0A"></p>
<p>Partial pivoting <img src="https://latex.codecogs.com/png.latex?P"> keeps the solve stable on a general square matrix. Cholesky applies only when <img src="https://latex.codecogs.com/png.latex?A"> is SPD.</p>
<p>Mean-variance with a budget (and a return target) is the same seven names and a <strong>different</strong> matrix. Minimise <img src="https://latex.codecogs.com/png.latex?%5Ctfrac12%20w%5ET%5CSigma%20w"> subject to <img src="https://latex.codecogs.com/png.latex?1%5ETw=1"> and <img src="https://latex.codecogs.com/png.latex?%5Cmu%5ETw=%5Cmu_%5Cstar">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Bbmatrix%7D%0A%5CSigma%20&amp;%201%20&amp;%20%5Cmu%20%5C%5C%0A1%5ET%20&amp;%200%20&amp;%200%20%5C%5C%0A%5Cmu%5ET%20&amp;%200%20&amp;%200%0A%5Cend%7Bbmatrix%7D%0A%5Cbegin%7Bbmatrix%7D%20w%20%5C%5C%20%5Clambda%20%5C%5C%20%5Cgamma%20%5Cend%7Bbmatrix%7D%0A=%0A%5Cbegin%7Bbmatrix%7D%200%20%5C%5C%201%20%5C%5C%20%5Cmu_%5Cstar%20%5Cend%7Bbmatrix%7D.%0A"></p>
<p>The KKT matrix is <img src="https://latex.codecogs.com/png.latex?(n+2)%5Ctimes(n+2)"> and symmetric indefinite. <img src="https://latex.codecogs.com/png.latex?%5Cmu_%5Cstar"> is the equal-weight historical mean, so a feasible book exists.</p>
<div id="kkt-lu" class="cell" data-execution_count="9">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb14-1">mu <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> R.mean(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb14-2">mu_star <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(mu <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> w_eq)</span>
<span id="cb14-3">kkt_n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> n_names <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb14-4">K <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros((kkt_n, kkt_n))</span>
<span id="cb14-5">K[:n_names, :n_names] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Sigma</span>
<span id="cb14-6">K[:n_names, n_names] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span></span>
<span id="cb14-7">K[n_names, :n_names] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span></span>
<span id="cb14-8">K[:n_names, n_names <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mu</span>
<span id="cb14-9">K[n_names <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, :n_names] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mu</span>
<span id="cb14-10">rhs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros(kkt_n)</span>
<span id="cb14-11">rhs[n_names] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span></span>
<span id="cb14-12">rhs[n_names <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mu_star</span>
<span id="cb14-13"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"KKT shape </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>K<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb14-14"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"KKT eigenvalues:"</span>, np.linalg.eigvalsh(K).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>))</span>
<span id="cb14-15"></span>
<span id="cb14-16"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb14-17">    cho_factor(K)</span>
<span id="cb14-18">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cho_factor: succeeded"</span>)</span>
<span id="cb14-19"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> np.linalg.LinAlgError <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> err:</span>
<span id="cb14-20">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"cho_factor: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span>(err)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">__name__</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb14-21"></span>
<span id="cb14-22">lu, piv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> lu_factor(K)</span>
<span id="cb14-23">sol <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> lu_solve((lu, piv), rhs)</span>
<span id="cb14-24">w_kkt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sol[:n_names]</span>
<span id="cb14-25"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"weights:"</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(names, w_kkt.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>))))</span>
<span id="cb14-26"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"1^T w = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>w_kkt<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.6f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">   mu^T w = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>w_kkt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> mu<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.6e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">   target = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>mu_star<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.6e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb14-27"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"||K sol - rhs|| = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>norm(K <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> sol <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> rhs)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>KKT shape (9, 9)
KKT eigenvalues: [-2.645004e+00 -1.622000e-03  1.850000e-04  2.040000e-04  2.960000e-04
  3.990000e-04  5.820000e-04  2.265000e-03  2.646503e+00]
cho_factor: LinAlgError
weights: {'META': np.float64(0.0542), 'AAPL': np.float64(0.2074), 'AMZN': np.float64(-0.0792), 'NFLX': np.float64(0.1671), 'GOOGL': np.float64(0.2294), 'NVDA': np.float64(0.1624), 'AZN': np.float64(0.2587)}
1^T w = 1.000000   mu^T w = 1.155372e-03   target = 1.155372e-03
||K sol - rhs|| = 2.813e-19</code></pre>
</div>
</div>
<p><code>cho_factor</code> raises <code>LinAlgError</code> because a constraint pivot is not positive. LU returns weights that sum to <img src="https://latex.codecogs.com/png.latex?1"> and hit <img src="https://latex.codecogs.com/png.latex?%5Cmu_%5Cstar"> to printed precision. The residual is on the order of <img src="https://latex.codecogs.com/png.latex?10%5E%7B-16%7D">. The weights are a constrained quadratic programme, not a return forecast.</p>
</section>
<section id="svd" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="svd"><span class="header-section-number">6</span> SVD</h2>
<p><img src="https://latex.codecogs.com/png.latex?%0AA%20=%20U%5CSigma%20V%5ET,%20%5Cqquad%20O(%5Cmin(mn%5E2,%20m%5E2n)).%0A"></p>
<p>Truncation at rank <img src="https://latex.codecogs.com/png.latex?k"> stores <img src="https://latex.codecogs.com/png.latex?k(m+n+1)"> numbers instead of <img src="https://latex.codecogs.com/png.latex?mn">. Geometry of the three factors is <a href="../svd-rotate-stretch-rotate/">The Matrix That Rotates, Stretches, and Rotates Again</a>. This section is compression only.</p>
<section id="astronaut-photograph" class="level3" data-number="6.1">
<h3 data-number="6.1" class="anchored" data-anchor-id="astronaut-photograph"><span class="header-section-number">6.1</span> Astronaut photograph</h3>
<ul>
<li><strong>Source:</strong> <code>skimage.data.astronaut()</code>, NASA public-domain photograph of Eileen Collins, <img src="https://latex.codecogs.com/png.latex?512%5Ctimes%20512"> grayscale in <img src="https://latex.codecogs.com/png.latex?%5B0,1%5D">.</li>
<li><strong>This post asks:</strong> Frobenius residual and storage at ranks <img src="https://latex.codecogs.com/png.latex?5">, <img src="https://latex.codecogs.com/png.latex?20">, <img src="https://latex.codecogs.com/png.latex?50">.</li>
<li><strong>A poorly chosen rank:</strong> a reconstruction that drops the face, or a store that does not shrink.</li>
<li><strong>Why SVD:</strong> the photograph is a rectangular array (here square, still not SPD and not a least-squares design). Eckart–Young says the truncated SVD is the optimal rank-<img src="https://latex.codecogs.com/png.latex?k"> Frobenius summary.</li>
</ul>
<div id="cell-fig-astronaut" class="cell" data-execution_count="10">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb16-1">img <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rgb2gray(data.astronaut())</span>
<span id="cb16-2">U_img, s_img, Vt_img <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> svd(img, full_matrices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb16-3">m, n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> img.shape</span>
<span id="cb16-4">ranks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">50</span>)</span>
<span id="cb16-5">energy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (s_img <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>).cumsum() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (s_img <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>()</span>
<span id="cb16-6"></span>
<span id="cb16-7">fig, axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">11</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.2</span>))</span>
<span id="cb16-8">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].imshow(img, cmap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gray"</span>)</span>
<span id="cb16-9">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].set_title(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"original</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> n<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> values"</span>)</span>
<span id="cb16-10">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].axis(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"off"</span>)</span>
<span id="cb16-11"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> ax, k <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:], ranks):</span>
<span id="cb16-12">    approx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (U_img[:, :k] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> s_img[:k]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> Vt_img[:k]</span>
<span id="cb16-13">    stored <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb16-14">    ax.imshow(approx, cmap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gray"</span>)</span>
<span id="cb16-15">    ax.set_title(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"rank </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>k<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>stored<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> values, </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>energy[k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">%"</span>)</span>
<span id="cb16-16">    ax.axis(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"off"</span>)</span>
<span id="cb16-17">plt.tight_layout()</span>
<span id="cb16-18">plt.show()</span>
<span id="cb16-19"></span>
<span id="cb16-20"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> k <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> ranks:</span>
<span id="cb16-21">    approx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (U_img[:, :k] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> s_img[:k]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> Vt_img[:k]</span>
<span id="cb16-22">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(</span>
<span id="cb16-23">        <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"k=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>k<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:2d}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  ||A-A_k||_F=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>norm(img <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> approx, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'fro'</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  "</span></span>
<span id="cb16-24">        <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"energy=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>energy[k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">%  "</span></span>
<span id="cb16-25">        <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"storage=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> / </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> n<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> (</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> n) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">%)"</span></span>
<span id="cb16-26">    )</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-astronaut" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-astronaut-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/why-so-many-matrix-factorizations/index_files/figure-html/fig-astronaut-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-astronaut-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;5: Grayscale astronaut photograph and truncated SVD at ranks 5, 20, and 50.
</figcaption>
</figure>
</div>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>k= 5  ||A-A_k||_F=79.4  energy=91.5%  storage=5125 / 262144 (2.0%)
k=20  ||A-A_k||_F=40.1  energy=97.8%  storage=20500 / 262144 (7.8%)
k=50  ||A-A_k||_F=22.0  energy=99.3%  storage=51250 / 262144 (19.6%)</code></pre>
</div>
</div>
<p>Rank <img src="https://latex.codecogs.com/png.latex?5"> keeps <img src="https://latex.codecogs.com/png.latex?91.5%5C%25"> of Frobenius energy in <img src="https://latex.codecogs.com/png.latex?2.0%5C%25"> of the raw values. Rank <img src="https://latex.codecogs.com/png.latex?50"> keeps <img src="https://latex.codecogs.com/png.latex?99.3%5C%25"> in <img src="https://latex.codecogs.com/png.latex?19.6%5C%25">. JPEG will use fewer bytes at a given quality. The factorization is still a compressor for a rectangular array, which is the job here.</p>
</section>
</section>
<section id="eigendecomposition" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="eigendecomposition"><span class="header-section-number">7</span> Eigendecomposition</h2>
<p><img src="https://latex.codecogs.com/png.latex?%0AA%20=%20Q%5CLambda%20Q%5ET,%20%5Cqquad%20O(n%5E3),%5Cquad%20A%20%5Ctext%7B%20symmetric%7D.%0A"></p>
<p>On a covariance whose <img src="https://latex.codecogs.com/png.latex?%5Ckappa"> is small, eigen and SVD of the centered data agree. Forming the Gram matrix <img src="https://latex.codecogs.com/png.latex?X%5ETX"> and taking eigen squares the condition number:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ckappa(X%5ETX)%20=%20%5Ckappa(X)%5E2.%0A"></p>
<section id="well-conditioned-covariance" class="level3" data-number="7.1">
<h3 data-number="7.1" class="anchored" data-anchor-id="well-conditioned-covariance"><span class="header-section-number">7.1</span> Well-conditioned covariance</h3>
<p>The <img src="https://latex.codecogs.com/png.latex?7%5Ctimes%207"> return covariance has modest <img src="https://latex.codecogs.com/png.latex?%5Ckappa">. Eigenvalues of <img src="https://latex.codecogs.com/png.latex?%5CSigma"> must match squared singular values of the centered return matrix, up to the <img src="https://latex.codecogs.com/png.latex?1/(T-1)"> in <code>np.cov</code>.</p>
<div id="eigen-cov" class="cell" data-execution_count="11">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb18-1">Xc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> R <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> R.mean(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb18-2">evals <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.sort(eigh(Sigma)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])[::<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb18-3">_, s_c, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> svd(Xc, full_matrices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb18-4">s2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (s_c <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(Xc) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb18-5"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Sigma eigenvalues:     "</span>, evals.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>))</span>
<span id="cb18-6"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sigma^2 / (T-1):       "</span>, s2.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>))</span>
<span id="cb18-7"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"relative gap:          "</span>, np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(evals <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> s2) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> evals)</span>
<span id="cb18-8"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"cond(Sigma) = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>cond(Sigma)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>Sigma eigenvalues:      [0.001782 0.000508 0.000482 0.000397 0.000281 0.000197 0.00016 ]
sigma^2 / (T-1):        [0.001782 0.000508 0.000482 0.000397 0.000281 0.000197 0.00016 ]
relative gap:           [3.64963989e-16 4.26580996e-16 5.61795600e-16 4.09238418e-16
 1.15879974e-15 8.26176621e-16 1.18418879e-15]
cond(Sigma) = 11.125</code></pre>
</div>
</div>
<p>Relative gaps stay near <img src="https://latex.codecogs.com/png.latex?10%5E%7B-15%7D">. The two writings of this spectrum agree.</p>
</section>
<section id="polynomial-in-time-design" class="level3" data-number="7.2">
<h3 data-number="7.2" class="anchored" data-anchor-id="polynomial-in-time-design"><span class="header-section-number">7.2</span> Polynomial-in-time design</h3>
<p>A polynomial in years-since-first-print of log NVDA price is the same series written as a tall, nearly dependent design. Degree <img src="https://latex.codecogs.com/png.latex?10"> (eleven columns), <img src="https://latex.codecogs.com/png.latex?t_i%20=%20i/252">:</p>
<div id="cell-fig-eigen-digits" class="cell" data-execution_count="12">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb20" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb20-1">y_nvda <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.log(prices[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"NVDA"</span>].to_numpy())</span>
<span id="cb20-2">t_years <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.arange(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(y_nvda)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">252.0</span></span>
<span id="cb20-3">deg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span></span>
<span id="cb20-4">V <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.vander(t_years, N<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>deg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, increasing<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb20-5">G <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> V.T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> V</span>
<span id="cb20-6">ge <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.sort(eigh(G)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])[::<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb20-7">_, s_v, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> svd(V, full_matrices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb20-8">s2_v <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> s_v <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb20-9"></span>
<span id="cb20-10"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Vandermonde V: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>V<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> x </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>V<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb20-11"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"cond(V)     = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>cond(V)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb20-12"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"cond(V.T@V) = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>cond(G)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb20-13"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"trailing eig(V.T@V):"</span>, ge[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>:])</span>
<span id="cb20-14"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"trailing sigma^2:   "</span>, s2_v[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>:])</span>
<span id="cb20-15"></span>
<span id="cb20-16">fig, axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">10.5</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.0</span>))</span>
<span id="cb20-17">idx_s <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.arange(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, n_names <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb20-18">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].semilogy(idx_s, evals, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"o-"</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ACCENT, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"eig(Sigma)"</span>)</span>
<span id="cb20-19">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].semilogy(idx_s, s2, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"s--"</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>TEAL, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sigma^2 / (T-1) of Xc"</span>)</span>
<span id="cb20-20">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"component"</span>)</span>
<span id="cb20-21">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"value"</span>)</span>
<span id="cb20-22">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"7x7 covariance: agreement"</span>)</span>
<span id="cb20-23">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].legend(fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb20-24"></span>
<span id="cb20-25">idx_v <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.arange(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(ge) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb20-26">rel <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(ge <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> s2_v) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> np.maximum(s2_v, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-300</span>)</span>
<span id="cb20-27">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].semilogy(idx_v, rel, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"o-"</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ACCENT)</span>
<span id="cb20-28">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].axhline(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-15</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>GREY, ls<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--"</span>, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"machine-eps scale"</span>)</span>
<span id="cb20-29">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"component"</span>)</span>
<span id="cb20-30">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"|lambda - sigma^2| / sigma^2"</span>)</span>
<span id="cb20-31">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"degree-10 time polynomial: relative gap"</span>)</span>
<span id="cb20-32">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].legend(fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb20-33"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"relative gap last3:"</span>, rel[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>:])</span>
<span id="cb20-34">plt.tight_layout()</span>
<span id="cb20-35">plt.show()</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>Vandermonde V: 1003 x 11
cond(V)     = 1.484e+09
cond(V.T@V) = 2.196e+18
trailing eig(V.T@V): [7.96121242e-02 1.94061656e-03 1.95533167e-05]
trailing sigma^2:    [8.03613608e-02 2.03273618e-03 2.29717038e-05]
relative gap last3: [0.00932334 0.04531804 0.1488086 ]</code></pre>
</div>
<div class="cell-output cell-output-display">
<div id="fig-eigen-digits" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-eigen-digits-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/why-so-many-matrix-factorizations/index_files/figure-html/fig-eigen-digits-output-2.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-eigen-digits-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;6: Left: eigenvalues of the 7x7 covariance match squared singular values of the centered returns. Right: relative gap |lambda - sigma^2| / sigma^2 on the degree-10 time polynomial, growing once kappa(X^T X) leaves float64.
</figcaption>
</figure>
</div>
</div>
</div>
<p><img src="https://latex.codecogs.com/png.latex?%5Ckappa(V)%5Capprox%201.5%5Ctimes%2010%5E9"> and <img src="https://latex.codecogs.com/png.latex?%5Ckappa(V%5ETV)%5Capprox%202.2%5Ctimes%2010%5E%7B18%7D">, past the float64 ceiling <img src="https://latex.codecogs.com/png.latex?%5Capprox%2010%5E%7B16%7D">. The last two eigenvalues of <img src="https://latex.codecogs.com/png.latex?V%5ETV"> differ from <img src="https://latex.codecogs.com/png.latex?%5Csigma%5E2"> by about <img src="https://latex.codecogs.com/png.latex?5%5C%25"> and <img src="https://latex.codecogs.com/png.latex?15%5C%25">. <img src="https://latex.codecogs.com/png.latex?V%5ETV"> is SPD by construction; the gap is the squared condition number in floating point.</p>
</section>
</section>
<section id="svd-as-inverse" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="svd-as-inverse"><span class="header-section-number">8</span> SVD as inverse</h2>
<p><img src="https://latex.codecogs.com/png.latex?%0AA%5E+%20=%20V%5CSigma%5E+%20U%5ET.%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?%5CSigma%5E+"> inverts the nonzero singular values and leaves the zeros as zeros. That one object covers the four linear-system jobs.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Matrix</th>
<th>Inverse that exists</th>
<th>What LU / <code>inv</code> / eigen-inverse need</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Square, full rank</td>
<td><img src="https://latex.codecogs.com/png.latex?A%5E%7B-1%7D=A%5E+"></td>
<td>Square and full rank</td>
</tr>
<tr class="even">
<td>Tall, full column rank</td>
<td>left inverse; OLS <img src="https://latex.codecogs.com/png.latex?%5Chat%5Cbeta=A%5E+y"></td>
<td>cannot apply</td>
</tr>
<tr class="odd">
<td>Fat, full row rank</td>
<td>right inverse; least-norm solve</td>
<td>cannot apply</td>
</tr>
<tr class="even">
<td>Rank-deficient</td>
<td><img src="https://latex.codecogs.com/png.latex?A%5E+"> drops the null space</td>
<td><code>inv</code> is undefined; eigen needs a full set of eigenvectors</td>
</tr>
</tbody>
</table>
<div id="pinv-checks" class="cell" data-execution_count="13">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb22" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb22-1">beta_pinv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pinv(V) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> y_nvda</span>
<span id="cb22-2">beta_ne <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.solve(G, V.T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> y_nvda)</span>
<span id="cb22-3"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"||beta_ne - beta_pinv|| = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>norm(beta_ne <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> beta_pinv)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb22-4"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"RSS pinv = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>norm(V <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> beta_pinv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> y_nvda)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb22-5"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"RSS NE   = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>norm(V <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> beta_ne <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> y_nvda)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb22-6"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"max |inv(G)|  = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(inv(G))<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb22-7"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"max |pinv(V)| = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(pinv(V))<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb22-8"></span>
<span id="cb22-9">S <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>], [<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.0</span>]])</span>
<span id="cb22-10"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"det(S) = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>det(S)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb22-11"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb22-12">    inv(S)</span>
<span id="cb22-13">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"inv(S): succeeded"</span>)</span>
<span id="cb22-14"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> np.linalg.LinAlgError <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> err:</span>
<span id="cb22-15">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"inv(S): </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span>(err)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">__name__</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb22-16"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pinv(S) ="</span>)</span>
<span id="cb22-17"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(pinv(S))</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>||beta_ne - beta_pinv|| = 0.820
RSS pinv = 2.9075
RSS NE   = 2.9075
max |inv(G)|  = 17223.7
max |pinv(V)| = 26.0
det(S) = 0.0
inv(S): LinAlgError
pinv(S) =
[[0.04 0.08]
 [0.08 0.16]]</code></pre>
</div>
</div>
<p>On the degree-10 NVDA design, <img src="https://latex.codecogs.com/png.latex?%5Chat%5Cbeta"> from <code>solve(V.T@V, V.T@y)</code> and from <code>pinv(V)@y</code> differ by <img src="https://latex.codecogs.com/png.latex?0.82"> in Euclidean norm (<code>||β||≈188</code>). Residuals still match to <img src="https://latex.codecogs.com/png.latex?10%5E%7B-5%7D"> because the error lives in the near-null columns. <code>inv(G)</code> has entries of size <img src="https://latex.codecogs.com/png.latex?1.7%5Ctimes%2010%5E4">; <code>pinv(V)</code> stays under <img src="https://latex.codecogs.com/png.latex?26">.</p>
<p>The <img src="https://latex.codecogs.com/png.latex?2%5Ctimes%202"> <img src="https://latex.codecogs.com/png.latex?S=%5Cbegin%7Bbmatrix%7D1&amp;2%5C%5C2&amp;4%5Cend%7Bbmatrix%7D"> is exactly singular. <code>inv</code> raises <code>LinAlgError</code>. <code>pinv</code> returns the Moore–Penrose inverse. LU on <img src="https://latex.codecogs.com/png.latex?S"> needs a rank decision. The SVD pseudoinverse is defined for every real matrix, which is why it is the inverse we use when the shape or the rank is not known in advance.</p>
</section>
<section id="cost" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="cost"><span class="header-section-number">9</span> Cost</h2>
<p>Flop counts decide among methods that already fit the job. They do not pick the job. The table is the leading term for dense <img src="https://latex.codecogs.com/png.latex?n%5Ctimes%20n">; the widget is the same models restricted to methods that apply.</p>
<div id="tbl-flops" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-tbl figure">
<figcaption class="quarto-float-caption-top quarto-float-caption quarto-float-tbl" id="tbl-flops-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Table&nbsp;2: Flop-count models (Golub &amp; Van Loan / Trefethen &amp; Bau).
</figcaption>
<div aria-describedby="tbl-flops-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<table class="caption-top table">
<thead>
<tr class="header">
<th>Factorization</th>
<th>Flops</th>
<th>When the matrix is rectangular</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Cholesky</td>
<td><img src="https://latex.codecogs.com/png.latex?n%5E3/3"></td>
<td>SPD only; not defined</td>
</tr>
<tr class="even">
<td>LU</td>
<td><img src="https://latex.codecogs.com/png.latex?2n%5E3/3"></td>
<td>square only</td>
</tr>
<tr class="odd">
<td>Householder QR</td>
<td><img src="https://latex.codecogs.com/png.latex?4n%5E3/3"></td>
<td><img src="https://latex.codecogs.com/png.latex?2mn%5E2-2n%5E3/3"> for tall <img src="https://latex.codecogs.com/png.latex?m%5Ctimes%20n"></td>
</tr>
<tr class="even">
<td>Symmetric eigen</td>
<td><img src="https://latex.codecogs.com/png.latex?%5Csim%209n%5E3"></td>
<td>square symmetric</td>
</tr>
<tr class="odd">
<td>Full SVD</td>
<td><img src="https://latex.codecogs.com/png.latex?%5Csim%2021n%5E3"></td>
<td><img src="https://latex.codecogs.com/png.latex?O(%5Cmin(mn%5E2,m%5E2n))"></td>
</tr>
<tr class="even">
<td>NMF</td>
<td><img src="https://latex.codecogs.com/png.latex?O(t%5C,k%5C,n%5E2)"></td>
<td><img src="https://latex.codecogs.com/png.latex?O(t%5C,k%5C,mn)">; iterative, not a direct factor</td>
</tr>
</tbody>
</table>
</div>
</figure>
</div>
<div id="cost-widget">

</div>
<p>Pick a job. Each bar is a flop-count model at the current <img src="https://latex.codecogs.com/png.latex?n">; a blank row is not defined for that matrix. Color is the method’s name, held across jobs, not a heat scale. The ▸ is the pick. The <img src="https://latex.codecogs.com/png.latex?m/n"> slider appears on tall least squares; <img src="https://latex.codecogs.com/png.latex?k"> and <img src="https://latex.codecogs.com/png.latex?t"> appear on nonnegative parts.</p>
<p>Jobs. Differ. Factors. Follow. Applications. Choose. The. Inverse. That. Fits.</p>
</section>
<section id="references" class="level2" data-number="10">
<h2 data-number="10" class="anchored" data-anchor-id="references"><span class="header-section-number">10</span> References</h2>
<ul>
<li>Golub, G. H., and Van Loan, C. F. <a href="https://www.amazon.com/dp/1421407949"><em>Matrix Computations</em></a> (4th ed., Johns Hopkins, 2013). Leading-term flop counts.</li>
<li>Trefethen, L. N., and Bau, D. <a href="https://www.amazon.com/dp/1611977150"><em>Numerical Linear Algebra</em></a> (25th anniversary ed., SIAM, 2022). QR, SVD, and <img src="https://latex.codecogs.com/png.latex?%5Ckappa(X%5ETX)=%5Ckappa(X)%5E2">.</li>
<li>Efron, B., Hastie, T., Johnstone, I., and Tibshirani, R. (2004). <a href="https://doi.org/10.1214/009053604000000067">Least angle regression</a>. <em>Annals of Statistics</em> 32(2). Diabetes data via <a href="https://scikit-learn.org/stable/modules/generated/sklearn.datasets.load_diabetes.html"><code>sklearn.datasets.load_diabetes</code></a>.</li>
<li><a href="https://www.nasdaq.com/market-activity/quotes/historical">Nasdaq.com historical quotes</a> — <code>META</code>, <code>AAPL</code>, <code>AMZN</code>, <code>NFLX</code>, <code>GOOGL</code>, <code>NVDA</code>, <code>AZN</code>, 2022-09-01 to 2026-09-01 (<code>prices.csv</code>).</li>
<li><a href="https://scikit-image.org/docs/stable/api/skimage.data.html#skimage.data.astronaut"><code>skimage.data.astronaut()</code></a> — <a href="https://flic.kr/p/r9qvLn">NASA public-domain photograph of Eileen Collins</a>.</li>
<li><a href="../matrix-factorizations/">Matrix Factorizations as Optimization Problems</a> — QR conditioning, NMF on Golub leukemia.</li>
<li><a href="../eigendecomposition/">The Directions a Matrix Refuses to Turn</a> — eigenvectors as unrotated directions.</li>
<li><a href="../svd-rotate-stretch-rotate/">The Matrix That Rotates, Stretches, and Rotates Again</a> — SVD geometry and the astronaut widget.</li>
</ul>
<script src="widgets.js"></script>


</section>

 ]]></description>
  <category>Linear Algebra</category>
  <category>Machine Learning</category>
  <category>Numerical Methods</category>
  <guid>https://project-delphi.github.io/ml-blog/posts/why-so-many-matrix-factorizations/</guid>
  <pubDate>Thu, 03 Sep 2026 05:00:00 GMT</pubDate>
  <media:content url="https://project-delphi.github.io/ml-blog/posts/why-so-many-matrix-factorizations/cover.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Claude Agent SDK in Python</title>
  <dc:creator>Ravi Kalia</dc:creator>
  <link>https://project-delphi.github.io/ml-blog/posts/claude-agent-sdk-python/</link>
  <description><![CDATA[ 





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/claude-agent-sdk-python/cover.png" class="img-fluid figure-img"></p>
<figcaption>Claude Agent SDK in Python</figcaption>
</figure>
</div>
<p><code>messages.create()</code> returns one model turn. An agent that reads a file, edits it, and runs a test needs many turns. You append the <code>tool_use</code>, run the function, append the <code>tool_result</code>, and call again.</p>
<p>The Agent SDK wraps Claude Code so you skip that loop. Use <code>query()</code> for one shot. Use <code>ClaudeSDKClient</code> to keep talking.</p>
<p><code>query()</code> starts a child process, works, and exits. <code>ClaudeSDKClient</code> starts the process once and leaves it up. A later prompt can say “the first one”.</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">%%{init: {"theme": "base", "themeVariables": {"fontFamily": "ui-sans-serif, system-ui, sans-serif", "lineColor": "#C96442", "edgeLabelBackground": "#FAF7F2", "clusterBkg": "#F4F0E8", "clusterBorder": "#C96442"}}}%%
flowchart TB
  Prompt["user prompt"] --&gt; Options["ClaudeAgentOptions"]
  Options --&gt; Query["query() — new session"]
  Options --&gt; Client["ClaudeSDKClient — held session"]
  Query --&gt; Create["messages.create"]
  Client --&gt; Create
  Create --&gt; Stop{"stop_reason"}
  Stop --&gt;|"tool_use"| Tools["run Read / Edit / Bash"]
  Tools --&gt; Append["append tool_result"]
  Append --&gt; Create
  Stop --&gt;|"end_turn"| Result["ResultMessage"]

  classDef cream fill:#F4F0E8,stroke:#C96442,stroke-width:2px,color:#191919
  classDef terracotta fill:#C96442,stroke:#8F3F24,stroke-width:2px,color:#FAF7F2
  classDef dark fill:#191919,stroke:#C96442,stroke-width:2px,color:#FAF7F2
  classDef peach fill:#E8A087,stroke:#C96442,stroke-width:2px,color:#191919

  class Prompt,Options cream
  class Query terracotta
  class Client dark
  class Create,Tools,Append peach
  class Stop,Result terracotta
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p><code>query()</code> shuts the child process down after one turn. The client keeps it. The diamond is <code>stop_reason</code>. Loop while the model wants a tool. Stop when it is done.</p>
<section id="agent-sdk" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="agent-sdk"><span class="header-section-number">1</span> Agent SDK</h2>
<p>Claude Code already does this in a terminal. You type a prompt. Claude reads a file or runs a command. It looks at the output. It decides whether to continue. The Agent SDK starts that process from Python. The package is <code>claude-agent-sdk</code>.</p>
<p>The child process:</p>
<ul>
<li>starts and stops Claude Code</li>
<li>offers <code>Read</code>, <code>Edit</code>, <code>Write</code>, <code>Bash</code>, <code>Glob</code>, <code>Grep</code></li>
<li>checks permissions (<code>permission_mode</code>, <code>allowed_tools</code>)</li>
<li>calls the model again after each tool result</li>
</ul>
<p><code>messages.create()</code> is one HTTP request. You send the history. You get one turn back. Claude does not open a shell. It does not edit files. It does not call itself again. A <code>tool_use</code> block means you run the function and send the next request. That package is <a href="../claude-api-python-sdk/">the Anthropic Python SDK</a>. Write the loop there. Let Claude Code write it here.</p>
</section>
<section id="query-vs-claudesdkclient" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="query-vs-claudesdkclient"><span class="header-section-number">2</span> query vs ClaudeSDKClient</h2>
<p>Two ways in. Both take <code>ClaudeAgentOptions</code>: system prompt, tools, permissions. Both are <code>asyncio</code>. Wrap them in <code>async def main()</code> and start with <code>asyncio.run(main())</code>. That <code>asyncio.run</code> starts the event loop. It is not a client method.</p>
<ul>
<li><strong><code>query()</code></strong> — one prompt, no follow-up. Opens a session, works, closes. No <code>interrupt()</code>. To continue later, pass <code>continue_conversation=True</code> or a <code>resume</code> id.</li>
<li><strong><code>ClaudeSDKClient</code></strong> — the next prompt depends on the last answer. The <code>async with</code> block holds the connection. A second <code>client.query()</code> keeps the session. No <code>resume</code>. <code>interrupt()</code> works.</li>
</ul>
<p>Open the client with <code>async with ClaudeSDKClient(options) as client</code>. Python connects on enter and disconnects on leave. After <code>client.query()</code>, read <code>receive_response()</code> until the turn ends. Then send the next prompt.</p>
<section id="run" class="level3" data-number="2.1">
<h3 data-number="2.1" class="anchored" data-anchor-id="run"><span class="header-section-number">2.1</span> run</h3>
<p>Other libraries ship <code>client.run(prompt)</code>. One call sends and waits. <code>ClaudeSDKClient</code> has no <code>.run()</code>. It splits the turn so you can interrupt.</p>
<ul>
<li><strong><code>await client.query(prompt)</code></strong> — write the user turn. Returns when the write lands. Tools may still be running.</li>
<li><strong><code>async for message in client.receive_response()</code></strong> — read until <code>ResultMessage</code>.</li>
</ul>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">%%{init: {"theme": "base", "themeVariables": {"fontFamily": "ui-sans-serif, system-ui, sans-serif", "lineColor": "#C96442", "edgeLabelBackground": "#FAF7F2", "clusterBkg": "#F4F0E8", "clusterBorder": "#C96442"}}}%%
flowchart TB
  subgraph missing ["Guessed one-shot"]
    Run["client.run(prompt)"]
  end
  subgraph present ["Actual turn"]
    Q["query(prompt) — write"]
    Gap["interrupt() window"]
    Recv["receive_response() — wait"]
    Done["ResultMessage"]
    Q --&gt; Gap --&gt; Recv --&gt; Done
  end
  Run -.-&gt; Q

  classDef cream fill:#F4F0E8,stroke:#C96442,stroke-width:2px,color:#191919
  classDef terracotta fill:#C96442,stroke:#8F3F24,stroke-width:2px,color:#FAF7F2
  classDef dark fill:#191919,stroke:#C96442,stroke-width:2px,color:#FAF7F2
  classDef peach fill:#E8A087,stroke:#C96442,stroke-width:2px,color:#191919

  class Run peach
  class Q terracotta
  class Gap dark
  class Recv peach
  class Done terracotta
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p>The dashed edge is <code>run()</code>. It is missing. Send, optionally cancel, then wait.</p>
</section>
</section>
<section id="one-shot-query" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="one-shot-query"><span class="header-section-number">3</span> One-shot query</h2>
<p>Use <code>query()</code> for one prompt. <code>ClaudeAgentOptions</code> sets the system prompt, the tools, and whether edits apply without asking.</p>
<div id="6252e7c6" class="cell" data-execution_count="1">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> asyncio</span>
<span id="cb1-2"></span>
<span id="cb1-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> claude_agent_sdk <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> (</span>
<span id="cb1-4">    AssistantMessage,</span>
<span id="cb1-5">    ClaudeAgentOptions,</span>
<span id="cb1-6">    ResultMessage,</span>
<span id="cb1-7">    TextBlock,</span>
<span id="cb1-8">    query,</span>
<span id="cb1-9">)</span>
<span id="cb1-10"></span>
<span id="cb1-11"></span>
<span id="cb1-12"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> main():</span>
<span id="cb1-13">    options <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ClaudeAgentOptions(</span>
<span id="cb1-14">        system_prompt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are a Python reviewer. Edit only what the prompt names."</span>,</span>
<span id="cb1-15">        allowed_tools<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Read"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Edit"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Glob"</span>],</span>
<span id="cb1-16">        permission_mode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"acceptEdits"</span>,</span>
<span id="cb1-17">    )</span>
<span id="cb1-18">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> message <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> query(</span>
<span id="cb1-19">        prompt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Add a one-line module docstring to src/auth.py"</span>,</span>
<span id="cb1-20">        options<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>options,</span>
<span id="cb1-21">    ):</span>
<span id="cb1-22">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(message, AssistantMessage):</span>
<span id="cb1-23">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> block <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> message.content:</span>
<span id="cb1-24">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(block, TextBlock):</span>
<span id="cb1-25">                    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(block.text)</span>
<span id="cb1-26">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(message, ResultMessage):</span>
<span id="cb1-27">            <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(message.subtype, message.total_cost_usd)</span>
<span id="cb1-28"></span>
<span id="cb1-29"></span>
<span id="cb1-30">asyncio.run(main())</span></code></pre></div>
</details>
</div>
<p>Hand-written. No <code>ANTHROPIC_API_KEY</code>; the cell does not run.</p>
<pre><code>AssistantMessage(
    content=[
        TextBlock(
            type='text',
            text='Added a module docstring to src/auth.py.',
        ),
    ],
)
ResultMessage(
    subtype='success',
    total_cost_usd=0.0421,
)</code></pre>
<p><code>query()</code> starts Claude Code. It can <code>Read</code> and <code>Edit</code> <code>src/auth.py</code>. Text streams out. <code>ResultMessage</code> ends the loop. You never check <code>stop_reason</code>. The child process already did.</p>
</section>
<section id="persistent-client" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="persistent-client"><span class="header-section-number">4</span> Persistent client</h2>
<p>List the files under <code>src/</code>. Then ask Claude to open the first one. The second prompt names no path. Claude needs the first answer to pick <code>src/auth.py</code>.</p>
<p><code>ClaudeSDKClient</code> keeps the session. Stay in the <code>async with</code> block. Each <code>client.query()</code> continues the conversation. Send with <code>query()</code>. Wait with <code>receive_response()</code>.</p>
<div id="b8518e3a" class="cell" data-execution_count="2">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> asyncio</span>
<span id="cb3-2"></span>
<span id="cb3-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> claude_agent_sdk <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> (</span>
<span id="cb3-4">    AssistantMessage,</span>
<span id="cb3-5">    ClaudeAgentOptions,</span>
<span id="cb3-6">    ClaudeSDKClient,</span>
<span id="cb3-7">    TextBlock,</span>
<span id="cb3-8">)</span>
<span id="cb3-9"></span>
<span id="cb3-10"></span>
<span id="cb3-11"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> main():</span>
<span id="cb3-12">    options <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ClaudeAgentOptions(</span>
<span id="cb3-13">        allowed_tools<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Read"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Glob"</span>],</span>
<span id="cb3-14">        permission_mode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"acceptEdits"</span>,</span>
<span id="cb3-15">    )</span>
<span id="cb3-16">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">with</span> ClaudeSDKClient(options<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>options) <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> client:</span>
<span id="cb3-17">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> client.query(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"List the Python files under src/"</span>)</span>
<span id="cb3-18">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> message <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> client.receive_response():</span>
<span id="cb3-19">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(message, AssistantMessage):</span>
<span id="cb3-20">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> block <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> message.content:</span>
<span id="cb3-21">                    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(block, TextBlock):</span>
<span id="cb3-22">                        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(block.text)</span>
<span id="cb3-23"></span>
<span id="cb3-24">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> client.query(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Open the first one and quote its module docstring."</span>)</span>
<span id="cb3-25">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> message <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> client.receive_response():</span>
<span id="cb3-26">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(message, AssistantMessage):</span>
<span id="cb3-27">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> block <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> message.content:</span>
<span id="cb3-28">                    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(block, TextBlock):</span>
<span id="cb3-29">                        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(block.text)</span>
<span id="cb3-30"></span>
<span id="cb3-31"></span>
<span id="cb3-32">asyncio.run(main())</span></code></pre></div>
</details>
</div>
<p>Hand-written. The second answer names <code>src/auth.py</code>. The prompt did not.</p>
<p>First turn:</p>
<pre><code>Claude: src/auth.py
src/app.py</code></pre>
<p>Second turn:</p>
<pre><code>Claude: src/auth.py starts with: """Session tokens and password checks."""</code></pre>
<p>Two standalone <code>query()</code> calls start two sessions. The second has no file list. Pass <code>resume</code> or <code>continue_conversation=True</code>, or name <code>src/auth.py</code>.</p>
</section>
<section id="messages-api-loop" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="messages-api-loop"><span class="header-section-number">5</span> Messages API loop</h2>
<p><code>query()</code> and the client still call <code>messages.create()</code>. You or Claude Code must keep the <code>messages</code> list and run the tools.</p>
<p>Write the loop and you do eight steps each time the model wants a tool:</p>
<ol type="1">
<li>Call <code>client.messages.create(...)</code> with <code>tools</code> and the current <code>messages</code> list.</li>
<li>Read <code>response.stop_reason</code>.</li>
<li>If it is <code>"tool_use"</code>, collect every <code>tool_use</code> block.</li>
<li>Run each named function on <code>block.input</code>.</li>
<li>Append the assistant turn (<code>response.content</code>) to <code>messages</code>.</li>
<li>Append a user turn whose content is the matching <code>tool_result</code> blocks.</li>
<li>Call <code>messages.create()</code> again with that longer list.</li>
<li>Repeat from step 2 until <code>stop_reason</code> is not <code>"tool_use"</code>.</li>
</ol>
<div id="2f97c823" class="cell" data-execution_count="3">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> asyncio</span>
<span id="cb6-2"></span>
<span id="cb6-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> anthropic</span>
<span id="cb6-4"></span>
<span id="cb6-5">POLICIES <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb6-6">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"standard"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Refunds within 30 days of purchase, unused items only."</span>,</span>
<span id="cb6-7">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"plus"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Refunds within 60 days; opened software is excluded."</span>,</span>
<span id="cb6-8">}</span>
<span id="cb6-9"></span>
<span id="cb6-10"></span>
<span id="cb6-11"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> lookup_policy(plan: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb6-12">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> POLICIES.get(plan, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"No policy found for that plan."</span>)</span>
<span id="cb6-13"></span>
<span id="cb6-14"></span>
<span id="cb6-15"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> main():</span>
<span id="cb6-16">    client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> anthropic.AsyncAnthropic()</span>
<span id="cb6-17">    tools <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb6-18">        {</span>
<span id="cb6-19">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"name"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lookup_policy"</span>,</span>
<span id="cb6-20">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"description"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Return the refund policy text for a subscription plan."</span>,</span>
<span id="cb6-21">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"input_schema"</span>: {</span>
<span id="cb6-22">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"type"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"object"</span>,</span>
<span id="cb6-23">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"properties"</span>: {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"plan"</span>: {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"type"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"string"</span>}},</span>
<span id="cb6-24">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"required"</span>: [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"plan"</span>],</span>
<span id="cb6-25">            },</span>
<span id="cb6-26">        }</span>
<span id="cb6-27">    ]</span>
<span id="cb6-28">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"What is the refund window on Plus?"</span>}]</span>
<span id="cb6-29"></span>
<span id="cb6-30">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> client.messages.create(</span>
<span id="cb6-31">        model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"claude-sonnet-5"</span>,</span>
<span id="cb6-32">        max_tokens<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1024</span>,</span>
<span id="cb6-33">        tools<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>tools,</span>
<span id="cb6-34">        messages<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>messages,</span>
<span id="cb6-35">    )</span>
<span id="cb6-36"></span>
<span id="cb6-37">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">while</span> response.stop_reason <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tool_use"</span>:</span>
<span id="cb6-38">        messages.append({<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"assistant"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: response.content})</span>
<span id="cb6-39">        results <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb6-40">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> block <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> response.content:</span>
<span id="cb6-41">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> block.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tool_use"</span>:</span>
<span id="cb6-42">                results.append(</span>
<span id="cb6-43">                    {</span>
<span id="cb6-44">                        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"type"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tool_result"</span>,</span>
<span id="cb6-45">                        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tool_use_id"</span>: block.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">id</span>,</span>
<span id="cb6-46">                        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: lookup_policy(block.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">input</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"plan"</span>]),</span>
<span id="cb6-47">                    }</span>
<span id="cb6-48">                )</span>
<span id="cb6-49">        messages.append({<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: results})</span>
<span id="cb6-50">        response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> client.messages.create(</span>
<span id="cb6-51">            model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"claude-sonnet-5"</span>,</span>
<span id="cb6-52">            max_tokens<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1024</span>,</span>
<span id="cb6-53">            tools<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>tools,</span>
<span id="cb6-54">            messages<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>messages,</span>
<span id="cb6-55">        )</span>
<span id="cb6-56"></span>
<span id="cb6-57"></span>
<span id="cb6-58">asyncio.run(main())</span></code></pre></div>
</details>
</div>
<p>The refund rows are made up. They show block shape, not a real policy store.</p>
<p><code>query()</code> and <code>ClaudeSDKClient</code> run those eight steps inside Claude Code. Set <code>allowed_tools</code>. Read <code>AssistantMessage</code> and <code>ResultMessage</code>. <a href="../claude-api-python-sdk/">The Anthropic Python SDK post</a> lists the six <code>stop_reason</code> values. <a href="../claude-architect-prep-week-1/">The orchestrator-worker loop</a> writes the branch.</p>
<p>Query. Starts. Fresh. Client. Holds. Context. Loop. Stays. Hidden.</p>
</section>
<section id="references" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="references"><span class="header-section-number">6</span> References</h2>
<ul>
<li><a href="https://code.claude.com/docs/en/agent-sdk/python">Agent SDK reference — Python</a></li>
<li><a href="https://code.claude.com/docs/en/agent-sdk/sessions">Agent SDK: Sessions</a></li>
<li><a href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/overview">Anthropic: Tool use</a></li>
<li><a href="../claude-api-python-sdk/">Claude API with the Anthropic Python SDK</a> — this blog; <code>messages.create()</code> and the six <code>stop_reason</code> values.</li>
<li><a href="../claude-architect-prep-week-1/">Claude Certified Architect, Week 1: The Orchestrator-Worker Loop</a> — this blog; the loop that branches on every <code>stop_reason</code>.</li>
</ul>


</section>

 ]]></description>
  <category>Agents</category>
  <category>AI</category>
  <category>Claude</category>
  <category>Python</category>
  <guid>https://project-delphi.github.io/ml-blog/posts/claude-agent-sdk-python/</guid>
  <pubDate>Mon, 31 Aug 2026 05:00:00 GMT</pubDate>
  <media:content url="https://project-delphi.github.io/ml-blog/posts/claude-agent-sdk-python/cover.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Fingerprint Algorithms: From Ridge Formation to On-Device Embeddings</title>
  <dc:creator>Ravi Kalia</dc:creator>
  <link>https://project-delphi.github.io/ml-blog/posts/fingerprint-algorithms/</link>
  <description><![CDATA[ 





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/fingerprint-algorithms/cover.png" class="img-fluid figure-img"></p>
<figcaption>Fingerprint Algorithms: From Ridge Formation to On-Device Embeddings</figcaption>
</figure>
</div>
<p>You unlock a phone with your thumb a hundred times a week. It works when your thumb is wet, turned forty degrees off, pressed too hard, or only half on the glass. It fails when you offer a different finger.</p>
<p>The strange part is that the phone has no picture of your thumb. There is nothing in it to compare a photograph against. At enrolment it threw the picture away and kept something much smaller, and every unlock since has been a comparison between two of those smaller things.</p>
<p>So the interesting question is not “how do you match a fingerprint”. It is <strong>what do you keep</strong>, and what survives when the capture is bad. This post builds four answers to that question, using 397 real inked fingers from NIST, and scores all four against one shared gallery of two hundred of them. One of the four is a modern metric-learning network. It does not win, and the reason it does not win turns out to be the most useful thing here.</p>
<p>If you have trained a classifier, you already have most of the machinery for this. Matching <em>is</em> classification: each enrolled finger is a class, and a probe image has to be assigned to one of them. What makes it strange is the shape of the training data. There is one example per class, the classes are created after the model ships — you enrol a new thumb and nobody retrains anything — and there are as many classes as there are enrolled fingers. Those three conditions break the usual recipe of a softmax over a fixed label set, which is why the work moves out of the decision rule and into the representation. So the post compares four answers to “what do you keep”, under one decision rule held fixed.</p>
<section id="a-pattern-nobody-chose-not-even-your-genome" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="a-pattern-nobody-chose-not-even-your-genome"><span class="header-section-number">1</span> A pattern nobody chose, not even your genome</h2>
<p>Friction ridges form on the volar pads between roughly week 10 and week 17 of gestation. The basal layer of the epidermis is a stiff sheet growing on a soft foundation, and it is being compressed as the pad underneath it swells and then regresses. A stiff sheet under compression on a soft bed does not stay flat. It buckles, into a regular corrugation, at a wavelength set by the mechanics rather than by any instruction.</p>
<p>Kücken and Newell (2005) fit that picture to fetal pad geometry: ridge direction follows the lines of the compressive stress field, and the pattern type — loop, whorl, arch — falls out of the pad’s shape and timing. The same periodic stripes can be produced by a Turing reaction–diffusion system, which is why chemistry is the intuitive guess, but the model that matches the embryology is mechanical.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Where the ridge spacing comes from
</div>
</div>
<div class="callout-body-container callout-body">
<p>For a stiff film of thickness <img src="https://latex.codecogs.com/png.latex?h"> and modulus <img src="https://latex.codecogs.com/png.latex?E_f"> bonded to a soft substrate of modulus <img src="https://latex.codecogs.com/png.latex?E_s">, compression buckles the film at the wavelength that minimises the total energy — bending the film wants long waves, deforming the substrate wants short ones. The balance gives the classical wrinkling result</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Clambda%20=%202%5Cpi%20h%20%5Cleft(%5Cfrac%7BE_f%7D%7B3E_s%7D%5Cright)%5E%7B1/3%7D.%0A"></p>
<p>What matters is not the constant but that <img src="https://latex.codecogs.com/png.latex?%5Clambda"> depends only on a thickness and a stiffness ratio, so a patch of skin has a ridge period whether or not anything told it what period to have. Every filter in this post has to be tuned to that period, and measuring it per print is the first thing the code does.</p>
</div>
</div>
<p>Once the epidermis keratinises the pattern is fixed for life, scars aside. And identical twins, who share a genome, do not share ridge detail — the minute arrangement of endings and forks differs between them, and between your own two index fingers. That is what makes this a usable identifier: the thing being measured is a developmental accident, not an inherited plan, so it is effectively a per-finger random draw that then never changes.</p>
<p>A <a href="../what-is-a-biological-signature/">biological signature</a> in the molecular sense is a short list of features that stands in for a condition. A fingerprint template is the same move performed on skin. The engineering problem is to make the standing-in deterministic, so that two captures of one finger reduce to the same object and two captures of different fingers do not.</p>
</section>
<section id="fingers-twice-each-and-what-it-cost-to-standardise-them" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="fingers-twice-each-and-what-it-cost-to-standardise-them"><span class="header-section-number">2</span> 397 fingers, twice each, and what it cost to standardise them</h2>
<p>Everything measured here comes from one cache, so here is what is in it before anything is asked of it.</p>
<ul>
<li><strong>What it is.</strong> NIST’s MINEX III validation imagery: 801 raw 8-bit grayscale scans of inked fingerprint cards at 500 dpi, from the <code>usnistgov/minex</code> repository (commit <code>dc57b22</code>), released into the public domain as a US Government work. Retrieved 2026-09-12; the built cache is 20.3 MB, sha256 <code>426f1ccf…</code>.</li>
<li><strong>Who made it and why.</strong> NIST publishes it so that vendors’ minutiae extractors and matchers can be validated against a common set before certification. It exists <em>to be a benchmark</em>, which is exactly why it is safe to use as one, and why the results here are not a comment on anyone’s product.</li>
<li><strong>The structure that makes it a test.</strong> Files are named <code>&lt;set&gt;&lt;subject&gt;_&lt;position&gt;</code> — <code>a001_02</code> is set A, subject 001, right index. Sets A and B are two separate impressions of the same finger. Two impressions is the minimum that makes identification measurable: enrol one, search with the other, and ask which enrolled finger comes back first. 397 fingers have both impressions, giving 794 images; the remaining seven scans have no mate in the other set and are dropped.</li>
<li><strong>What this post asks of it.</strong> Rank-1 identification against a gallery of two hundred fingers, and an equal error rate for the one-against-one case. Nothing about latents, and nothing about live sensors — these are inked cards.</li>
<li><strong>What a wrong answer costs.</strong> In the deployed version of this problem, a false accept opens someone else’s phone and a false reject locks you out of your own. Those two errors trade against each other, which is why one accuracy number is never enough and the tables here carry both.</li>
</ul>
<p>Each scan is put on one scale and one centre by <code>src/fetch_data.py</code>, and that step makes a trade which bites one of the four methods later:</p>
<div id="dd386c45" class="cell" data-execution_count="1">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># src/fetch_data.py -- standardise(), abridged</span></span>
<span id="cb1-2">own <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ridges.analyse(img).period        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># this print's own ridge period</span></span>
<span id="cb1-3">zoom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> period <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> own                     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># resample every print to a common period</span></span>
<span id="cb1-4">source <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> img.astype(np.float64)</span>
<span id="cb1-5"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> zoom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:</span>
<span id="cb1-6">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Anti-alias before shrinking. Resampling a 10-pixel ridge period down to 6</span></span>
<span id="cb1-7">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># without it folds the ridges back as high-frequency noise.</span></span>
<span id="cb1-8">    source <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ndimage.gaussian_filter(source, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> zoom)</span>
<span id="cb1-9">scaled <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.clip(ndimage.zoom(source, zoom, order<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">255</span>).astype(np.uint8)</span></code></pre></div>
</details>
</div>
<p>Two things vary between cards for reasons that have nothing to do with whose finger it is: how large the print was rolled, and where on the card it landed. Both are removed — every print is resampled to a ridge period of 6 pixels and cropped to a 192-pixel square centred on its inked area. Everything a matcher has to survive is left in: rotation, ink coverage, elastic distortion of the skin, damage, and how much of the finger the roll caught.</p>
<p>The cost is resolution. The source scans run a median ridge period of 11.5 pixels and the cache halves that, which keeps the whole print inside a small square — necessary, because the methods that measure global ridge flow lose about four times the accuracy on a sensor-sized window at full resolution. Ridge <em>flow</em> survives the resampling comfortably. Individual ridge endings and forks do not, and that is a debt the landmark methods pay in full.</p>
<p>One more thing about the data, because it explains a lot of the spread later: this imagery is not uniformly good. Over 80 randomly drawn prints, the ridge mask covers a median 56% of the frame, seven prints fall under 10%, and on three of them the analysis finds no reliable ridge region at all. NIST’s own quality grade correlates only about 0.56 with that coverage, so grade is informative and not decisive.</p>
</section>
<section id="the-task-is-classification-with-one-example-per-class" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="the-task-is-classification-with-one-example-per-class"><span class="header-section-number">3</span> The task is classification with one example per class</h2>
<p>Two impressions per finger is what makes the problem measurable, and it also fixes the vocabulary every table later is read in.</p>
<p>One impression of each finger is <strong>enrolled</strong> — stored, and treated as the reference copy. The stored set is the <strong>gallery</strong>. The other impression is the <strong>probe</strong>: the image handed over with the question “whose is this?”. A probe scored against its own finger’s gallery entry is a <strong>genuine pair</strong>, and against any other entry an <strong>impostor pair</strong>. The entry that belongs to the probe is its <strong>mate</strong>.</p>
<p>Put that way, identification is a classification problem with the labels supplied by the gallery. There are 199 test fingers — the 397 are split in half by finger, and the half used for scoring is the half nothing was fitted on — so there are 199 classes, the gallery holds one labelled example of each, and the probe is the point to be labelled. A method answers by scoring the probe against all 199 entries and sorting them.</p>
<p>Three of the numbers in the results tables are then familiar quantities under biometric names:</p>
<ul>
<li><strong>Rank-1</strong> is top-1 accuracy: how often the highest-scoring entry is the mate.</li>
<li><strong>Rank-5</strong> is top-5 accuracy: how often the mate is anywhere in the first five.</li>
<li><strong>Median rank</strong> is where the mate typically lands in the sorted 199. It reports what rank-1 hides — a method can almost never be right first and still put the mate reliably near the top, or scatter it.</li>
</ul>
<p>With 199 classes and one example each, guessing gives a rank-1 of <img src="https://latex.codecogs.com/png.latex?1/199%20=%200.005">. Anything near half a percent is a method saying nothing.</p>
<p>The obvious next move would be to train a 199-way softmax on the gallery and read off the argmax. No method here does that, for two reasons that belong to the deployed problem rather than to this dataset. There is one example per class, so a per-class decision boundary has nothing to be fitted to. And the label set is not fixed: enrolling a finger creates a class, and no phone retrains a network when someone adds a thumb. A method that must be refitted whenever the classes change cannot ship.</p>
<p>So the decision rule is nearest neighbour, and it is held fixed for all four methods: encode the probe, score it against every gallery entry with whatever similarity the method defines, take the best. The comparison in this post is never between classifiers. It is between the representations that similarity is computed on, which is what “what do you keep” is asking.</p>
<section id="the-other-question-is-a-binary-classifier-over-pairs" class="level3" data-number="3.1">
<h3 data-number="3.1" class="anchored" data-anchor-id="the-other-question-is-a-binary-classifier-over-pairs"><span class="header-section-number">3.1</span> The other question is a binary classifier over pairs</h3>
<p>A phone is not searching a database. It has a claim — this is the enrolled thumb — and has to answer yes or no. That is <strong>verification</strong>, and it is binary classification over <em>pairs</em>: genuine or impostor, decided by a threshold on the score.</p>
<p>Two errors trade against each other there. Set the threshold low and impostor pairs get accepted; set it high and genuine pairs get rejected. Sweeping the threshold traces the <strong>false accept rate</strong> up as the <strong>false reject rate</strong> comes down, and the <strong>equal error rate</strong> is the error rate at the threshold where those two meet — one number summarising the trade. Lower is better, and 0.5 means the threshold is doing no work at all.</p>
<p><strong>d′</strong> asks a blunter question: is there a usable threshold at all? Score every genuine pair and every impostor pair and you have two distributions. d′ is the gap between their means, measured in pooled standard deviations. Near 0 the two sit on top of each other, and no threshold separates them however the ranking looks.</p>
<p>The identification numbers here are <strong>closed-set</strong>: the probe’s finger is always somewhere in the gallery, so a right answer always exists to be found. Searching a gallery that might not hold the probe’s finger at all is open-set identification, and it is strictly harder, because the system also has to decide when its best match is still not good enough. That decision is a threshold — the same instrument verification needs, which is why a search system ends up caring about both columns.</p>
</section>
<section id="four-answers-keeping-less-each-time" class="level3" data-number="3.2">
<h3 data-number="3.2" class="anchored" data-anchor-id="four-answers-keeping-less-each-time"><span class="header-section-number">3.2</span> Four answers, keeping less each time</h3>
<p>With the decision rule fixed, what remains is the representation, and the four built here keep progressively less of the image:</p>
<ol type="1">
<li><strong>The pixels themselves</strong>, raw and then enhanced.</li>
<li><strong>A fixed-length measurement of ridge flow</strong>, cell by cell.</li>
<li><strong>A list of landmark points</strong>, with the image thrown away.</li>
<li><strong>128 floats a network chose</strong>, with nobody telling it what a ridge is.</li>
</ol>
<p>Three of the four need to know which way the ridges run before they can start.</p>
</section>
</section>
<section id="everything-downstream-needs-to-know-which-way-the-ridges-run" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="everything-downstream-needs-to-know-which-way-the-ridges-run"><span class="header-section-number">4</span> Everything downstream needs to know which way the ridges run</h2>
<p>Before any method can describe a print, it has to answer a smaller question at every point: which direction do the ridges run here, and how far apart are they? The answer is computed once and shared, so every method that needs it is reading the same geometry.</p>
<p>Direction is the awkward part, because a ridge has no arrowhead. A ridge running north-east is the same ridge running south-west, so the two gradient directions <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> and <img src="https://latex.codecogs.com/png.latex?%5Ctheta%20+%20%5Cpi"> are the same answer. Averaging them naively cancels them out. The fix is to average the <em>doubled</em> angle, where the two agree instead of opposing:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctheta%20=%20%5Cfrac%7B1%7D%7B2%7D%5Coperatorname%7Batan2%7D%5C!%5Cleft(2%5Csum%20g_x%20g_y,%5C;%20%5Csum%5Cleft(g_x%5E2%20-%20g_y%5E2%5Cright)%5Cright)%20+%20%5Cfrac%7B%5Cpi%7D%7B2%7D.%0A"></p>
<div id="a7fd0113" class="cell" data-execution_count="2">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># src/ridges.py -- orientation()</span></span>
<span id="cb2-2">gy, gx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.gradient(ndimage.gaussian_filter(x, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>))</span>
<span id="cb2-3">gxx, gyy, gxy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> blocks(gx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> gx, block), blocks(gy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> gy, block), blocks(gx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> gy, block)</span>
<span id="cb2-4">num, den <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> gxy, gxx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> gyy</span>
<span id="cb2-5">theta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.arctan2(num, den) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> np.pi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># gradient normal -&gt; ridge tangent</span></span>
<span id="cb2-6">coherence <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.hypot(num, den) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (gxx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> gyy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-12</span>)</span></code></pre></div>
</details>
</div>
<p>That second line out is <code>coherence</code>: how single-minded the gradients in a block are. It runs near 1 on clean parallel ridges and near 0 on blank card, which makes it the natural measure of whether a block is worth trusting.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/fingerprint-algorithms/figures/ridge-geometry.png" class="img-fluid figure-img"></p>
<figcaption>The print, its orientation field, the ridge-strength map, and the mask of blocks holding friction ridges. Drawn from <code>a002_04</code>, whose mask covers 75% of the frame. The orientation field curves around the core of the loop, which is the structure every later method is reading.</figcaption>
</figure>
</div>
<p>With direction and period in hand, the enhancement step is a <strong>directional comb</strong>. A Gabor filter is a sinusoid wrapped in a Gaussian envelope — it has teeth at one spacing, running in one direction — and combing a patch of ridges along the direction they already run reinforces them, while noise that runs across the teeth is flattened:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ag(x,y;%5Clambda,%5Ctheta,%5Cpsi,%5Csigma,%5Cgamma)%0A=%5Cexp%5Cleft(-%5Cfrac%7Bx'%5E2+%5Cgamma%5E2%20y'%5E2%7D%7B2%5Csigma%5E2%7D%5Cright)%0A%5Ccos%5Cleft(2%5Cpi%5Cfrac%7Bx'%7D%7B%5Clambda%7D+%5Cpsi%5Cright),%0A"></p>
<p>with <img src="https://latex.codecogs.com/png.latex?x'=x%5Ccos%5Ctheta+y%5Csin%5Ctheta"> and <img src="https://latex.codecogs.com/png.latex?y'=-x%5Csin%5Ctheta+y%5Ccos%5Ctheta">. <img src="https://latex.codecogs.com/png.latex?%5Clambda"> is the ridge period, <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> the local orientation, <img src="https://latex.codecogs.com/png.latex?%5Csigma"> the envelope width. Use the wrong <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> and the comb fights the ridges instead of combing them, which is why the orientation field has to come first (Hong, Wan and Jain, 1998).</p>
<div id="5b776d78" class="cell" data-execution_count="3">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># src/enhance.py -- enhance(): filter with the whole bank, then select per pixel</span></span>
<span id="cb3-2">bank <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> gabor_bank(img, analysis)                  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># one filtered copy per orientation</span></span>
<span id="cb3-3">theta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> _fit(np.kron(analysis.theta, np.ones((BLOCK, BLOCK))), img.shape)</span>
<span id="cb3-4">idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(theta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (np.pi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> ORIENTATIONS)).astype(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%</span> ORIENTATIONS</span>
<span id="cb3-5"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> np.take_along_axis(bank, idx[<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span></code></pre></div>
</details>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/fingerprint-algorithms/figures/enhance-steps.png" class="img-fluid figure-img"></p>
<figcaption>Left to right the same print gets legible: the raw scan is faint and unevenly inked, the Gabor bank recovers continuous ridges, the crest map reduces each ridge to a line, and tracing plus gap-bridging closes most of the breaks. The visible improvement from the third panel to the fourth is the repair of ink gaps, which matters when the landmarks are extracted.</figcaption>
</figure>
</div>
</section>
<section id="keeping-the-picture-slide-the-two-prints-over-each-other" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="keeping-the-picture-slide-the-two-prints-over-each-other"><span class="header-section-number">5</span> Keeping the picture: slide the two prints over each other</h2>
<p>The first answer is the one that does the least. Keep the image, and compare two prints by how well they line up — no landmarks, no training, and in its plainest form no model of what a fingerprint is at all. That plain version is the control the other answers have to beat: a representation that keeps less than the picture has to earn what it threw away.</p>
<p>It runs here in two versions, and the second is not plain. One correlates the raw scans. The other correlates the same prints after the Gabor comb, so it does lean on the ridge geometry. Scoring both puts a number on what the enhancement alone is worth, with no descriptor involved — and the second version turns out to beat everything else in the post.</p>
<p>One thing has to be handled even so. Two impressions of a finger never land in the same place on the card, so comparing them pixel against pixel in place compares a ridge in one with a valley in the other and reports almost nothing. The repair is to let one image slide over the other and keep the best alignment found. Correlation at every possible shift costs one Fourier transform per image rather than one comparison per offset, so “try every alignment” is cheaper than it sounds:</p>
<div id="0c16a451" class="cell" data-execution_count="4">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># src/pixels.py -- match_all(): best shift-aligned correlation, via the FFT</span></span>
<span id="cb4-2">probe_f <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.fft.rfft2(probe)</span>
<span id="cb4-3">gallery_f <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.fft.rfft2(gallery, axes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb4-4">surface <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.fft.irfft2(gallery_f <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.conj(probe_f), s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>probe.shape, axes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb4-5"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> surface.reshape(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(gallery), <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span></code></pre></div>
</details>
</div>
<p>Sliding is allowed because the prints are centred on their inked area, which moves with how much of the finger each roll caught. Rotation is <em>not</em> allowed. Handling it here would mean rotating the probe, transforming it again, and taking a fresh inverse transform against every one of the 199 gallery entries — the entire search, once per angle tried. Being unable to afford that is why the later methods describe a print in terms that do not change when the finger turns.</p>
</section>
<section id="keeping-the-flow-comb-the-print-then-count-the-energy-cell-by-cell" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="keeping-the-flow-comb-the-print-then-count-the-energy-cell-by-cell"><span class="header-section-number">6</span> Keeping the flow: comb the print, then count the energy cell by cell</h2>
<p>Correlation compares two prints only as wholes. The next answer keeps a <em>description</em> instead — a fixed-length vector, computed from one print without reference to any other, so that comparing two prints is comparing two vectors. This is the familiar shape of a feature extractor, and it buys what a fixed-length vector always buys: a template of known size, and a comparison between two short vectors rather than between two images.</p>
<p>Jain, Prabhakar, Hong and Pankanti (2000) built one out of the filter bank already in hand, the FingerCode. Lay a polar tessellation over the print — concentric rings cut into wedges — and for each cell record how much energy each Gabor orientation puts into it. A cell where the ridges run north-east answers strongly in the north-east filter and weakly in the others, so the vector is a map of <em>which way the ridges run where</em>, at a fixed resolution. Concatenate the cells and the size is the same whatever the print looked like: here 4 rings cut into 16 wedges gives 64 cells, times 8 orientations.</p>
<p>The elegant part is what happens when the finger turns. Rotating the print moves energy around the tessellation in a completely predictable way: the wedges shift round by some number of sectors, and the ridge directions shift by a matching number of orientation bins. So comparing against a rotated version of a print costs two array rolls instead of re-filtering a rotated image:</p>
<div id="08667f47" class="cell" data-execution_count="5">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># src/enhance.py -- rotations(): every whole-sector rotation of a FingerCode</span></span>
<span id="cb5-2"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> s <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(sectors):</span>
<span id="cb5-3">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Sectors divide a full turn and orientations divide a half turn, so s</span></span>
<span id="cb5-4">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># sectors of rotation is s * 2 * orientations / sectors orientation bins.</span></span>
<span id="cb5-5">    bins <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(s <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> orientations <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> sectors)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%</span> orientations</span>
<span id="cb5-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">yield</span> np.roll(np.roll(code, s, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>), bins, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span></code></pre></div>
</details>
</div>
<p>That comment is load-bearing. Rolling the histogram by half as far as the tessellation leaves every candidate but the zero-rotation one comparing a turned tessellation against unturned ridge directions, and a print rotated 90° then scores 0.78 against itself instead of 1.00.</p>
<p>The claim is checkable, so the third panel checks it. Turn a print ninety degrees, recompute its descriptor from scratch, and score it against the original at all sixteen assumed rotations:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/fingerprint-algorithms/figures/fingercode.png" class="img-fluid figure-img"></p>
<figcaption>The polar tessellation over the print, its FingerCode as an 8-by-64 energy map, and the match score of the same print turned 90° against the original at each assumed rotation. The score peaks at exactly 90° and reaches 1.000, against 0.611 for comparing the two descriptors unrolled. Rotation really is an array roll. The lesser bump near 270° is the ambiguity from earlier coming back: ridge direction has no arrowhead, so a half-turn puts much of the energy back where it started.</figcaption>
</figure>
</div>
</section>
<section id="keeping-the-landmarks-throw-the-picture-away-and-keep-the-constellation" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="keeping-the-landmarks-throw-the-picture-away-and-keep-the-constellation"><span class="header-section-number">7</span> Keeping the landmarks: throw the picture away and keep the constellation</h2>
<p>The FingerCode still describes the print everywhere, at a fixed grid of cells. The third answer keeps far less: a few dozen points, and nothing else. This is what an examiner means by a fingerprint, and what every automated system meant by one until the 2010s.</p>
<p>The idea is that most of a print is redundant. Ridges run parallel over most of their length, and parallel ridges look like every other patch of parallel ridges. What distinguishes one finger is where that regularity <em>breaks</em> — a ridge stops, or a ridge splits. Record only those places, as a list of points <img src="https://latex.codecogs.com/png.latex?(x,%20y,%20%5Ctheta)"> with a position and the direction the ridge was heading, and throw the image away.</p>
<p>Two properties follow, and they are why this representation outlived the others. A list of points carries no orientation of its own, so rotating the finger rotates the list and nothing else — rotation is handled by construction rather than by searching. And a partial print still yields the landmarks it contains, so a fragment is a shorter list rather than a corrupted image.</p>
<p>Finding them is a local counting exercise. On a skeleton one pixel wide, walk the eight neighbours of a pixel in a ring and count how many times the ring flips between skeleton and background. In the middle of a ridge it flips twice. One flip is a ridge ending; three is a bifurcation:</p>
<div id="3d06b110" class="cell" data-execution_count="6">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># src/minutiae.py -- crossing_number(): half the flips around each pixel's ring</span></span>
<span id="cb6-2">ring <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.stack([np.roll(np.roll(skel, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>dy, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>dx, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> dy, dx <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> _RING])</span>
<span id="cb6-3">changes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(ring.astype(np.int8) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> np.roll(ring, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>).astype(np.int8))</span>
<span id="cb6-4"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> changes.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span></code></pre></div>
</details>
</div>
<p>Matching two such lists is the hard half, and the right way to picture it is <strong>constellation alignment</strong>. You have two star charts of the same patch of sky, taken with different cameras, at different rotations, with different amounts of cloud. Neither chart is complete and neither is quite to scale. You are not asked whether the two images look alike; you are asked whether the same <em>arrangement</em> of stars appears in both.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Registering constellations: Kendall shape and Procrustes distance
</div>
</div>
<div class="callout-body-container callout-body">
<p>Treat the minutiae as labelled landmarks. Two impressions of one finger differ by a translation, a rotation, and — after standardisation — very nearly a scale. Quotient those out and what is left is the print’s <em>shape</em> in the sense of <a href="../kendall-shape-analysis/">Kendall shape analysis</a>: a point in a space where two configurations are the same point when some similarity transform carries one to the other.</p>
<p>For landmark matrices <img src="https://latex.codecogs.com/png.latex?X"> and <img src="https://latex.codecogs.com/png.latex?Y">, centred and normalised to unit size, the Procrustes distance is the residual after the best rotation:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ad_P(X,%20Y)%20=%20%5Cmin_%7BR%20%5Cin%20SO(2)%7D%20%5ClVert%20X%20-%20Y%20R%20%5CrVert_F%20,%0A"></p>
<p>solved in closed form by the SVD of <img src="https://latex.codecogs.com/png.latex?Y%5E%5Ctop%20X">. It is the right way to compare two constellations — <em>if</em> you already know which star corresponds to which.</p>
<p>That proviso is the whole difficulty. Procrustes needs correspondences, and correspondences are what a fingerprint matcher does not have. So this method sidesteps the transform entirely: each minutia is described by its own neighbourhood in its own frame of reference — how far its neighbours sit, in what direction relative to the way it points, and which way they point. Such a description does not change when the finger rotates, so two prints are compared by matching descriptions rather than by searching for an alignment. That is the idea behind Cappelli, Ferrara and Maltoni’s Minutia Cylinder-Code (2010), in a smaller form.</p>
</div>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/fingerprint-algorithms/figures/minutiae.png" class="img-fluid figure-img"></p>
<figcaption>The two extractors on one print, and the mate impression. Crossing numbers on a thinned binary image find 20 landmarks, clustered where the ink is heavy. Ridge following finds 39, spread more evenly across the pattern; the mate gives 42. More landmarks, better spread — and, as the next section measures, no more repeatable for it.</figcaption>
</figure>
</div>
<section id="this-measures-the-extractor-not-minutiae-matching" class="level3" data-number="7.1">
<h3 data-number="7.1" class="anchored" data-anchor-id="this-measures-the-extractor-not-minutiae-matching"><span class="header-section-number">7.1</span> This measures the extractor, not minutiae matching</h3>
<p>Here is the uncomfortable result, stated before the table rather than after it. On this cache, the minutiae method identifies a few percent of probes. Plain correlation of the enhanced images — the picture with a comb run over it, no landmarks at all — identifies ten times as many.</p>
<p>That is not a fact about minutiae matching. Real automated fingerprint identification systems do well on exactly this imagery, which is why NIST published it. It is a fact about <em>this extractor</em>, and the way to show that is to measure the landmarks directly instead of arguing about the score.</p>
<p>The diagnostic is a gate, in the sense that an extractor failing it cannot be rescued by any matcher downstream. Take two impressions of one finger and register them by their ridge flow, searching over rotation and shift for the alignment that best correlates the enhanced images. This hands the matcher the transform it would otherwise have to find, so nothing that follows can be blamed on a bad alignment. Then ask the only question that matters — what fraction of the landmarks in one impression have a landmark in the other within a ridge period of them? Call that the <strong>landing rate</strong>: a landmark that lands is one the extractor found twice.</p>
<div id="fdcd4dbf" class="cell" data-execution_count="7">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># src/minutiae_follow.py -- the gate</span></span>
<span id="cb7-2">angle, peak, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> best_alignment(b_enhanced, a_enhanced, angles)</span>
<span id="cb7-3">moved <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> transform_points(b_min.xy, angle, peak, a_enhanced.shape)</span>
<span id="cb7-4">genuine.append(landing_rate(moved, a_min.xy, tolerance))</span></code></pre></div>
</details>
</div>
<p>Against an impostor floor — a different finger, registered the same way, whose points overlap by accident at this density — the crossing-number extractor lands <strong>0.137 of its landmarks against a floor of 0.066</strong>, averaged over 60 fingers and three seeds. Roughly twice the floor, which is the same ratio <code>src/minutiae.py</code> recorded independently as “about a fifth against a tenth”. A neighbourhood descriptor needs the neighbourhood to be stable, and at that rate almost every description is built from mostly different members. No scoring rule can recover a correspondence that was never there, which is why the log of attempts in <code>src/minutiae.py</code> — greedy one-to-one pairing, normalising by landmark count, voting on the implied rotation, a full rigid point-pattern search, three binarisations, a pixel-resolution orientation field, three thinning variants — moves none of it.</p>
<p>So the fix has to be a different class of extractor: ridge <em>following</em> with a quality map, in the manner of NIST’s MINDTCT, rather than crossing numbers on a thinned skeleton. <code>src/minutiae_follow.py</code> is that attempt. It finds ridges as crests — the line where the enhanced image is a local maximum measured across the flow, which is a statement about ridge geometry rather than about where a threshold fell — smooths along the flow to close ink gaps, bridges the gaps that survive, and scores every candidate by coherence, ridge strength and distance from the edge of the print:</p>
<div id="74c0143e" class="cell" data-execution_count="8">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># src/minutiae_follow.py -- bridge_gaps(): join endpoints that face each other</span></span>
<span id="cb8-2">towards <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> delta[i, j] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> d</span>
<span id="cb8-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Each endpoint must be heading at the other, not merely near it.</span></span>
<span id="cb8-4"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> towards <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> tangent[i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> cone <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> (<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>towards) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> tangent[j] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> cone:</span>
<span id="cb8-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">continue</span></span>
<span id="cb8-6">rr, cc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> draw_line(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(ends[i, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]), <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(ends[i, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]), <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(ends[j, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]), <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(ends[j, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]))</span>
<span id="cb8-7">out[rr, cc] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span></code></pre></div>
</details>
</div>
<p>It behaves better on controls, which <code>src/minutiae_follow.py --controls</code> reproduces. On a synthetic ridge field containing no minutiae at all, both extractors correctly return nothing. On a field with a half-period phase step driven across a band — which dislocates every ridge crossing it — both return three landmarks, and the difference is where they put them. The ridge follower’s three sit 3, 4 and 3 pixels from the dislocation line. The crossing-number extractor’s three sit 9, 44 and 60 pixels from it, which is to say two of them are describing the ink somewhere else entirely. Same count, and only one of the two is looking at the feature. It also finds more landmarks on real prints, and spreads them across the pattern instead of bunching them where the ink is thick.</p>
<p>And it does not help. The table reads landing rate against impostor floor, and <strong>lift</strong> is how much better than chance overlap the extractor manages — the ratio of those two, taken per seed and then averaged. A lift of 1 is an extractor finding nothing a matcher could use:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 13%">
<col style="width: 17%">
<col style="width: 17%">
<col style="width: 17%">
<col style="width: 17%">
<col style="width: 17%">
</colgroup>
<thead>
<tr class="header">
<th>extractor</th>
<th style="text-align: right;">minutiae per print</th>
<th style="text-align: right;">genuine landing</th>
<th style="text-align: right;">impostor floor</th>
<th style="text-align: right;">lift</th>
<th style="text-align: right;">lift, per seed</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>crossing number, thinned skeleton</td>
<td style="text-align: right;">18</td>
<td style="text-align: right;">0.137</td>
<td style="text-align: right;">0.066</td>
<td style="text-align: right;"><strong>2.10</strong></td>
<td style="text-align: right;">1.74 – 2.37</td>
</tr>
<tr class="even">
<td>ridge following, quality ≥ 0.15</td>
<td style="text-align: right;">22</td>
<td style="text-align: right;">0.129</td>
<td style="text-align: right;">0.077</td>
<td style="text-align: right;">1.72</td>
<td style="text-align: right;">1.20 – 2.05</td>
</tr>
<tr class="odd">
<td>ridge following, quality ≥ 0.35</td>
<td style="text-align: right;">13</td>
<td style="text-align: right;">0.095</td>
<td style="text-align: right;">0.059</td>
<td style="text-align: right;">1.61</td>
<td style="text-align: right;">1.33 – 1.84</td>
</tr>
<tr class="even">
<td>ridge following, quality ≥ 0.55</td>
<td style="text-align: right;">5</td>
<td style="text-align: right;">0.066</td>
<td style="text-align: right;">0.024</td>
<td style="text-align: right;">3.76</td>
<td style="text-align: right;">1.40 – 6.65</td>
</tr>
</tbody>
</table>
<p>Two things about that table before reading anything into it.</p>
<p><strong>The last column is there because the first draft of this section did not have it.</strong> On a single seed at half this sample, the top two rows came out at 0.156 and 0.155 — a gap of one in the third decimal — and it was tempting to write that a landmark from the new extractor is individually just as repeatable as one from the old. Widen the sample to the code’s own default and average over three seeds and that coincidence dissolves: the two extractors’ per-seed lifts overlap, 1.74–2.37 against 1.20–2.05. The gate says ridge following is probably worse, and at this sample size it cannot say so cleanly.</p>
<p><strong>The lift column averages the per-seed ratios; it is not the quotient of the two columns beside it.</strong> Those are the right statistics for different questions — what a typical run gives, against how the pooled landmarks behave — and for the first three rows the two agree to within a couple of hundredths. On the bottom row they do not: averaging the ratios gives 3.76 where dividing the printed means gives 2.75.</p>
<p>That divergence is the warning label. A lift of 3.76 is the largest number in the table and it is an artefact: at quality ≥ 0.55 only five landmarks survive per print, the impostor floor collapses to 0.024, and a ratio with a denominator that small swings between 1.40 and 6.65 depending on which fingers were drawn. When the mean of the ratios and the ratio of the means come apart, the ratio is telling you about the sample rather than the extractor. Ratios of small counts are not measurements.</p>
<p>So the gate is not what convicts the rewrite. What convicts it is the scored comparison, where every one of the 199 test fingers is used instead of a 60-finger subsample: rank-1 0.025 against 0.030, EER 0.488 against 0.482, d′ 0.05 against 0.08. The extra landmarks are real, they are better placed on a synthetic control, and they buy nothing on real prints.</p>
<p>The resampling is the likeliest cause, upstream of every extractor. At a 6-pixel ridge period a ridge ending is a few pixels of evidence, and both extractors are reading a feature the cache no longer resolves. <code>src/minutiae.py</code> records that rebuilding the cache at the source period of 11.5 pixels lifts repeatability by about a third — real, nowhere near enough, and it would cost the global ridge flow that the methods which <em>do</em> work depend on, so this post keeps the 6-pixel cache and inherits that number rather than re-measuring it. Both minutiae extractors are scored anyway, because a rewrite that fails is worth as much as one that works and costs the reader less to believe. Neither is evidence about minutiae matching as a technique.</p>
</section>
</section>
<section id="keeping-whatever-separates-fingers-let-the-network-decide" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="keeping-whatever-separates-fingers-let-the-network-decide"><span class="header-section-number">8</span> Keeping whatever separates fingers: let the network decide</h2>
<p>The first three answers are hand-built. Someone decided that ridge orientation matters, that a Gabor bank is the way to measure it, that endings and forks are the landmarks worth keeping. A metric-learning network is told none of that. It is given pairs and a rule about distances, and has to work out what to measure for itself.</p>
<p>Here the classification framing earns its keep, because the architecture you would reach for first is the wrong one. Train a convolutional network with a softmax head over the training fingers and it will separate those fingers — and the head is then useless, because the fingers it is asked about later are not the fingers it was trained on. Every class it learned to name is a class nobody will ever ask for again.</p>
<p>Metric learning keeps the network and throws the head away. Train on whatever identities are to hand, but supervise the <em>geometry of the feature space</em> instead of the labels: pull two impressions of one finger together, push impressions of different fingers apart. The bet is that a space arranged that way for the training fingers stays arranged that way for fingers the network never saw — and then enrolling a new finger is one forward pass, with nothing refitted. It is the same move as in face recognition, and it is why a phone can add a thumb in thirty seconds.</p>
<p>The mental picture is a <strong>field of attractors</strong>. Every finger is a mass placed on the surface of a 128-dimensional sphere, and training pulls the two impressions of one finger into the same well while pushing different fingers’ wells apart. Nothing tells the network what a ridge is. It only ever learns that these two images must end up close and those two must not.</p>
<p>For an anchor <img src="https://latex.codecogs.com/png.latex?a">, a positive <img src="https://latex.codecogs.com/png.latex?p"> from the same finger, and a negative <img src="https://latex.codecogs.com/png.latex?n"> from another, the triplet loss is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7BL%7D=%5Cbigl%5B%5C;%5ClVert%20f(a)-f(p)%5CrVert_2%5E2%0A-%5ClVert%20f(a)-f(n)%5CrVert_2%5E2%0A+m%5C;%5Cbigr%5D_+%20.%0A"></p>
<p>Picking triplets at random wastes almost every one of them, because most triplets already satisfy the margin and contribute no gradient. The batch-hard form (Hermans, Beyer and Leibe, 2017) takes the <em>worst</em> case in each batch instead — for every image, its own mate as the hardest positive and the nearest image of a different finger as the hardest negative:</p>
<div id="efbccec5" class="cell" data-execution_count="9">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># src/embed.py -- batch_hard_triplet()</span></span>
<span id="cb9-2">distance <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.cdist(embeddings, embeddings)</span>
<span id="cb9-3">same <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> labels[:, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> labels[<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, :]</span>
<span id="cb9-4">eye <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.eye(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(labels), dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>torch.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>, device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>labels.device)</span>
<span id="cb9-5"></span>
<span id="cb9-6">hardest_positive <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (distance <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (same <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>eye)).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>).values</span>
<span id="cb9-7">hardest_negative <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (distance <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> same <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e6</span>).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>).values</span>
<span id="cb9-8"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> F.relu(hardest_positive <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> hardest_negative <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> margin).mean()</span></code></pre></div>
</details>
</div>
<p>With two images per finger the network would otherwise memorise the pair rather than learn what makes it a pair, so each batch is augmented with random rotation, shift, scale and a blanked-out block. Each of those stands for something the sensor does anyway: the finger lands at a different angle, presses harder, and covers a different part of the platen. The blanked block is the partial print.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
ArcFace: putting the margin in the angle
</div>
</div>
<div class="callout-body-container callout-body">
<p>The embeddings here are L2-normalised, so similarity is a dot product and all the information is in angle. ArcFace (Deng et al., 2019) takes that seriously. Start from softmax cross-entropy, drop the bias, and normalise both the weight vectors and the features, so the logit for class <img src="https://latex.codecogs.com/png.latex?j"> becomes <img src="https://latex.codecogs.com/png.latex?W_j%5E%5Ctop%20f%20=%20%5Ccos%5Ctheta_j">. Then add a fixed margin <img src="https://latex.codecogs.com/png.latex?m"> <em>inside</em> the cosine for the true class only, and rescale by <img src="https://latex.codecogs.com/png.latex?s">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7BL%7D_%7B%5Ctext%7Barc%7D%7D%20=%20-%5Clog%0A%5Cfrac%7Be%5E%7B%5C,s%5Ccos(%5Ctheta_y%20+%20m)%7D%7D%0A%7Be%5E%7B%5C,s%5Ccos(%5Ctheta_y%20+%20m)%7D%20+%20%5Csum_%7Bj%20%5Cneq%20y%7D%20e%5E%7B%5C,s%5Ccos%5Ctheta_j%7D%7D%20.%0A"></p>
<p>Because <img src="https://latex.codecogs.com/png.latex?%5Ccos"> is decreasing on <img src="https://latex.codecogs.com/png.latex?%5B0,%5Cpi%5D">, demanding <img src="https://latex.codecogs.com/png.latex?%5Ccos(%5Ctheta_y%20+%20m)"> beat the other logits is demanding that the true class win <em>by an angular margin</em> <img src="https://latex.codecogs.com/png.latex?m"> — a constant-width band on the sphere, the same everywhere. A Euclidean margin is not that: the same distance means different angles depending on where you are. This is the loss behind most modern face recognition, and it is used for fingerprints unchanged.</p>
<p>There is a classification head here after all, which is worth reconciling with the claim that metric learning throws one away. ArcFace trains a softmax classifier over the <em>training</em> identities, uses it to shape the feature space, and discards it before anything is enrolled. The head is scaffolding, and the features underneath are the product. Triplet loss skips the scaffolding and constrains distances directly. Both arrive at the same destination: a space where nearest neighbour works on identities the network never saw.</p>
<p>This post scores the triplet version, because a margin in angle needs enough identities for that head to be meaningful and a few hundred fingers is not enough for the comparison to say anything.</p>
</div>
</div>
<p>Which is the caveat this method rests on. This network sees a few hundred images of a couple of hundred fingers. The systems that beat hand-built features on this problem see millions of prints, and millions is not a nicety — with two impressions per finger, the nearest image of a <em>different</em> finger is usually closer than a print’s own mate, so the loss can be reduced faster by shrinking the embedding than by separating anything in it. The scoring section reports what that looks like when it happens.</p>
</section>
<section id="the-six-variants-scored" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="the-six-variants-scored"><span class="header-section-number">9</span> The six variants, scored</h2>
<p>The four answers make six rows, because correlation is scored raw and enhanced, and the landmark answer is scored with both extractors. Two rules keep the comparison honest, and both cost accuracy.</p>
<p><strong>Nothing is scored on a finger it was fitted on.</strong> The fingers are split in two before anything runs. The network trains on one half; every method, trained or not, is scored on the other. The hand-built methods would score the same either way, and applying the rule to every row is what keeps the network from gaining by breaking it. The split is by <em>finger</em>, not by image, because a model that trained on one impression of a finger and was then asked to recognise the other would be answering a question no phone ever asks.</p>
<p><strong>Every method is charged for the ridge geometry it needs.</strong> Enhanced correlation, FingerCode and both minutiae extractors all start by asking which way the ridges run. Computing that once and sharing it is the only sane way to run the comparison, but attributing it to nobody would make four of the six look free, so the shared pass is timed once and added to each method that reads it.</p>
<p>Everything reported comes off one object per method — a score matrix, one row per probe, one column per enrolled finger, higher meaning more alike — which is what makes a pixel correlation, a Gabor descriptor, a point set and a learned vector comparable at all. Ties are resolved against the matcher, deliberately:</p>
<div id="20246900" class="cell" data-execution_count="10">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># src/bench.py -- score(): ties count against the matcher, so silence scores last</span></span>
<span id="cb10-2">genuine <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> matrix[rows, truth]</span>
<span id="cb10-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Counting only strictly-better entries would give a matcher that returns the</span></span>
<span id="cb10-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># same score for everything a rank of zero on every probe -- a perfect result</span></span>
<span id="cb10-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># for saying nothing.</span></span>
<span id="cb10-6">ranks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (matrix <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> genuine[:, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>]).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span></code></pre></div>
</details>
</div>
<p>199 fingers enrolled, 199 probes, one impression each — so the chance rate from the task section, 0.005, is the floor every row is read against.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 10%">
<col style="width: 10%">
<col style="width: 13%">
<col style="width: 13%">
<col style="width: 13%">
<col style="width: 13%">
<col style="width: 13%">
<col style="width: 13%">
</colgroup>
<thead>
<tr class="header">
<th>Method</th>
<th>What it keeps</th>
<th style="text-align: right;">rank-1</th>
<th style="text-align: right;">rank-5</th>
<th style="text-align: right;">median rank</th>
<th style="text-align: right;">EER</th>
<th style="text-align: right;">d′</th>
<th style="text-align: right;">search</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>pixel correlation</td>
<td>the image</td>
<td style="text-align: right;">0.020</td>
<td style="text-align: right;">0.085</td>
<td style="text-align: right;">63</td>
<td style="text-align: right;">0.431</td>
<td style="text-align: right;">0.35</td>
<td style="text-align: right;">11 s</td>
</tr>
<tr class="even">
<td>pixel correlation, enhanced</td>
<td>the combed image</td>
<td style="text-align: right;"><strong>0.332</strong></td>
<td style="text-align: right;"><strong>0.508</strong></td>
<td style="text-align: right;"><strong>5</strong></td>
<td style="text-align: right;"><strong>0.251</strong></td>
<td style="text-align: right;"><strong>1.15</strong></td>
<td style="text-align: right;">34 s</td>
</tr>
<tr class="odd">
<td>FingerCode</td>
<td>flow energy, per cell and direction</td>
<td style="text-align: right;">0.161</td>
<td style="text-align: right;">0.332</td>
<td style="text-align: right;">33</td>
<td style="text-align: right;">0.370</td>
<td style="text-align: right;">0.58</td>
<td style="text-align: right;">22 s</td>
</tr>
<tr class="even">
<td>minutiae, crossing number</td>
<td>a list of landmarks</td>
<td style="text-align: right;">0.030</td>
<td style="text-align: right;">0.070</td>
<td style="text-align: right;">110</td>
<td style="text-align: right;">0.482</td>
<td style="text-align: right;">0.08</td>
<td style="text-align: right;">23 s</td>
</tr>
<tr class="odd">
<td>minutiae, ridge following</td>
<td>a list of landmarks</td>
<td style="text-align: right;">0.025</td>
<td style="text-align: right;">0.090</td>
<td style="text-align: right;">114</td>
<td style="text-align: right;">0.488</td>
<td style="text-align: right;">0.05</td>
<td style="text-align: right;">30 s</td>
</tr>
<tr class="even">
<td>learned embedding, triplet</td>
<td>128 floats</td>
<td style="text-align: right;">0.035</td>
<td style="text-align: right;">0.126</td>
<td style="text-align: right;">52</td>
<td style="text-align: right;">0.391</td>
<td style="text-align: right;">0.54</td>
<td style="text-align: right;"><strong>1 s</strong></td>
</tr>
</tbody>
</table>
<p>The search column is the whole 199-by-199 comparison, with the shared ridge geometry charged to every method that reads it, and the seconds are wall-clock on one laptop rather than anything to rank methods by. The network’s 145 seconds of training is not in it, because training happens once before any finger is enrolled.</p>
<div class="callout callout-style-simple callout-note">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p>One number in that table moved because of arithmetic precision, which is worth knowing before trusting the rest. The learned method’s scores all sit near 0.99999, and computing its 199-by-199 dot product in float32 rounded them onto 69 distinct values instead of 199. <code>bench.score</code> breaks ties <em>against</em> the matcher on purpose, so the rounding was inventing ties and charging them to the network: it scored rank-1 0.030 until the accumulation was widened to float64, and 0.035 after. Nothing else in the table shifted, because no other method crowds its scores into the last few decimals.</p>
</div>
</div>
</div>
<p><strong>The largest effect in the table is the oldest idea in it.</strong> The only difference between the first two rows is whether the images were combed with a Gabor bank before being correlated, and it moves rank-1 from 0.020 to 0.332 — a factor of sixteen, from a 1998 paper, with no descriptor, no landmarks and no training anywhere in sight. Enhancement is not a preprocessing detail that precedes the real method. On this data it <em>is</em> most of the method.</p>
<p><strong>FingerCode buys portability, not accuracy.</strong> It lands at half the rank-1 of enhanced correlation. What it gets in exchange is a fixed-length template a few kilobytes wide, and rotation handled by two array rolls instead of not handled at all.</p>
<p><strong>Both minutiae methods are near chance</strong>, at 0.030 and 0.025 against 0.005, with d′ of 0.08 and 0.05 — which is to say the genuine and impostor score distributions sit on top of each other and no threshold separates them. The ridge-following rewrite is worse on rank-1, median rank, EER and d′, and better on rank-5 — 0.090 against 0.070. Nothing in that pattern is a rescue. Putting the true mate in the top five nearly four times as often as it puts it first, while the genuine and impostor score distributions still sit on top of each other, describes a method that occasionally gets the right finger into a shortlist and never earns a threshold you could set. Neither number is evidence about minutiae matching; both are evidence about extractors reading a feature this cache resampled away.</p>
<p><strong>And the learned embedding is the interesting failure.</strong> It beats both minutiae extractors on every measure — rank-1 0.035 against 0.030, rank-5 0.126 against 0.070, median rank 52 against 110, d′ 0.54 against 0.08 — and loses to a 1998 Gabor bank by a factor of ten. But look at the last column. One second, against eleven to thirty-four for everything else, because at query time it does no ridge analysis, no filtering and no point matching. It encodes once and takes a dot product.</p>
<p>Be precise about that last column, because it is the reason phones work this way at all. The embedding is twenty-six times cheaper to search than the method that beat it and its template is 128 floats, and it achieves that while being a <strong>collapsed</strong> embedding: the loss settles at 0.300 against a margin of 0.3, which is what the hinge returns when every distance is equal, and per-dimension spread across prints comes out at <img src="https://latex.codecogs.com/png.latex?1.7%20%5Ctimes%2010%5E%7B-4%7D">. Soft-margin, a quarter of the learning rate, a smaller margin and half the batch were all tried; each settles at its own degenerate value and none recovers the spread. With two impressions per finger and two hundred fingers, shrinking everything is the only move that reduces the loss.</p>
<p>So the d′ of 0.54 is a real signal living in the residual directions of an embedding that has almost no extent. It is not a network that learned a little. It is a network that collapsed and still has a whisker of the answer in the rounding.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/fingerprint-algorithms/figures/embedding.png" class="img-fluid figure-img"></p>
<figcaption>Left: the batch-hard triplet loss settling onto the margin, which is the collapse — equal hardest-positive and hardest-negative distances return exactly the margin. Right: genuine and impostor similarity, plotted as an offset from the impostor median because every pair sits within about <img src="https://latex.codecogs.com/png.latex?10%5E%7B-5%7D"> of every other. The separation is real, and it is tiny.</figcaption>
</figure>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/fingerprint-algorithms/figures/cmc.png" class="img-fluid figure-img"></p>
<figcaption>Cumulative match characteristic: how often the true mate appears in the top <img src="https://latex.codecogs.com/png.latex?k"> as <img src="https://latex.codecogs.com/png.latex?k"> grows, one line per method. The curve to read is the one at <img src="https://latex.codecogs.com/png.latex?k=1">; the rest tells you how quickly a near-miss becomes a hit.</figcaption>
</figure>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Why a method can rank well and threshold badly
</div>
</div>
<div class="callout-body-container callout-body">
<p>Rank-1 and EER can pull apart, and a few rows here do. Rank-1 is settled inside one row of the score matrix: the mate only has to beat the other 198 entries <em>for this probe</em>, and a probe that scores every entry low is judged on the ordering, not the level. EER has to hold one threshold across every row at once. So a method whose scores are ordered sensibly within each probe but drift up and down between probes identifies respectably and verifies terribly.</p>
<p>Which column matters is a question about the deployment. Searching a criminal gallery or a border-crossing database is the rank-1 question. A phone checking one claimed identity is the EER question, and d′ is what says whether it has a threshold to tune at all.</p>
</div>
</div>
</section>
<section id="turn-the-probes-and-the-ranking-changes" class="level2" data-number="10">
<h2 data-number="10" class="anchored" data-anchor-id="turn-the-probes-and-the-ranking-changes"><span class="header-section-number">10</span> Turn the probes, and the ranking changes</h2>
<p>There is a quiet gift in that table, and it belongs to the two methods that did best. Inked cards are rolled onto a form, so every print in this cache is roughly upright. Neither correlation method can search over rotation — sliding one image over another is all they do — so the data has been flattering them.</p>
<p>A finger on a phone lands at whatever angle it lands at. That is one flag away from being measurable, so it is cheaper to measure it than to argue about it:</p>
<div id="8579af11" class="cell" data-execution_count="11">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># src/ladder.py -- turn_probes(): every probe gets its own random angle</span></span>
<span id="cb11-2">rng <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.random.default_rng(seed)</span>
<span id="cb11-3">angles <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rng.uniform(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>degrees, degrees, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(probe.images))</span>
<span id="cb11-4">turned <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.stack([</span>
<span id="cb11-5">    np.clip(rotate_about_centre(img.astype(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>), np.deg2rad(a)), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">255</span>).astype(np.uint8)</span>
<span id="cb11-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> img, a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(probe.images, angles)</span>
<span id="cb11-7">])</span></code></pre></div>
</details>
</div>
<p>Enrol the same gallery, turn each probe by its own random angle up to <img src="https://latex.codecogs.com/png.latex?%5Cpm%2030%C2%B0">, and run the comparison again:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Method</th>
<th style="text-align: right;">rank-1 upright</th>
<th style="text-align: right;">rank-1 turned</th>
<th style="text-align: right;">EER upright</th>
<th style="text-align: right;">EER turned</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>pixel correlation</td>
<td style="text-align: right;">0.020</td>
<td style="text-align: right;">0.015</td>
<td style="text-align: right;">0.431</td>
<td style="text-align: right;">0.461</td>
</tr>
<tr class="even">
<td>pixel correlation, enhanced</td>
<td style="text-align: right;"><strong>0.332</strong></td>
<td style="text-align: right;">0.101</td>
<td style="text-align: right;">0.251</td>
<td style="text-align: right;">0.380</td>
</tr>
<tr class="odd">
<td>FingerCode</td>
<td style="text-align: right;">0.161</td>
<td style="text-align: right;"><strong>0.131</strong></td>
<td style="text-align: right;">0.370</td>
<td style="text-align: right;"><strong>0.371</strong></td>
</tr>
<tr class="even">
<td>minutiae, crossing number</td>
<td style="text-align: right;">0.030</td>
<td style="text-align: right;">0.020</td>
<td style="text-align: right;">0.482</td>
<td style="text-align: right;">0.477</td>
</tr>
<tr class="odd">
<td>minutiae, ridge following</td>
<td style="text-align: right;">0.025</td>
<td style="text-align: right;">0.035</td>
<td style="text-align: right;">0.488</td>
<td style="text-align: right;">0.501</td>
</tr>
<tr class="even">
<td>learned embedding, triplet</td>
<td style="text-align: right;">0.035</td>
<td style="text-align: right;">0.030</td>
<td style="text-align: right;">0.391</td>
<td style="text-align: right;">0.432</td>
</tr>
</tbody>
</table>
<p><strong>Enhanced correlation loses two thirds of its accuracy and FingerCode barely moves, so the order swaps.</strong> Enhanced correlation falls from 0.332 to 0.101; FingerCode goes from 0.161 to 0.131 and is now the best of the six. Look at the EER columns for the cleanest version of it: FingerCode’s goes from 0.370 to 0.371, and its d′ from 0.58 to 0.57. Rotating the finger costs the descriptor that was designed to absorb rotation almost nothing.</p>
<p>The learned embedding also holds up better than it scores — d′ 0.54 to 0.44 — which is not a mystery: it was trained on batches augmented with rotations up to <img src="https://latex.codecogs.com/png.latex?%5Cpm%2025%C2%B0">, so turned probes are inside what it was shown. It paid for that robustness in advance, with data.</p>
<p>The two minutiae rows each move by two probes out of 199, which at those rates is noise rather than a finding. Nothing here rescues them.</p>
<p>So the ranking depends on a property of the capture, not of the algorithm. On upright rolled cards, comb the images and correlate them — the 1998 answer wins and nothing since has earned its keep. Let the finger turn, and the descriptor that treats rotation as an array roll takes over, at half the peak accuracy but without a cliff to fall off. This is the shape of the whole history in one table: later methods are not uniformly better, they are less dependent on the capture being kind.</p>
</section>
<section id="the-sensor-decides-what-any-of-this-can-see" class="level2" data-number="11">
<h2 data-number="11" class="anchored" data-anchor-id="the-sensor-decides-what-any-of-this-can-see"><span class="header-section-number">11</span> The sensor decides what any of this can see</h2>
<p>Everything measured here came off inked cards. The comparison on a phone is the same comparison, but the sensor decides what the representation is even allowed to contain — and the three sensors in shipping phones measure three different physical quantities.</p>
<div>

</div>
<div class="quarto-layout-panel" data-layout-ncol="3">
<div class="quarto-layout-row">
<div class="quarto-layout-cell" style="flex-basis: 33.3%;justify-content: flex-start;">
<div class="callout callout-style-simple callout-note">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p><strong>Capacitive — Apple Touch ID</strong></p>
<p>A silicon array measures electrical capacitance between the array and the live layer of skin under the epidermis. Ridges sit closer than valleys, so the image is 2D electrical contrast, not a photograph.</p>
<p>Depth: none. Template never leaves the <a href="https://support.apple.com/guide/security/touch-id-sec066eb0cd4/web">Secure Enclave</a> and matching happens there.</p>
</div>
</div>
</div>
</div>
<div class="quarto-layout-cell" style="flex-basis: 33.3%;justify-content: flex-start;">
<div class="callout callout-style-simple callout-note">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p><strong>Ultrasonic — Qualcomm 3D Sonic</strong></p>
<p>A piezoelectric transducer emits an ultrasonic pulse and times the echoes. Time of flight gives a genuine depth map of ridge and pore topography, through glass and through moisture.</p>
<p>Depth: yes. A printed 2D spoof has no volume, so its echo profile is wrong.</p>
</div>
</div>
</div>
</div>
<div class="quarto-layout-cell" style="flex-basis: 33.3%;justify-content: flex-start;">
<div class="callout callout-style-simple callout-note">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p><strong>Optical — in-display</strong></p>
<p>An illuminator under the glass photographs the finger and reads subsurface light scattering — how light diffuses back out of living tissue.</p>
<p>Depth: none. Liveness is a small network on scatter statistics, run locally, because the alternative is shipping fingerprint images off-device.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th></th>
<th>Capacitive</th>
<th>Ultrasonic</th>
<th>Optical</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Physical quantity</td>
<td>capacitance to subdermal skin</td>
<td>acoustic time of flight</td>
<td>back-scattered light</td>
</tr>
<tr class="even">
<td>Depth channel</td>
<td>no</td>
<td><strong>yes</strong></td>
<td>no</td>
</tr>
<tr class="odd">
<td>Through water / grease</td>
<td>poor</td>
<td>good</td>
<td>moderate</td>
</tr>
<tr class="even">
<td>Defeats a flat printed spoof</td>
<td>weakly</td>
<td>by geometry</td>
<td>by scatter statistics, learned</td>
</tr>
<tr class="odd">
<td>Area captured</td>
<td>small, so partial prints are normal</td>
<td>small to large</td>
<td>medium</td>
</tr>
<tr class="even">
<td>What the template may contain</td>
<td>2D contrast only</td>
<td>ridge and pore relief</td>
<td>2D image plus scatter</td>
</tr>
</tbody>
</table>
<p>And this is where the arc bends towards learning, on an argument the scored comparison cannot make. A phone sensor sees a small, rotated, partly wet fragment. A forensic latent is worse: partial, smudged, overlapping another print, lifted off a curved surface. Minutiae extraction assumes there is a clean skeleton to walk, and on a latent there is not.</p>
<p>NIST <a href="https://pages.nist.gov/elft/">ELFT</a> (Evaluation of Latent Friction Ridge Technology) is the test that matters for that regime: open-set search of a gallery using latent probes, hit rates reported at fixed false-positive rates. It specifies no architecture and measures a black box. Systems that lead those tables combine spatial-domain enhancement — reconstructing ridge flow in the regions where it has been destroyed — with learned embeddings, sometimes embedding the latent and a rolled mate jointly. ELFT does not say which piece did the work, and the results here are not a claim about that regime. They are a claim about what each representation keeps.</p>
</section>
<section id="what-to-keep-and-when" class="level2" data-number="12">
<h2 data-number="12" class="anchored" data-anchor-id="what-to-keep-and-when"><span class="header-section-number">12</span> What to keep, and when</h2>
<table class="caption-top table">
<colgroup>
<col style="width: 16%">
<col style="width: 16%">
<col style="width: 16%">
<col style="width: 16%">
<col style="width: 16%">
<col style="width: 16%">
</colgroup>
<thead>
<tr class="header">
<th>Method</th>
<th>What it keeps</th>
<th>Handles rotation</th>
<th>Survives a partial print</th>
<th>Template</th>
<th>Needs training data</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>pixel correlation</td>
<td>the whole image</td>
<td>no</td>
<td>poorly</td>
<td>the image</td>
<td>no</td>
</tr>
<tr class="even">
<td>enhanced correlation</td>
<td>the combed image</td>
<td>no</td>
<td>poorly</td>
<td>the image</td>
<td>no</td>
</tr>
<tr class="odd">
<td>FingerCode</td>
<td>energy per cell, per direction</td>
<td><strong>yes, as an array roll</strong></td>
<td>moderately</td>
<td>~4 kB, fixed</td>
<td>no</td>
</tr>
<tr class="even">
<td>minutiae</td>
<td>a list of <img src="https://latex.codecogs.com/png.latex?(x,y,%5Ctheta)"> landmarks</td>
<td><strong>yes, by construction</strong></td>
<td><strong>well, in principle</strong></td>
<td>tens of points</td>
<td>no</td>
</tr>
<tr class="odd">
<td>learned embedding</td>
<td>whatever separates fingers</td>
<td>only what it was shown</td>
<td><strong>well, if trained for it</strong></td>
<td>128 floats</td>
<td>a great deal</td>
</tr>
</tbody>
</table>
<p>The ranking in that table is not the ranking in the scores, and the reasons are specific rather than damning. The minutiae methods are reading a feature this cache resampled away, and they would be different methods on 500 dpi originals. The learned embedding is reading a few hundred prints where it wants millions, and it would be a different thing inside a phone vendor with a data pipeline. Only two of the six are near what they can do – enhanced correlation and FingerCode – and they are the two that scored.</p>
<p>Which returns to the question this started with. Keeping the picture works startlingly well — 0.332 rank-1, better than everything else here — right up until the finger turns, and then it is 0.101. Keeping a measurement of the flow is blunter at its best and costs 0.001 of its equal error rate to a thirty-degree turn. Keeping landmarks is the densest choice and the most fragile, because it presumes the capture was good enough to find them, and on a cache resampled to a 6-pixel ridge period it was not. Keeping a learned vector moves the fragility into the training set, where it stops being a property of the algorithm and becomes someone’s data-collection budget — and buys, in exchange, a search that costs one second instead of thirty-three.</p>
<p>None of those is the best representation. Each is the best answer to a different question about the capture, and the sensor is what decides which question you are being asked.</p>
<p>Ridges. Buckle. Patterns. Persist. Combs. Beat. Landmarks. Capture. Decides. Which. Wins.</p>
</section>
<section id="references" class="level2" data-number="13">
<h2 data-number="13" class="anchored" data-anchor-id="references"><span class="header-section-number">13</span> References</h2>
<ul>
<li>Kücken, M. &amp; Newell, A. C. (2005). <a href="https://doi.org/10.1016/j.jtbi.2005.01.005">Fingerprint formation</a>. <em>Journal of Theoretical Biology</em> 235(1):71–83.</li>
<li>Hong, L., Wan, Y. &amp; Jain, A. (1998). <a href="https://doi.org/10.1109/34.709565">Fingerprint image enhancement: algorithm and performance evaluation</a>. <em>IEEE Transactions on Pattern Analysis and Machine Intelligence</em> 20(8):777–789.</li>
<li>Jain, A. K., Prabhakar, S., Hong, L. &amp; Pankanti, S. (2000). <a href="https://doi.org/10.1109/83.841531">Filterbank-based fingerprint matching</a>. <em>IEEE Transactions on Image Processing</em> 9(5):846–859.</li>
<li>Cappelli, R., Ferrara, M. &amp; Maltoni, D. (2010). <a href="https://doi.org/10.1109/TPAMI.2010.52">Minutia Cylinder-Code: a new representation and matching technique for fingerprint recognition</a>. <em>IEEE Transactions on Pattern Analysis and Machine Intelligence</em> 32(12):2128–2141.</li>
<li>Schroff, F., Kalenichenko, D. &amp; Philbin, J. (2015). <a href="https://arxiv.org/abs/1503.03832">FaceNet: a unified embedding for face recognition and clustering</a>. <em>CVPR</em>.</li>
<li>Hermans, A., Beyer, L. &amp; Leibe, B. (2017). <a href="https://arxiv.org/abs/1703.07737">In defense of the triplet loss for person re-identification</a>. <em>arXiv:1703.07737</em>.</li>
<li>Deng, J., Guo, J., Xue, N. &amp; Zafeiriou, S. (2019). <a href="https://arxiv.org/abs/1801.07698">ArcFace: additive angular margin loss for deep face recognition</a>. <em>CVPR</em>.</li>
<li>NIST. <a href="https://github.com/usnistgov/minex">MINEX III validation imagery</a>. Public domain (US Government work).</li>
<li>NIST. <a href="https://pages.nist.gov/elft/">Evaluation of Latent Friction Ridge Technology (ELFT)</a>.</li>
<li>NIST. <a href="https://www.nist.gov/services-resources/software/nist-biometric-image-software-nbis">NBIS / MINDTCT minutiae detector</a>.</li>
<li>Apple. <a href="https://support.apple.com/guide/security/touch-id-sec066eb0cd4/web">Touch ID security</a>. <em>Apple Platform Security</em>.</li>
<li>Qualcomm. <a href="https://www.qualcomm.com/products/features/3d-sonic-sensor">3D Sonic Sensor</a>.</li>
</ul>


</section>

 ]]></description>
  <category>Biology</category>
  <category>Deep Learning</category>
  <category>Machine Learning</category>
  <guid>https://project-delphi.github.io/ml-blog/posts/fingerprint-algorithms/</guid>
  <pubDate>Mon, 31 Aug 2026 05:00:00 GMT</pubDate>
  <media:content url="https://project-delphi.github.io/ml-blog/posts/fingerprint-algorithms/cover.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Tensor Inverses: Independence, Completion, Identification</title>
  <dc:creator>Ravi Kalia</dc:creator>
  <link>https://project-delphi.github.io/ml-blog/posts/tensor-inverse-examples/</link>
  <description><![CDATA[ 





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/tensor-inverse-examples/cover.png" class="img-fluid figure-img"></p>
<figcaption>Tensor Inverses: Independence, Completion, Identification</figcaption>
</figure>
</div>
<p>You already undo a mixed recording, fill a blank rating, and pick plant settings from a desired output. For a square, full-rank matrix those three jobs share one inverse. A tensor does not. You pick a product, then extra structure, and a leftover still remains — permutation and scale, a rank, a lag.</p>
<p>A meeting transcript, a recommendation, and a dryer batch each hang on which leftover you keep.</p>
<p><a href="../tensor-factorizations/">Tensor Factorizations and Tensor Inverses</a> has the algebra. <a href="../uses-of-tensor-factorizations/">Uses of Tensor Factorizations</a> has the compression case. <a href="../tensor-inverses-in-practice/">Tensor Inverses in Practice</a> works four other problems and lists the products. This post takes three more — unmixing speech, filling missing ratings, identifying a dryer — and shows that even after you pick a product, the inverse is not unique.</p>
<section id="words-used-here" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="words-used-here"><span class="header-section-number">1</span> Words used here</h2>
<p>Mode, flatten, rank, null space, inverse, and pseudoinverse are defined in <a href="../tensor-inverses-in-practice/">Tensor Inverses in Practice</a>. Four more words appear below.</p>
<ul>
<li><strong>Cumulant.</strong> A moment of the data with the Gaussian part subtracted. The fourth-order cumulant of a vector is a <img src="https://latex.codecogs.com/png.latex?n%5Ctimes%20n%5Ctimes%20n%5Ctimes%20n"> tensor. Independent non-Gaussian sources make that tensor diagonal.</li>
<li><strong>Joint diagonalization.</strong> One orthogonal change of basis that makes several matrices as diagonal as they can be at once. JADE does this to the slices of the cumulant.</li>
<li><strong>Sampling operator.</strong> The map that keeps the observed cells of an array and throws the rest away. Its null space is every array that is zero on those cells.</li>
<li><strong>Impulse-response tensor.</strong> For a linear plant, <img src="https://latex.codecogs.com/png.latex?H_%7Bijk%7D"> is how output <img src="https://latex.codecogs.com/png.latex?i"> responds to input <img src="https://latex.codecogs.com/png.latex?j"> at lag <img src="https://latex.codecogs.com/png.latex?k">.</li>
</ul>
<div id="setup" class="cell" data-execution_count="1">
<div class="cell-output cell-output-stderr">
<pre><code>/Users/ravikalia/Code/github.com/ml-blog/.venv-tensor-factorizations/lib/python3.12/site-packages/tensorly/solvers/nnls.py:103: SyntaxWarning: invalid escape sequence '\l'
  .. math:: \lambda_s, \lambda_r
/Users/ravikalia/Code/github.com/ml-blog/.venv-tensor-factorizations/lib/python3.12/site-packages/tensorly/solvers/admm.py:99: SyntaxWarning: invalid escape sequence '\_'
  .. math:: dual\_var = dual\_var + (Ax + Bx_{split} - c)</code></pre>
</div>
</div>
</section>
<section id="why-the-inverse-is-not-unique" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="why-the-inverse-is-not-unique"><span class="header-section-number">2</span> Why the inverse is not unique</h2>
<p>A matrix inverse undoes matrix multiplication. There is one matrix product, so there is one inverse when the matrix is square and full rank.</p>
<p>A tensor has several products. Each product has its own inverse. That is the algebra in the earlier posts. This post is about the leftover that stays after you pick one.</p>
<ul>
<li><strong>Independence.</strong> The mixing matrix is recoverable only up to permutation and scale, and only if the sources are independent with at most one Gaussian. Two signed permutations of <img src="https://latex.codecogs.com/png.latex?A%5E%7B-1%7D"> both undo the mix. They are different matrices.</li>
<li><strong>Missing entries.</strong> The sampling operator has a huge null space. Infinitely many arrays match the observed ratings. Rank and the ALS start pick one filling. Two ranks fill the same hole with two numbers.</li>
<li><strong>MIMO identification.</strong> Input-output data underdetermines a full parameter tensor. A lag length picks one impulse-response tensor. Inverting that tensor for a control input is a second, different inverse. Two products, two settings.</li>
</ul>
<p>In real work you solve, you do not invert. That line is already in <a href="../tensor-inverses-in-practice/">Tensor Inverses in Practice</a>. This post builds the inverse anyway, so you can see the leftover.</p>
</section>
<section id="data" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="data"><span class="header-section-number">3</span> Data</h2>
<p>Three sources. Two are downloaded. The speech mix is constructed from real recordings, so the recovery can be scored.</p>
<section id="three-voices-one-mix" class="level3" data-number="3.1">
<h3 data-number="3.1" class="anchored" data-anchor-id="three-voices-one-mix"><span class="header-section-number">3.1</span> Three voices, one mix</h3>
<ul>
<li><strong>What it is.</strong> Four seconds each of three Wikimedia recordings of Aesop’s <em>The North Wind and the Sun</em>: <a href="https://commons.wikimedia.org/wiki/File:Recording_of_speaker_of_British_English_(Received_Pronunciation).ogg">British English, Received Pronunciation</a> (P. Roach / International Phonetic Association, CC BY-SA 3.0), <a href="https://commons.wikimedia.org/wiki/File:Sv-The_North_Wind_and_the_Sun.ogg">Swedish</a>, and <a href="https://commons.wikimedia.org/wiki/File:Cdo_northwind_sun_04.ogg">Foochow</a> (GnuDoyng, public domain). Resampled to 8 kHz mono.</li>
<li><strong>Who recorded them and why.</strong> Phonetic archives keep this fable so accents can be compared. The recordings were not made for unmixing.</li>
<li><strong>What this post asks of them.</strong> Mix the three tracks with a known <img src="https://latex.codecogs.com/png.latex?3%5Ctimes%203"> matrix (seed 7) and try to get the voices back. The mix is constructed. The waveforms are not.</li>
<li><strong>The business problem.</strong> A meeting recorder, a hearing aid, or a call centre hears several talkers on one microphone. The product is a transcript, a boosted voice, or a compliance log that follows one speaker.</li>
<li><strong>What solving buys.</strong> Each voice comes back on its own channel. A transcript can attribute a sentence. A hearing aid can raise one talker and lower the others.</li>
<li><strong>What a wrong inverse costs.</strong> Words land on the wrong speaker. A quote in minutes, a medical note, or a legal record names the person who did not say it.</li>
<li><strong>Why a tensor inverse.</strong> The fourth-order cumulant of the mix is a <img src="https://latex.codecogs.com/png.latex?3%5Ctimes%203%5Ctimes%203%5Ctimes%203"> tensor. Independent non-Gaussian sources make it diagonal. Inverting that structure is how you recover the mixing matrix. The inverse is unique only up to permutation and scale.</li>
</ul>
<p>The three sources, then the three mixed channels.</p>
<p><strong>English (RP)</strong></p>
<audio controls="" src="media/source-0.wav">
</audio>
<p><strong>Swedish</strong></p>
<audio controls="" src="media/source-1.wav">
</audio>
<p><strong>Foochow</strong></p>
<audio controls="" src="media/source-2.wav">
</audio>
<p><strong>Mixed channel 1</strong></p>
<audio controls="" src="media/mix-0.wav">
</audio>
<p><strong>Mixed channel 2</strong></p>
<audio controls="" src="media/mix-1.wav">
</audio>
<p><strong>Mixed channel 3</strong></p>
<audio controls="" src="media/mix-2.wav">
</audio>
<div id="speech-load" class="cell" data-execution_count="2">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1">sp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.load(DATA <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"speech.npz"</span>, allow_pickle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb2-2">sources, mixed, A <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sp[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sources"</span>], sp[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mixed"</span>], sp[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mix_matrix"</span>].astype(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>)</span>
<span id="cb2-3">rate <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(sp[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rate"</span>])</span>
<span id="cb2-4"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>sources<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> rate<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> s at </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>rate<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> Hz, mix condition number </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>cond(A)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb2-5"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"labels:"</span>)</span>
<span id="cb2-6"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> lab <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> sp[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"labels"</span>]:</span>
<span id="cb2-7">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"  </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>lab<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>4.0 s at 8000 Hz, mix condition number 3.60
labels:
  The North Wind and the Sun (British English, RP)
  The North Wind and the Sun (Swedish)
  The North Wind and the Sun (Foochow)</code></pre>
</div>
</div>
<div id="cell-fig-speech" class="cell" data-fig-height="5.2" data-fig-width="10" data-execution_count="3">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1">t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.arange(sources.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> rate</span>
<span id="cb4-2">fig, axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.2</span>), sharex<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb4-3"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>):</span>
<span id="cb4-4">    axes[i, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].plot(t, sources[i], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>TEAL, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span>)</span>
<span id="cb4-5">    axes[i, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].plot(t, mixed[i], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>CORAL, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span>)</span>
<span id="cb4-6">    axes[i, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].set_ylabel(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"src </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb4-7">    axes[i, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].set_ylabel(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"mic </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb4-8">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Sources"</span>)</span>
<span id="cb4-9">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Mix"</span>)</span>
<span id="cb4-10">axes[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"time (s)"</span>)</span>
<span id="cb4-11">axes[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"time (s)"</span>)</span>
<span id="cb4-12">fig.tight_layout()</span>
<span id="cb4-13">plt.show()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-speech" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-speech-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/tensor-inverse-examples/index_files/figure-html/fig-speech-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-speech-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Three source waveforms (teal) and the three mixed channels (coral). The mix is a known 3 × 3 applied at every sample.
</figcaption>
</figure>
</div>
</div>
</div>
</section>
<section id="movielens-100k" class="level3" data-number="3.2">
<h3 data-number="3.2" class="anchored" data-anchor-id="movielens-100k"><span class="header-section-number">3.2</span> MovieLens 100K</h3>
<ul>
<li><strong>Provenance.</strong> The <a href="https://grouplens.org/datasets/movielens/100k/">MovieLens 100K</a> ratings dump from GroupLens Research at the University of Minnesota (Harper and Konstan 2015). 100,000 ratings, 943 users, 1,682 movies, 19 September 1997 to 22 April 1998.</li>
<li><strong>Collector and motive.</strong> Students in a recommender-systems class, plus visitors to the MovieLens site, rated films so the lab could study collaborative filtering. These are ratings, not watches.</li>
<li><strong>What this post uses.</strong> The 80 most-active users and the 80 most-rated movies, binned by calendar month. A cell is the mean rating of that user for that movie in that month, or missing. <code>src/fetch_data.py</code> writes <code>data/movielens.npz</code>. A render never downloads the zip. The crop is <img src="https://latex.codecogs.com/png.latex?80%5Ctimes%2080%5Ctimes%208"> with 4,797 observed cells — about 9% of the array.</li>
<li><strong>The business problem.</strong> A catalogue page cannot show an empty slot. The product is a ranked list: which title to put in front of this user this month.</li>
<li><strong>What solving buys.</strong> A held-out rating is filled, so a title the user would like is not buried. Inventory and attention go to that title.</li>
<li><strong>What a wrong inverse costs.</strong> Rank 3 and rank 8 fill the same hole with different numbers. One number says “show <em>The Terminator</em>”. The other says “do not”. A bad fill wastes a slot and trains the next model on a rating nobody gave.</li>
<li><strong>Why a tensor method.</strong> User, movie, and month are three modes. Flattening to user <img src="https://latex.codecogs.com/png.latex?%5Ctimes"> movie throws the month away. A rating in October is not a rating in April.</li>
</ul>
<div id="ml-load" class="cell" data-execution_count="4">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1">ml <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.load(DATA <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"movielens.npz"</span>, allow_pickle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb5-2">ratings <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ml[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ratings"</span>]</span>
<span id="cb5-3">obs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.isfinite(ratings)</span>
<span id="cb5-4"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(</span>
<span id="cb5-5">    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"crop </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>ratings<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(obs.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>())<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> observed cells "</span></span>
<span id="cb5-6">    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"(</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> obs<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>mean()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">%), months </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(ml[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'months'</span>])<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb5-7">)</span>
<span id="cb5-8"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"first titles:"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">", "</span>.join(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(t) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> t <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> ml[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"titles"</span>][:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>]))</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>crop (80, 80, 8), 4797 observed cells (9.4%), months [np.str_('1997-09'), np.str_('1997-10'), np.str_('1997-11'), np.str_('1997-12'), np.str_('1998-01'), np.str_('1998-02'), np.str_('1998-03'), np.str_('1998-04')]
first titles: Star Wars (1977), Contact (1997), Fargo (1996), Return of the Jedi (1983), Liar Liar (1997)</code></pre>
</div>
</div>
<div id="cell-fig-ratings" class="cell" data-fig-height="5.4" data-fig-width="7" data-execution_count="5">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1">nov <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(ml[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"months"</span>]).index(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1997-11"</span>)</span>
<span id="cb7-2">panel <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ratings[:, :, nov]</span>
<span id="cb7-3">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.4</span>))</span>
<span id="cb7-4">im <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ax.imshow(panel, cmap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"magma"</span>, vmin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, vmax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, aspect<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"auto"</span>)</span>
<span id="cb7-5">ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"movie (most-rated first)"</span>)</span>
<span id="cb7-6">ax.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user (most-active first)"</span>)</span>
<span id="cb7-7">ax.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1997-11"</span>)</span>
<span id="cb7-8">fig.colorbar(im, ax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ax, fraction<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.046</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mean rating"</span>)</span>
<span id="cb7-9">fig.tight_layout()</span>
<span id="cb7-10">plt.show()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-ratings" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-ratings-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/tensor-inverse-examples/index_files/figure-html/fig-ratings-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-ratings-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: Mean rating by user and movie in 1997-11. White is missing. Most of the month is empty; that emptiness is the sampling operator.
</figcaption>
</figure>
</div>
</div>
</div>
</section>
<section id="an-industrial-dryer" class="level3" data-number="3.3">
<h3 data-number="3.3" class="anchored" data-anchor-id="an-industrial-dryer"><span class="header-section-number">3.3</span> An industrial dryer</h3>
<ul>
<li><strong>Provenance.</strong> DaISy dataset <a href="https://ftp.esat.kuleuven.be/pub/SISTA/data/process_industry/dryer2.txt">96-016</a>, “Data from an industrial dryer (supplied by Cambridge Control Ltd)”, contributed by Jan Maciejowski. 867 samples at 10 s. Three inputs: fuel flow, hot-gas exhaust fan speed, raw-material flow. Three outputs: dry-bulb temperature, wet-bulb temperature, moisture of the raw material.</li>
<li><strong>Collector and motive.</strong> Cambridge Control recorded a working dryer so multivariable identification methods could be compared on one plant (Maciejowski 1996; Chou and Maciejowski 1997). De Moor hosts the file in <a href="https://homes.esat.kuleuven.be/~smc/daisy/">DaISy</a>.</li>
<li><strong>What this post uses.</strong> The six series, centred. <code>data/dryer.npz</code> is committed. A render never hits the KU Leuven host.</li>
<li><strong>The business problem.</strong> The dryer has to hit a moisture target without scorching the product or wasting fuel. The product of the inverse is a set of actuator settings: fuel, fan, feed.</li>
<li><strong>What solving buys.</strong> You can ask “what input window gives this moisture and these temperatures?” and get a number you can put on the plant.</li>
<li><strong>What a wrong inverse costs.</strong> One inverse of the fitted tensor asks for a small change in feed. Another, on the same target, asks for a thousand-unit dump. A wrong pick over-dries a batch, or ships it wet.</li>
<li><strong>Why a tensor inverse.</strong> The plant map is output <img src="https://latex.codecogs.com/png.latex?%5Ctimes"> input <img src="https://latex.codecogs.com/png.latex?%5Ctimes"> lag. That is three physical modes. Flattening them into one matrix hides which inverse you took.</li>
</ul>
<div id="dryer-load" class="cell" data-execution_count="6">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1">dr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.load(DATA <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dryer.npz"</span>, allow_pickle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb8-2">U_raw, Y_raw <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dr[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"U"</span>].astype(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>), dr[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Y"</span>].astype(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>)</span>
<span id="cb8-3">U <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> U_raw <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> U_raw.mean(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb8-4">Y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Y_raw <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> Y_raw.mean(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb8-5">dt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(dr[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dt"</span>])</span>
<span id="cb8-6"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(dr[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cite"</span>])</span>
<span id="cb8-7"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(U)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> samples at </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>dt<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.0f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> s, inputs </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(dr[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'inputs'</span>])<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, outputs </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(dr[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'outputs'</span>])<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>96-016 industrial dryer (Cambridge Control Ltd)
867 samples at 10 s, inputs [np.str_('fuel flow'), np.str_('exhaust fan'), np.str_('raw-material flow')], outputs [np.str_('dry-bulb temp'), np.str_('wet-bulb temp'), np.str_('moisture')]</code></pre>
</div>
</div>
<div id="cell-fig-dryer" class="cell" data-fig-height="5.2" data-fig-width="10" data-execution_count="7">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1">tt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.arange(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(U)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> dt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">60.0</span></span>
<span id="cb10-2">fig, axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.2</span>), sharex<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb10-3"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>):</span>
<span id="cb10-4">    axes[i, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].plot(tt, U[:, i], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>TEAL, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span>)</span>
<span id="cb10-5">    axes[i, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].plot(tt, Y[:, i], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>CORAL, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span>)</span>
<span id="cb10-6">    axes[i, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].set_ylabel(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(dr[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"inputs"</span>][i]))</span>
<span id="cb10-7">    axes[i, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].set_ylabel(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(dr[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"outputs"</span>][i]))</span>
<span id="cb10-8">axes[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"minutes"</span>)</span>
<span id="cb10-9">axes[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"minutes"</span>)</span>
<span id="cb10-10">fig.tight_layout()</span>
<span id="cb10-11">plt.show()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-dryer" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-dryer-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/tensor-inverse-examples/index_files/figure-html/fig-dryer-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-dryer-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: Centred dryer inputs (teal) and outputs (coral). Fuel, fan, and feed on the left; dry-bulb, wet-bulb, and moisture on the right.
</figcaption>
</figure>
</div>
</div>
</div>
</section>
</section>
<section id="independence" class="level2 page-columns page-full" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="independence"><span class="header-section-number">4</span> Independence</h2>
<p><strong>Who does this.</strong> A speech engineer pulling one talker out of a meeting recording.</p>
<div class="page-columns page-full">
<div id="fig-poster-speech" class="quarto-float quarto-figure quarto-figure-center anchored page-columns page-full" alt="Poster in four numbered stages plus a footer. One: English, Swedish, and Foochow source waveforms. Two: a constructed 3 by 3 mix, three microphone channels. Three: an isometric cube labelled order 4, 3 by 3 by 3 by 3, the cumulant C. Four: two cards, A inverse keeps English as English, signed-permutation inverse swaps speakers. Footer: the inverse puts each voice on its own channel so a transcript can attribute a quote; a leftover permutation names the wrong speaker.">
<figure class="quarto-float quarto-float-fig figure page-columns page-full">
<div aria-describedby="fig-poster-speech-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca" class="page-columns page-full">
<img src="https://project-delphi.github.io/ml-blog/posts/tensor-inverse-examples/media/poster-speech.png" class="img-fluid figure-img column-page" alt="Poster in four numbered stages plus a footer. One: English, Swedish, and Foochow source waveforms. Two: a constructed 3 by 3 mix, three microphone channels. Three: an isometric cube labelled order 4, 3 by 3 by 3 by 3, the cumulant C. Four: two cards, A inverse keeps English as English, signed-permutation inverse swaps speakers. Footer: the inverse puts each voice on its own channel so a transcript can attribute a quote; a leftover permutation names the wrong speaker.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-poster-speech-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;4: Three voices become one mix, then a <img src="https://latex.codecogs.com/png.latex?3%5Ctimes3%5Ctimes3%5Ctimes3"> cumulant. Two inverses of that tensor both undo the mix. <img src="https://latex.codecogs.com/png.latex?A%5E%7B-1%7D"> keeps the speaker names. <img src="https://latex.codecogs.com/png.latex?P%20A%5E%7B-1%7D"> swaps them.
</figcaption>
</figure>
</div>
</div>
<p>The fourth-order cumulant of a centred, whitened recording <img src="https://latex.codecogs.com/png.latex?Z"> is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AC_%7Bijkl%7D=%5Cmathbb%7BE%7D%5Bz_i%20z_j%20z_k%20z_l%5D-%5Cdelta_%7Bij%7D%5Cdelta_%7Bkl%7D-%5Cdelta_%7Bik%7D%5Cdelta_%7Bjl%7D-%5Cdelta_%7Bil%7D%5Cdelta_%7Bjk%7D.%0A"></p>
<p>Independent non-Gaussian sources make <img src="https://latex.codecogs.com/png.latex?C"> diagonal. A mixing matrix <img src="https://latex.codecogs.com/png.latex?A"> rotates those axes. Recovering <img src="https://latex.codecogs.com/png.latex?A%5E%7B-1%7D"> is a joint diagonalization of the slices of <img src="https://latex.codecogs.com/png.latex?C">. Any signed permutation of <img src="https://latex.codecogs.com/png.latex?A%5E%7B-1%7D"> diagonalizes <img src="https://latex.codecogs.com/png.latex?C"> as well. That leftover is the inverse that is not unique.</p>
<p>The mix here is constructed, so <img src="https://latex.codecogs.com/png.latex?A%5E%7B-1%7D"> is known. Two inverses, then a third that only inverts the covariance.</p>
<div id="ica-inverses" class="cell" data-execution_count="8">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1">Ainv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.inv(A)</span>
<span id="cb11-2">perm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span>
<span id="cb11-3">signs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>])</span>
<span id="cb11-4">Ainv_perm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> C.signed_perm(Ainv, perm, signs)</span>
<span id="cb11-5">rec_true <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Ainv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> mixed</span>
<span id="cb11-6">rec_perm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Ainv_perm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> mixed</span>
<span id="cb11-7">rec_pca <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> C.pca_sources(mixed)</span>
<span id="cb11-8">rec_ica, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> C.fastica(mixed)</span>
<span id="cb11-9"></span>
<span id="cb11-10"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> report(name, rec):</span>
<span id="cb11-11">    sir <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> C.sir_db(rec, sources)</span>
<span id="cb11-12">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:&lt;18}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> SIR dB  </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>sir[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:5.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>sir[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:5.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>sir[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:5.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">   mean </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>sir<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>mean()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:4.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb11-13"></span>
<span id="cb11-14">report(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A inverse"</span>, rec_true)</span>
<span id="cb11-15">report(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"signed perm"</span>, rec_perm)</span>
<span id="cb11-16">report(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PCA / whiten"</span>, rec_pca)</span>
<span id="cb11-17">report(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"FastICA"</span>, rec_ica)</span>
<span id="cb11-18"></span>
<span id="cb11-19">Cz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> C.fourth_cumulant(C.whiten(mixed)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span>
<span id="cb11-20">Cs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> C.fourth_cumulant(C.whiten(sources)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span>
<span id="cb11-21"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"cumulant </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>Cz<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb11-22"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"off-diagonal energy  sources </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>C<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>offdiag_energy(Cs)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">   mix </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>C<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>offdiag_energy(Cz)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb11-23"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A inverse and the signed permutation are different matrices: "</span></span>
<span id="cb11-24">      <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"||W1 - W2|| / ||W1|| = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>norm(Ainv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> Ainv_perm) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>norm(Ainv)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>A inverse          SIR dB  144.9 145.3 146.8   mean 145.7
signed perm        SIR dB  145.3 146.8 144.9   mean 145.7
PCA / whiten       SIR dB   15.9  16.0  20.2   mean 17.4
FastICA            SIR dB   16.3   9.8  10.8   mean 12.3
cumulant (3, 3, 3, 3)
off-diagonal energy  sources 0.007   mix 0.081
A inverse and the signed permutation are different matrices: ||W1 - W2|| / ||W1|| = 1.47</code></pre>
</div>
</div>
<p><img src="https://latex.codecogs.com/png.latex?A%5E%7B-1%7D"> and <img src="https://latex.codecogs.com/png.latex?P%20A%5E%7B-1%7D"> both recover the three voices. Mean SIR is high because the mix is known. They are not the same matrix: relative Frobenius difference <img src="https://latex.codecogs.com/png.latex?1.47">. The leftover is a permutation and two sign flips. A transcript that uses the second inverse swaps speakers and flips a polarity. The words are the same. The names are not.</p>
<p>PCA inverts the covariance, not the cumulant. Whitening is a second-order inverse. The leftover rotation stays. FastICA picks one rotation from the fourth-order structure. On four seconds of real speech it is a finite-sample pick, not the true <img src="https://latex.codecogs.com/png.latex?A%5E%7B-1%7D">.</p>
<div id="cell-fig-ica" class="cell" data-fig-height="3.6" data-fig-width="10" data-execution_count="9">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1">t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.arange(sources.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> rate</span>
<span id="cb13-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># After the signed permutation, English (row 0) lives on recovered row 2 with a plus sign.</span></span>
<span id="cb13-3">fig, axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.6</span>), sharex<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb13-4">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].plot(t, sources[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>TEAL, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span>)</span>
<span id="cb13-5">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"source 1"</span>)</span>
<span id="cb13-6">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].plot(t, rec_true[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>GOLD, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span>)</span>
<span id="cb13-7">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A inverse"</span>)</span>
<span id="cb13-8">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>].plot(t, rec_perm[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>CORAL, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span>)</span>
<span id="cb13-9">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>].set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"signed perm"</span>)</span>
<span id="cb13-10">axes[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"time (s)"</span>)</span>
<span id="cb13-11">fig.tight_layout()</span>
<span id="cb13-12">plt.show()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-ica" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-ica-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/tensor-inverse-examples/index_files/figure-html/fig-ica-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-ica-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;5: Source 1 against two recoveries. Teal is the English track. Gold is A inverse. Coral is the signed permutation, which has moved this voice to another channel and flipped its sign.
</figcaption>
</figure>
</div>
</div>
</div>
<p>The coral trace is the English voice, recovered by the second inverse, on a different channel and with the opposite sign. Both inverses solved the mix. Only one of them keeps the speaker labels.</p>
</section>
<section id="completion" class="level2 page-columns page-full" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="completion"><span class="header-section-number">5</span> Completion</h2>
<p><strong>Who does this.</strong> A recommender filling a catalogue cell so a page is not empty.</p>
<div class="page-columns page-full">
<div id="fig-poster-ratings" class="quarto-float quarto-figure quarto-figure-center anchored page-columns page-full" alt="Poster in four numbered stages plus a footer. One: isometric cube user 80 by movie 80 by month 8, 4,797 of 51,200 cells observed. Two: the same cube as a sampling operator, holes in the null space. Three: two CP inverses, rank 3 says Terminator 3.1 show it, rank 8 says 2.0 park it. Four: two catalogue pages, one uses the slot, one hides the film. Footer: a rank picks one filling of the holes; a wrong leftover hides a film the user rated.">
<figure class="quarto-float quarto-float-fig figure page-columns page-full">
<div aria-describedby="fig-poster-ratings-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca" class="page-columns page-full">
<img src="https://project-delphi.github.io/ml-blog/posts/tensor-inverse-examples/media/poster-ratings.png" class="img-fluid figure-img column-page" alt="Poster in four numbered stages plus a footer. One: isometric cube user 80 by movie 80 by month 8, 4,797 of 51,200 cells observed. Two: the same cube as a sampling operator, holes in the null space. Three: two CP inverses, rank 3 says Terminator 3.1 show it, rank 8 says 2.0 park it. Four: two catalogue pages, one uses the slot, one hides the film. Footer: a rank picks one filling of the holes; a wrong leftover hides a film the user rated.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-poster-ratings-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;6: An <img src="https://latex.codecogs.com/png.latex?80%5Ctimes80%5Ctimes8"> rating tensor, mostly empty. The sampling operator has no inverse. Rank 3 fills <em>The Terminator</em> at 3.1 and shows it. Rank 8 fills 2.0 and hides it.
</figcaption>
</figure>
</div>
</div>
<p>The sampling operator <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BP%7D_%5COmega"> keeps the observed cells and throws the rest away. Its null space is every <img src="https://latex.codecogs.com/png.latex?80%5Ctimes%2080%5Ctimes%208"> array that is zero on <img src="https://latex.codecogs.com/png.latex?%5COmega">. No inverse of <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BP%7D_%5COmega"> exists. A rank-<img src="https://latex.codecogs.com/png.latex?R"> CP model picks one point in that affine space. ALS is how the point is found. The rank is a choice. The start is a choice. Both change the filling.</p>
<p>Hold out 20% of the observed cells (seed 7). Complete from the rest at rank 3 and at rank 8. Flatten to user <img src="https://latex.codecogs.com/png.latex?%5Ctimes"> movie, fill with a rank-3 SVD, and repeat that matrix across months.</p>
<div id="complete-fit" class="cell" data-execution_count="10">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb14-1">train <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> K.holdout(obs, frac<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.2</span>, seed<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>)</span>
<span id="cb14-2">held <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> obs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>train</span>
<span id="cb14-3">X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.where(obs, ratings, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>)</span>
<span id="cb14-4">fill3 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> K.cp_complete(X, train, rank<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, seed<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>)</span>
<span id="cb14-5">fill8 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> K.cp_complete(X, train, rank<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, seed<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>)</span>
<span id="cb14-6">flat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> K.flatten_svd_complete(X, train, rank<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb14-7"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"train </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(train.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>())<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> cells, held out </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(held.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>())<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb14-8"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"RMSE rank 3   </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>K<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>rmse(fill3, ratings, held)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb14-9"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"RMSE rank 8   </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>K<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>rmse(fill8, ratings, held)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb14-10"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"RMSE flatten  </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>K<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>rmse(flat, ratings, held)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb14-11"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"mean |rank3 - rank8| on held cells </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>mean(np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(fill3[held] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> fill8[held]))<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>train 3838 cells, held out 959
RMSE rank 3   0.932
RMSE rank 8   0.924
RMSE flatten  0.919
mean |rank3 - rank8| on held cells 0.187</code></pre>
</div>
</div>
<p>On this crop the flattened SVD is slightly better on RMSE. That is not a win for flattening as a method. It is a warning that RMSE on a 9% observed tensor is a weak score. The two CP ranks still disagree on the held cells by about 0.2 stars on average. The hole that disagrees most is the one a catalogue page would have to act on.</p>
<div id="complete-hole" class="cell" data-execution_count="11">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb16-1">diff <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(fill3 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> fill8)</span>
<span id="cb16-2">diff[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>held] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span></span>
<span id="cb16-3">i, j, k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.unravel_index(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(np.argmax(diff)), diff.shape)</span>
<span id="cb16-4"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(</span>
<span id="cb16-5">    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"user </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(ml[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'users'</span>][i])<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>ml[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'titles'</span>][j]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>ml[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'months'</span>][k]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">: "</span></span>
<span id="cb16-6">    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"true </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>ratings[i, j, k]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, rank 3 </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>fill3[i, j, k]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, "</span></span>
<span id="cb16-7">    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"rank 8 </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>fill8[i, j, k]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, flatten </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>flat[i, j, k]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb16-8">)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>user 269, Terminator, The (1984), 1998-04: true 3.0, rank 3 3.11, rank 8 1.99, flatten 2.67</code></pre>
</div>
</div>
<p>User 269, <em>The Terminator</em> (1984), April 1998. The true rating is 3. Rank 3 fills 3.1 — a middling title, showable. Rank 8 fills 2.0 — park it. The flatten, which has no month, fills one number for every month of that user-movie pair. A ranking that uses rank 8 hides a film the user rated 3. A ranking that uses rank 3 shows it. Both ranks solved the same incomplete tensor. They shipped two different pages.</p>
<div id="cell-fig-complete" class="cell" data-fig-height="4.6" data-fig-width="10" data-execution_count="12">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb18-1">month <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> k</span>
<span id="cb18-2">show <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.where(held[:, :, month], fill3[:, :, month], np.nan)</span>
<span id="cb18-3">delta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.where(held[:, :, month], fill8[:, :, month] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> fill3[:, :, month], np.nan)</span>
<span id="cb18-4">fig, axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.6</span>))</span>
<span id="cb18-5">im0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].imshow(show, cmap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"magma"</span>, vmin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, vmax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, aspect<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"auto"</span>)</span>
<span id="cb18-6">im1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].imshow(delta, cmap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"coolwarm"</span>, vmin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span>, vmax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span>, aspect<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"auto"</span>)</span>
<span id="cb18-7">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].set_title(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"rank 3 fill, </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>ml[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'months'</span>][month]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb18-8">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rank 8 minus rank 3"</span>)</span>
<span id="cb18-9"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> ax <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> axes:</span>
<span id="cb18-10">    ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"movie"</span>)</span>
<span id="cb18-11">    ax.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>)</span>
<span id="cb18-12">fig.colorbar(im0, ax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], fraction<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.046</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rating"</span>)</span>
<span id="cb18-13">fig.colorbar(im1, ax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], fraction<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.046</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"stars"</span>)</span>
<span id="cb18-14">fig.tight_layout()</span>
<span id="cb18-15">plt.show()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-complete" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-complete-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/tensor-inverse-examples/index_files/figure-html/fig-complete-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-complete-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;7: Held-out cells for one month. Left: rank-3 fill. Right: rank-8 fill minus rank-3 fill. The right panel is the leftover. It is not noise: it is the part of the inverse that the rank chose.
</figcaption>
</figure>
</div>
</div>
</div>
</section>
<section id="identification" class="level2 page-columns page-full" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="identification"><span class="header-section-number">6</span> Identification</h2>
<p><strong>Who does this.</strong> A control engineer setting fuel, fan, and feed on a dryer so moisture hits a target.</p>
<div class="page-columns page-full">
<div id="fig-poster-dryer" class="quarto-float quarto-figure quarto-figure-center anchored page-columns page-full" alt="Poster in four numbered stages plus a footer. One: drawn dryer, fuel fan feed in, two temperatures and moisture out. Two: isometric cube H, output 3 by input 3 by lag L, L is 5 or 15. Three: two inverses, lag-0 slice pseudoinverse versus stacked Einstein Moore-Penrose. Four: two settings, feed about plus 1,200 versus feed about 0. Footer: inverting H for a desired y is a second product; a wrong leftover over-dries a batch or ships it wet.">
<figure class="quarto-float quarto-float-fig figure page-columns page-full">
<div aria-describedby="fig-poster-dryer-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca" class="page-columns page-full">
<img src="https://project-delphi.github.io/ml-blog/posts/tensor-inverse-examples/media/poster-dryer.png" class="img-fluid figure-img column-page" alt="Poster in four numbered stages plus a footer. One: drawn dryer, fuel fan feed in, two temperatures and moisture out. Two: isometric cube H, output 3 by input 3 by lag L, L is 5 or 15. Three: two inverses, lag-0 slice pseudoinverse versus stacked Einstein Moore-Penrose. Four: two settings, feed about plus 1,200 versus feed about 0. Footer: inverting H for a desired y is a second product; a wrong leftover over-dries a batch or ships it wet.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-poster-dryer-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;8: A dryer with three inputs and three outputs becomes an impulse-response tensor <img src="https://latex.codecogs.com/png.latex?H%5Cin%5Cmathbb%7BR%7D%5E%7B3%5Ctimes3%5Ctimes%20L%7D">. The lag-0 inverse asks for a 1,200-unit feed dump. The stacked inverse asks for a small correction. Same moisture target.
</figcaption>
</figure>
</div>
</div>
<p>A linear MIMO plant is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ay(t)=%5Csum_%7Bk=0%7D%5E%7BL-1%7D%20H_%7B:,:,k%7D%5C,u(t-k).%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?H"> is output <img src="https://latex.codecogs.com/png.latex?%5Ctimes"> input <img src="https://latex.codecogs.com/png.latex?%5Ctimes"> lag. Fitting <img src="https://latex.codecogs.com/png.latex?H"> is least squares on a lagged design. That solve is unique for a fixed <img src="https://latex.codecogs.com/png.latex?L"> when the design has full rank. <img src="https://latex.codecogs.com/png.latex?L"> is not unique. Inverting <img src="https://latex.codecogs.com/png.latex?H"> for a control input is a different product, and two products disagree.</p>
<p>Fit <img src="https://latex.codecogs.com/png.latex?L=5"> and <img src="https://latex.codecogs.com/png.latex?L=15"> on the centred dryer. Then invert the <img src="https://latex.codecogs.com/png.latex?L=5"> tensor two ways, on a target taken from the model so the arithmetic is consistent.</p>
<div id="mimo-fit" class="cell" data-execution_count="13">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb19-1">H5 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> M.fit_fir(U, Y, n_lags<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>)</span>
<span id="cb19-2">H15 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> M.fit_fir(U, Y, n_lags<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">15</span>)</span>
<span id="cb19-3">Yhat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> M.predict(H5, U)</span>
<span id="cb19-4">t_star <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">200</span></span>
<span id="cb19-5">y_star <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Yhat[t_star]</span>
<span id="cb19-6">u0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> M.invert_lag0(H5, y_star)</span>
<span id="cb19-7">u_st <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> M.invert_stacked(H5, y_star)</span>
<span id="cb19-8">u0_15 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> M.invert_lag0(H15, y_star)</span>
<span id="cb19-9"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"fit RMSE   L=5 </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>M<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>fit_rmse(H5, U, Y)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">   L=15 </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>M<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>fit_rmse(H15, U, Y)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb19-10"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"cond(H[:,:,0])  L=5 </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>cond(H5[:, :, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.0f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">   L=15 </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>cond(H15[:, :, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.0f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb19-11"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"target y (centred) </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>array2string(y_star, precision<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb19-12"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"lag-0 inverse, L=5   </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>array2string(u0, precision<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb19-13"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"stacked inverse, L=5 </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>array2string(u_st[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>], precision<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  (lag 0 of the window)"</span>)</span>
<span id="cb19-14"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"lag-0 inverse, L=15  </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>array2string(u0_15, precision<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb19-15"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"||u0(L=5) - stacked lag0|| </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>norm(u0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> u_st[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>])<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb19-16"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"||u0(L=5) - u0(L=15)||     </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>norm(u0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> u0_15)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb19-17"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"lag-0 reconstructs y  </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>array2string(H5[:, :, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> u0, precision<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb19-18"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"stacked reconstructs  </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>array2string(M.apply_window(H5, u_st), precision<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>fit RMSE   L=5 1.610   L=15 1.329
cond(H[:,:,0])  L=5 1102   L=15 369
target y (centred) [-6.53  -2.956  0.359]
lag-0 inverse, L=5   [ -30.52   56.31 1262.67]
stacked inverse, L=5 [-2.61  3.07  0.01]  (lag 0 of the window)
lag-0 inverse, L=15  [  -69.69   147.95 11581.81]
||u0(L=5) - stacked lag0|| 1264.1
||u0(L=5) - u0(L=15)||     10319.6
lag-0 reconstructs y  [-6.53  -2.956  0.359]
stacked reconstructs  [-6.53  -2.956  0.359]</code></pre>
</div>
</div>
<p><img src="https://latex.codecogs.com/png.latex?L=15"> fits the training series a little better than <img src="https://latex.codecogs.com/png.latex?L=5">. Both are plausible plants. Their lag-0 inverses, asked for the same <img src="https://latex.codecogs.com/png.latex?y">, disagree by thousands of raw-material units.</p>
<p>The <img src="https://latex.codecogs.com/png.latex?L=5"> lag-0 slice has condition number about <img src="https://latex.codecogs.com/png.latex?10%5E3">. Its inverse reconstructs <img src="https://latex.codecogs.com/png.latex?y"> and asks for a huge feed. The stacked inverse of the same <img src="https://latex.codecogs.com/png.latex?H"> asks for a small window. One inverse is a one-step product, <img src="https://latex.codecogs.com/png.latex?H_%7B:,:,0%7D%5E%5Cdagger%20y">. The other is the Einstein / Moore–Penrose inverse of the unfolding <img src="https://latex.codecogs.com/png.latex?H%5Cin%5Cmathbb%7BR%7D%5E%7B3%5Ctimes(3%5Ccdot%205)%7D">. Both are legitimate inverses of <img src="https://latex.codecogs.com/png.latex?H">. They are different control moves.</p>
<p>A dryer that takes the lag-0 number dumps feed. A dryer that takes the stacked number makes a small correction. The moisture target was the same.</p>
<div id="cell-fig-mimo" class="cell" data-fig-height="3.8" data-fig-width="10" data-execution_count="14">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb21" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb21-1">fig, axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.8</span>))</span>
<span id="cb21-2">im <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].imshow(H5[:, :, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], cmap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"coolwarm"</span>)</span>
<span id="cb21-3">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].set_xticks(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>), <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(dr[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"inputs"</span>]), rotation<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>, ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"right"</span>)</span>
<span id="cb21-4">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].set_yticks(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>), <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(dr[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"outputs"</span>]))</span>
<span id="cb21-5">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"H[:,:,0] at L=5"</span>)</span>
<span id="cb21-6">fig.colorbar(im, ax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], fraction<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.046</span>)</span>
<span id="cb21-7">labs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fuel"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fan"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"feed"</span>]</span>
<span id="cb21-8">x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.arange(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb21-9">w <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.35</span></span>
<span id="cb21-10">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].bar(x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> w <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, u0, width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>w, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>CORAL, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lag-0 pinv"</span>)</span>
<span id="cb21-11">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].bar(x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> w <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, u_st[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>], width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>w, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>TEAL, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"stacked, lag 0"</span>)</span>
<span id="cb21-12">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].set_xticks(x, labs)</span>
<span id="cb21-13">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"centred input"</span>)</span>
<span id="cb21-14">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].legend(frameon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb21-15">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"two inverses, one target"</span>)</span>
<span id="cb21-16">fig.tight_layout()</span>
<span id="cb21-17">plt.show()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-mimo" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-mimo-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/tensor-inverse-examples/index_files/figure-html/fig-mimo-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-mimo-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;9: Lag-0 slice of H at L=5, and the two control inputs it produces for one target. The stacked window is drawn at lag 0 only, so the two bars are the same three actuators.
</figcaption>
</figure>
</div>
</div>
</div>
<p>The coral bars are the one-step inverse. The teal bars are the stacked inverse, lag 0. The feed bars do not fit on the same scale by accident. That gap is the leftover.</p>
</section>
<section id="side-by-side" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="side-by-side"><span class="header-section-number">7</span> Side by side</h2>
<p>Every number below was computed by a cell above.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
</colgroup>
<thead>
<tr class="header">
<th>Problem</th>
<th>Extra structure</th>
<th>What is unique</th>
<th>What is not</th>
<th>What flattening costs</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Speech mix</td>
<td>independence, at most one Gaussian</td>
<td>the sources, up to permutation and scale</td>
<td>which signed permutation you keep; FastICA’s finite-sample rotation</td>
<td>PCA inverts the covariance and leaves the cumulant’s rotation</td>
</tr>
<tr class="even">
<td>MovieLens</td>
<td>CP rank <img src="https://latex.codecogs.com/png.latex?R"></td>
<td>nothing about the holes</td>
<td>the filling. Rank 3 vs rank 8: <em>Terminator</em>, April 1998, 3.1 vs 2.0</td>
<td>user <img src="https://latex.codecogs.com/png.latex?%5Ctimes"> movie throws month away; RMSE can look fine</td>
</tr>
<tr class="odd">
<td>Dryer</td>
<td>FIR length <img src="https://latex.codecogs.com/png.latex?L">, then a product</td>
<td><img src="https://latex.codecogs.com/png.latex?H"> at a fixed <img src="https://latex.codecogs.com/png.latex?L"> when the design is full rank</td>
<td><img src="https://latex.codecogs.com/png.latex?L"> itself; the control inverse of <img src="https://latex.codecogs.com/png.latex?H"> (lag-0 vs stacked)</td>
<td>a <img src="https://latex.codecogs.com/png.latex?3%5Ctimes%2015"> matrix does not say which product you inverted</td>
</tr>
</tbody>
</table>
<p>Two of the three rows say that flattening is a different inverse, not a cheaper one. The ratings row says flattening can win a weak score and still hide a month.</p>
</section>
<section id="constraints" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="constraints"><span class="header-section-number">8</span> Constraints</h2>
<ul>
<li><strong>Speech.</strong> Four seconds at 8 kHz is a short record for a fourth-order statistic. FastICA’s leftover is a finite-sample leftover. The signed-permutation leftover is algebraic and does not go away with more data.</li>
<li><strong>Ratings.</strong> 9% observed. ALS has local minima. A second start at the same rank would move the filling again. RMSE on 959 held cells does not rank a catalogue.</li>
<li><strong>Dryer.</strong> 867 samples. <img src="https://latex.codecogs.com/png.latex?L=15"> already spends 45 parameters per output. A longer FIR will fit and will invert to a different <img src="https://latex.codecogs.com/png.latex?u">. The series is one plant on one week. It is not a licence to retune a dryer from this page.</li>
</ul>
<p>Inverses. Are. Not. Unique. Structure. Picks. One. Downstream. Feels. The. Choice.</p>
</section>
<section id="references" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="references"><span class="header-section-number">9</span> References</h2>
<ul>
<li>Cardoso, J.-F., and Souloumiac, A. (1993). Blind beamforming for non-Gaussian signals. <em>IEE Proceedings F</em> 140(6), 362–370.</li>
<li>Chou, C. T., and Maciejowski, J. M. (1997). <a href="https://doi.org/10.1109/9.599969">System identification using balanced parametrizations</a>. <em>IEEE Transactions on Automatic Control</em> 42(7), 956–974.</li>
<li>De Moor, B. L. R. (ed.). <a href="https://homes.esat.kuleuven.be/~smc/daisy/">DaISy: Database for the Identification of Systems</a>. ESAT/STADIUS, KU Leuven. Dataset 96-016, industrial dryer.</li>
<li>Harper, F. M., and Konstan, J. A. (2015). <a href="https://doi.org/10.1145/2827872">The MovieLens datasets: history and context</a>. <em>ACM Transactions on Interactive Intelligent Systems</em> 5(4), 19.</li>
<li>Hyvärinen, A., and Oja, E. (2000). Independent component analysis: algorithms and applications. <em>Neural Networks</em> 13(4–5), 411–430.</li>
<li>Kolda, T. G., and Bader, B. W. (2009). Tensor decompositions and applications. <em>SIAM Review</em> 51(3), 455–500.</li>
<li>Maciejowski, J. M. (1996). Parameter estimation of multivariable systems using balanced realizations. In Bittanti, S. (ed.), <em>Identification, Adaptation, and Learning</em>. Springer.</li>
<li><a href="https://grouplens.org/datasets/movielens/100k/">MovieLens 100K</a> — GroupLens Research.</li>
<li><a href="https://commons.wikimedia.org/wiki/File:Recording_of_speaker_of_British_English_(Received_Pronunciation).ogg">Recording of speaker of British English (Received Pronunciation)</a> — P. Roach / International Phonetic Association, CC BY-SA 3.0.</li>
<li><a href="https://commons.wikimedia.org/wiki/File:Sv-The_North_Wind_and_the_Sun.ogg">Sv-The North Wind and the Sun</a> — Wikimedia Commons.</li>
<li><a href="https://commons.wikimedia.org/wiki/File:Cdo_northwind_sun_04.ogg">Cdo northwind sun 04</a> — GnuDoyng, public domain.</li>
<li><a href="../tensor-factorizations/">Tensor Factorizations and Tensor Inverses</a> — CP, Tucker, TT, t-SVD, and the four inverses.</li>
<li><a href="../uses-of-tensor-factorizations/">Uses of Tensor Factorizations</a> — the compression case.</li>
<li><a href="../tensor-inverses-in-practice/">Tensor Inverses in Practice</a> — compliance, Pavia, a scan, and Chicago counts.</li>
</ul>


</section>

 ]]></description>
  <category>Linear Algebra</category>
  <category>Machine Learning</category>
  <category>Numerical Methods</category>
  <category>Tensors</category>
  <guid>https://project-delphi.github.io/ml-blog/posts/tensor-inverse-examples/</guid>
  <pubDate>Mon, 31 Aug 2026 05:00:00 GMT</pubDate>
  <media:content url="https://project-delphi.github.io/ml-blog/posts/tensor-inverse-examples/cover.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Tensor Inverses in Practice</title>
  <dc:creator>Ravi Kalia</dc:creator>
  <link>https://project-delphi.github.io/ml-blog/posts/tensor-inverses-in-practice/</link>
  <description><![CDATA[ 





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/tensor-inverses-in-practice/cover.png" class="img-fluid figure-img"></p>
<figcaption>Tensor Inverses in Practice</figcaption>
</figure>
</div>
<p>Four real problems where the operator is a tensor. For each one, this post names the product that applies, takes the inverse in that product, and measures what flattening the tensor into a matrix would have cost.</p>
<p>Two earlier posts give the background. <a href="../tensor-factorizations/">Tensor Factorizations and Tensor Inverses</a> has the algebra. <a href="../uses-of-tensor-factorizations/">Uses of Tensor Factorizations</a> has the compression case. The first one shows that a tensor has no default product. Because of that, it has no default inverse either.</p>
<p>This post answers the question that leaves open. For a given problem, which product applies, and what does flattening cost?</p>
<p>The table of inverses comes first. Then the four problems. Then all the numbers side by side.</p>
<section id="words-used-here" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="words-used-here"><span class="header-section-number">1</span> Words used here</h2>
<p>Some of these words have a different everyday meaning. This is what they mean in this post.</p>
<ul>
<li><strong>Mode.</strong> One axis of an array. A <img src="https://latex.codecogs.com/png.latex?52%5Ctimes24%5Ctimes8"> array has three modes.</li>
<li><strong>Order.</strong> How many modes an array has. A vector has order 1, a matrix order 2.</li>
<li><strong>Frontal slice.</strong> For a three-mode array <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BA%7D">, the matrix <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BA%7D%5B:,:,k%5D">. There is one for each value of <img src="https://latex.codecogs.com/png.latex?k">.</li>
<li><strong>Flatten.</strong> Rearrange a tensor into a matrix or a vector. Other papers say <em>unfold</em> or <em>vectorise</em> for the same idea. This post says <em>flatten</em> throughout.</li>
<li><strong>Operator.</strong> Anything that takes an input and returns an output. Here it is usually a tensor that takes a tensor and returns a tensor.</li>
<li><strong>Rank.</strong> How many independent directions an operator really uses. An operator on nine dimensions can have rank 6.</li>
<li><strong>Null space.</strong> The set of inputs that an operator sends to zero. An operator with a null space larger than <img src="https://latex.codecogs.com/png.latex?%5C%7B0%5C%7D"> has no inverse.</li>
<li><strong>Inverse.</strong> The operator that undoes another operator.</li>
<li><strong>Pseudoinverse.</strong> What you use when no inverse exists. It undoes the operator on the directions the operator keeps, and returns zero on the directions the operator destroys.</li>
<li><strong>Condition number, <img src="https://latex.codecogs.com/png.latex?%5Ckappa">.</strong> How much an operator can magnify an error. If <img src="https://latex.codecogs.com/png.latex?%5Ckappa%20=%2010%5E%7B12%7D">, an error in the twelfth digit of the input can reach the first digit of the output.</li>
<li><strong>Residual.</strong> How far a computed answer sits from the answer it should give.</li>
<li><strong>Covariance.</strong> A table saying how strongly each pair of measurements moves together.</li>
<li><strong>Precision matrix.</strong> The inverse of a covariance matrix. Here the word does not mean accuracy.</li>
</ul>
</section>
<section id="the-kinds-of-tensor-inverse" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="the-kinds-of-tensor-inverse"><span class="header-section-number">2</span> The kinds of tensor inverse</h2>
<p>A matrix inverse undoes matrix multiplication. There is one matrix product, so there is one matrix inverse.</p>
<p>Tensors work differently. There are several tensor products, and each one has its own inverse. You cannot use one in place of another, and none of them is <em>the</em> tensor inverse.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
</colgroup>
<thead>
<tr class="header">
<th>Inverse</th>
<th>Product it inverts</th>
<th>Order and shape it needs</th>
<th>Reach for it when</th>
<th>What it costs</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Mode-<img src="https://latex.codecogs.com/png.latex?n"> pseudoinverse</strong></td>
<td>mode-<img src="https://latex.codecogs.com/png.latex?n"> product <img src="https://latex.codecogs.com/png.latex?%5Ctimes_n"></td>
<td>any order</td>
<td>you want to recover a factor, a mixing matrix, or a change of basis along <strong>one</strong> mode: <img src="https://latex.codecogs.com/png.latex?A%20=%20Y_%7B(n)%7DX_%7B(n)%7D%5E%7B%5Cdagger%7D"></td>
<td>it inverts a matrix that acts on one mode. It does <strong>not</strong> invert the tensor. Readers confuse these two more often than anything else in this table</td>
</tr>
<tr class="even">
<td><strong>t-inverse / t-pseudoinverse</strong></td>
<td>t-product <img src="https://latex.codecogs.com/png.latex?*"></td>
<td>third order, with a meaningful third mode</td>
<td>the third mode is time, wavelength, or a frequency grid: video, hyperspectral cubes, some fMRI layouts</td>
<td>one FFT along mode 3, then <img src="https://latex.codecogs.com/png.latex?n_3"> matrix inverses. Each slice is inverted on its own, so a single bad slice ruins the whole answer</td>
</tr>
<tr class="odd">
<td><strong>Einstein-product inverse</strong></td>
<td>Einstein product <img src="https://latex.codecogs.com/png.latex?%5Ccircledast"></td>
<td>even order, square: <img src="https://latex.codecogs.com/png.latex?I_1%7B%5Ctimes%7D%5Ccdots%7B%5Ctimes%7DI_N%7B%5Ctimes%7DI_1%7B%5Ctimes%7D%5Ccdots%7B%5Ctimes%7DI_N"></td>
<td>the tensor really <em>is</em> a linear map that takes a tensor and returns one: stiffness and compliance, or a PDE written out on a grid</td>
<td>it is the same object as a <img src="https://latex.codecogs.com/png.latex?(%5Cprod_k%20I_k)%5E2"> matrix, and it exists exactly when that matrix can be inverted</td>
</tr>
<tr class="even">
<td><strong>Multilinear Moore–Penrose</strong></td>
<td>Einstein product</td>
<td>even order, rectangular or rank-deficient</td>
<td>same setting, but the operator is not square or not full rank</td>
<td>the one object that satisfies the four Penrose conditions; at order 2 it is the ordinary matrix pseudoinverse</td>
</tr>
<tr class="odd">
<td><strong>Tucker- or TT-format inverse</strong></td>
<td>mode products, TT contraction</td>
<td>any order, low multilinear or TT rank</td>
<td>the full operator will not fit in memory, and you need the inverse stored in the same compressed form</td>
<td>only approximate. The inverse usually needs a higher rank than the operator</td>
</tr>
<tr class="even">
<td><strong>CP-based approximate inverse</strong></td>
<td>—</td>
<td>any order</td>
<td>a rough, cheap approximation is enough and you never need the exact inverse</td>
<td>nothing guarantees that it exists or that it is unique. Treat it as an approximation, not an inverse</td>
</tr>
<tr class="odd">
<td><strong>Kronecker-separable precision</strong></td>
<td>—</td>
<td>any order</td>
<td>you are modelling a covariance over a multi-way array (the tensor-normal or matrix-normal model)</td>
<td><strong>not a tensor inverse at all.</strong> You are assuming that the inverse splits mode by mode. It is in this table because people mistake it for one</td>
</tr>
</tbody>
</table>
<section id="two-rules-that-come-with-the-table" class="level3" data-number="2.1">
<h3 data-number="2.1" class="anchored" data-anchor-id="two-rules-that-come-with-the-table"><span class="header-section-number">2.1</span> Two rules that come with the table</h3>
<p><strong>In real work you solve, you do not invert.</strong> This is the same rule as for matrices. Tensor Krylov methods, such as t-product GMRES, find <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BX%7D"> in <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BA%7D%20*%20%5Cmathcal%7BX%7D%20=%20%5Cmathcal%7BB%7D"> without ever building <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BA%7D%5E%7B-1%7D"> (El Guide et al.&nbsp;2021). This post builds the inverse anyway, so that you can see what it looks like. That is a choice made for teaching, not advice for production code.</p>
<p><strong>Using the wrong product is a mistake of kind, not a mistake of accuracy.</strong> Suppose <img src="https://latex.codecogs.com/png.latex?%5ClVert%20%5Cmathcal%7BA%7D%20%5Ctimes_n%20%5Cmathcal%7BX%7D%20-%20%5Cmathcal%7BB%7D%5CrVert"> comes out small. That tells you nothing about <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BA%7D%20*%20%5Cmathcal%7BX%7D%20=%20%5Cmathcal%7BB%7D">. The two products multiply along different modes. They are different equations that happen to use the same letters. Computing in higher precision will never turn one into the other.</p>
</section>
</section>
<section id="data" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="data"><span class="header-section-number">3</span> Data</h2>
<p>Four problems and four sources. Two are downloaded. One is a set of published material constants. One is simulated, because the real data needs a signed agreement.</p>
<section id="an-off-axis-carbonepoxy-ply" class="level3" data-number="3.1">
<h3 data-number="3.1" class="anchored" data-anchor-id="an-off-axis-carbonepoxy-ply"><span class="header-section-number">3.1</span> An off-axis carbon/epoxy ply</h3>
<ul>
<li><strong>What it is.</strong> Five elastic constants for a unidirectional T300/5208 carbon-fibre/epoxy ply at room temperature: <img src="https://latex.codecogs.com/png.latex?E_1%20=%20181"> GPa along the fibres, <img src="https://latex.codecogs.com/png.latex?E_2%20=%2010.3"> GPa across them, <img src="https://latex.codecogs.com/png.latex?G_%7B12%7D%20=%207.17"> GPa, <img src="https://latex.codecogs.com/png.latex?%5Cnu_%7B12%7D%20=%200.28">, <img src="https://latex.codecogs.com/png.latex?%5Cnu_%7B23%7D%20=%200.35">. These are typical published values for that material, written out in full so you can check the arithmetic below. Nothing here turns on the third significant figure.</li>
<li><strong>Who measures them and why.</strong> Materials labs pull, press and twist small test pieces cut from cured panels. Structural engineers use the results to predict how a part will bend before anyone builds it. The measurement exists to feed a stiffness model.</li>
<li><strong>What this post asks of them.</strong> Build the fourth-order stiffness tensor and rotate the ply <img src="https://latex.codecogs.com/png.latex?30%C2%B0"> off the fibre axis. Then invert it to get compliance, so that a known stress returns the strain it caused.</li>
<li><strong>What being wrong costs.</strong> Compliance turns a load into a deflection. An error here means an aircraft panel or a pressure vessel is sized against the wrong strain. That is a part that breaks, not a plot that looks wrong.</li>
<li><strong>Why a tensor inverse.</strong> A <img src="https://latex.codecogs.com/png.latex?3%5Ctimes3%5Ctimes3%5Ctimes3"> stiffness tensor really is a linear map. It takes a symmetric <img src="https://latex.codecogs.com/png.latex?3%5Ctimes3"> matrix and returns a symmetric <img src="https://latex.codecogs.com/png.latex?3%5Ctimes3"> matrix. This is exactly what the Einstein product describes. It is also the one problem here where flattening works, so it is a fair place to start.</li>
</ul>
</section>
<section id="pavia-university" class="level3" data-number="3.2">
<h3 data-number="3.2" class="anchored" data-anchor-id="pavia-university"><span class="header-section-number">3.2</span> Pavia University</h3>
<ul>
<li><strong>Provenance.</strong> A hyperspectral image of the University of Pavia campus in northern Italy, <img src="https://latex.codecogs.com/png.latex?610%5Ctimes340"> pixels over 103 spectral bands at 1.3 m ground resolution. The ROSIS sensor recorded it from an aircraft. Prof.&nbsp;Paolo Gamba released it for research; he works at the Telecommunications and Remote Sensing Laboratory at the University of Pavia. Graña, Veganzones and Ayerdi at UPV/EHU then put it into the Hyperspectral Remote Sensing Scenes collection, which serves it today.</li>
<li><strong>Collector and motive.</strong> An aircraft flight, not a survey and not a web scrape. The team flew the scene and released it so that hyperspectral classification methods would share one benchmark. Hundreds of remote-sensing papers now use it.</li>
<li><strong>What this post uses.</strong> A <img src="https://latex.codecogs.com/png.latex?128%5Ctimes128%5Ctimes103"> crop. <code>src/fetch_data.py</code> writes it to <code>data/paviaU_crop.npz</code>, which is committed. The official host blocks scripted downloads, so the script takes the whole scene from an open mirror, cuts out the crop, and throws the rest away.</li>
<li><strong>What this post asks of it.</strong> Blur the cube with an operator that smears space <em>and</em> mixes neighbouring bands, then try to get it back two ways.</li>
<li><strong>What being wrong costs.</strong> Hyperspectral analysis identifies a material from the shape of a pixel’s spectrum. Suppose a method sharpens the picture but leaves the bands mixed. It then reports the wrong material, and reports it with confidence. A mineral survey, a crop-disease map or a pollution estimate would rest on a spectrum that belongs to no real material.</li>
<li><strong>Why a tensor inverse.</strong> The blur mixes the bands into each other. You therefore cannot treat each band as a separate problem. The t-product is built for this case: a third mode whose order means something, and slices that mix.</li>
</ul>
</section>
<section id="a-synthetic-four-dimensional-scan" class="level3" data-number="3.3">
<h3 data-number="3.3" class="anchored" data-anchor-id="a-synthetic-four-dimensional-scan"><span class="header-section-number">3.3</span> A synthetic four-dimensional scan</h3>
<ul>
<li><strong>It is synthetic, and this is how it is made.</strong> A <img src="https://latex.codecogs.com/png.latex?16%5Ctimes16%5Ctimes16%5Ctimes120"> array. There are four regressors: a constant, two on-off task blocks with different periods, and a slow linear drift. Each regressor has its own Gaussian blob somewhere in space. The array is the design matrix times those blobs, plus independent Gaussian noise with <img src="https://latex.codecogs.com/png.latex?%5Csigma%20=%200.4">. The random seed is 7.</li>
<li><strong>What it stands in for, and why it is simulated.</strong> A task-fMRI session of the kind in ADNI or the CMU StarPlus set. Those need an application and a signed agreement, so a render cannot download them. There is a better reason too. This section has to recover a map that it already knows, so that it can score the result. No real scan comes with the answer attached.</li>
<li><strong>What being wrong costs.</strong> A task map that puts activity in the wrong voxels becomes a published claim about which part of the brain does a job. The claim would be wrong.</li>
<li><strong>Why a mode-<img src="https://latex.codecogs.com/png.latex?n"> pseudoinverse.</strong> Time is one mode out of four, and the design matrix acts only on that mode. Regress along that mode, treating the three space modes as samples, and the whole solve is <img src="https://latex.codecogs.com/png.latex?120%5Ctimes4">. The alternative is written out below.</li>
</ul>
</section>
<section id="chicago-incident-counts" class="level3" data-number="3.4">
<h3 data-number="3.4" class="anchored" data-anchor-id="chicago-incident-counts"><span class="header-section-number">3.4</span> Chicago incident counts</h3>
<ul>
<li><strong>Provenance.</strong> The City of Chicago open data portal, “Crimes — 2001 to Present” (Socrata resource <code>ijzp-q8t2</code>), a public extract of the police department’s records-management system.</li>
<li><strong>Collector and motive.</strong> Officers of the Chicago Police Department enter each reported incident, and the city publishes the records under its open-data programme. These are <em>reports</em>, not offences. How often people report a crime varies by area and by offence type, and the portal says so.</li>
<li><strong>What this post uses.</strong> All of 2023, for community areas 1–24 and the eight most common offence types. The counts go into a <img src="https://latex.codecogs.com/png.latex?52%20%5Ctimes%2024%20%5Ctimes%208"> array: week by area by offence type. The bins are 52 whole weeks. The one leftover day of the year is thrown away, so that the last bin is not longer than the others. <code>src/fetch_data.py</code> writes the array to <code>data/chicago_counts.npz</code>, which is committed, so a render never calls the API. The cell below prints the incident count.</li>
<li><strong>What this post asks of it.</strong> Estimate the covariance of the weekly array. A forecast needs that covariance before it can give any error bars.</li>
<li><strong>What being wrong costs.</strong> Cities use covariance estimates to plan patrols and staffing. A model that is confident about the wrong areas sends officers to the wrong areas.</li>
<li><strong>Why a structured inverse.</strong> Flattened, one week is a 192-dimensional vector, and there are only 52 weeks. The full covariance therefore cannot be inverted. A precision matrix that splits mode by mode can be fitted from the same data. As that section explains, splitting it is an assumption you are making, not an inverse you are taking.</li>
</ul>
</section>
</section>
<section id="compliance-the-einstein-product" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="compliance-the-einstein-product"><span class="header-section-number">4</span> Compliance: the Einstein product</h2>
<p><strong>Who does this.</strong> A mechanical engineer setting up a finite-element run on an anisotropic laminate.</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div id="fig-mech" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-mech-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div>
<pre class="mermaid mermaid-js" data-label="fig-mech">flowchart LR
  S["&lt;b&gt;σ&lt;/b&gt;&lt;br/&gt;stress, 3 × 3"] --&gt; C["&lt;b&gt;ℂ&lt;/b&gt;&lt;br/&gt;stiffness&lt;br/&gt;3 × 3 × 3 × 3"]
  C --&gt;|"invert under ⊛"| SS["&lt;b&gt;𝕊 = ℂ&lt;sup&gt;+&lt;/sup&gt;&lt;/b&gt;&lt;br/&gt;compliance"]
  SS --&gt; E["&lt;b&gt;ε = 𝕊 ⊛ σ&lt;/b&gt;&lt;br/&gt;strain, 3 × 3"]
</pre>
</div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-mech-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Stress in, strain out. The operator is a fourth-order stiffness tensor, and the inverse is taken under the Einstein product.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Hooke’s law in tensor form is <img src="https://latex.codecogs.com/png.latex?%5Csigma_%7Bij%7D%20=%20%5Cmathbb%7BC%7D_%7Bijkl%7D%5Cvarepsilon_%7Bkl%7D">. Summing over the last two indices of <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D"> against a <img src="https://latex.codecogs.com/png.latex?3%5Ctimes3"> matrix is the Einstein product.</p>
<p>So <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D"> takes a <img src="https://latex.codecogs.com/png.latex?3%5Ctimes3"> matrix and returns a <img src="https://latex.codecogs.com/png.latex?3%5Ctimes3"> matrix. Compliance is the operator that undoes it.</p>
<p>Build the ply, rotate it <img src="https://latex.codecogs.com/png.latex?30%C2%B0"> off the fibre axis, and look at the operator as a <img src="https://latex.codecogs.com/png.latex?9%5Ctimes9"> matrix.</p>
<div id="stiffness" class="cell" data-execution_count="2">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1">C4 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> D.off_axis_stiffness(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">30.0</span>)</span>
<span id="cb1-2">flat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> C4.reshape(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>)</span>
<span id="cb1-3">sv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.svd(flat, compute_uv<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb1-4"></span>
<span id="cb1-5"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"stiffness tensor  </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>C4<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, as an operator </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>flat<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb1-6"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"singular values / largest:"</span>)</span>
<span id="cb1-7"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(np.array2string(sv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> sv.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(), precision<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>, suppress_small<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>))</span>
<span id="cb1-8"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"rank </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>matrix_rank(flat)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> of 9"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>stiffness tensor  (3, 3, 3, 3), as an operator (9, 9)
singular values / largest:
[1.000000e+00 8.611932e-02 7.803630e-02 7.803630e-02 4.151939e-02
 4.151939e-02 4.158847e-18 1.170415e-18 7.788320e-20]
rank 6 of 9</code></pre>
</div>
</div>
<p>Three of the nine singular values are zero, to machine precision. So the stiffness tensor is a <img src="https://latex.codecogs.com/png.latex?9%5Ctimes9"> operator of rank 6, not rank 9. <strong>The Einstein-product inverse does not exist.</strong></p>
<div id="inverse-fails" class="cell" data-execution_count="3">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb3-2">    T.einstein_inv(C4, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb3-3"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> np.linalg.LinAlgError <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> err:</span>
<span id="cb3-4">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"einstein_inv: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>err<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>einstein_inv: Singular matrix</code></pre>
</div>
</div>
<p>This is not a flaw in the material. It follows from the minor symmetries <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D_%7Bijkl%7D%20=%20%5Cmathbb%7BC%7D_%7Bjikl%7D%20=%20%5Cmathbb%7BC%7D_%7Bijlk%7D">. Those symmetries mean the operator ignores the antisymmetric part of its input, and never produces one.</p>
<p>The antisymmetric <img src="https://latex.codecogs.com/png.latex?3%5Ctimes3"> matrices fill three dimensions. Those three are the null space. This makes physical sense: turning a rigid body does not stretch it.</p>
<p>The object that does exist is the <strong>multilinear Moore–Penrose inverse</strong>, which is row 4 of the table rather than row 3. It undoes the map on the six symmetric directions, and returns zero on the other three.</p>
<div id="compliance" class="cell" data-execution_count="4">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1">S4 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> T.einstein_pinv(C4, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb5-2">sigma <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> D.applied_stress()</span>
<span id="cb5-3">eps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> T.einstein(S4, sigma, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb5-4">back <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> T.einstein(C4, eps, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb5-5"></span>
<span id="cb5-6"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"applied stress (MPa)"</span>)</span>
<span id="cb5-7"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(np.array2string(sigma <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e6</span>, precision<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, suppress_small<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>))</span>
<span id="cb5-8"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">recovered strain (x 1e-3)"</span>)</span>
<span id="cb5-9"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(np.array2string(eps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e3</span>, precision<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, suppress_small<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>))</span>
<span id="cb5-10"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">strain is symmetric: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>allclose(eps, eps.T)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb5-11"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"round trip C(eps) vs sigma, relative error: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>norm(back <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> sigma) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>norm(sigma)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>applied stress (MPa)
[[120.  54.   0.]
 [ 54.  42.   0.]
 [  0.   0.   0.]]

recovered strain (x 1e-3)
[[ 1.303  -0.4146  0.    ]
 [-0.4146  0.6906 -0.    ]
 [ 0.     -0.     -0.7285]]

strain is symmetric: True
round trip C(eps) vs sigma, relative error: 2.04e-16</code></pre>
</div>
</div>
<p>The strain comes back symmetric, and the round trip closes to machine precision.</p>
<p>Notice the shear–extension coupling. A ply turned by <img src="https://latex.codecogs.com/png.latex?30%C2%B0"> under mostly normal stress also picks up a negative shear strain. Engineers stack plies in balanced pairs so that these shear strains cancel.</p>
<section id="why-voigt-notation-is-a-flattening-that-works" class="level3" data-number="4.1">
<h3 data-number="4.1" class="anchored" data-anchor-id="why-voigt-notation-is-a-flattening-that-works"><span class="header-section-number">4.1</span> Why Voigt notation is a flattening that works</h3>
<p>Voigt notation stacks the six independent components of a symmetric <img src="https://latex.codecogs.com/png.latex?3%5Ctimes3"> tensor into a vector of length 6. That turns <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D"> into a <img src="https://latex.codecogs.com/png.latex?6%5Ctimes6"> matrix. It is a flattening, it is standard engineering practice, and here it is exactly right.</p>
<p>The rank count above explains why. The three directions that Voigt throws away are exactly the three in the null space. Flattening onto the symmetric directions loses nothing, because there was nothing there to lose.</p>
<div id="voigt" class="cell" data-execution_count="5">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1">C6 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> D.tensor_to_voigt_stiffness(C4)</span>
<span id="cb7-2">S6 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.inv(C6)</span>
<span id="cb7-3">S4_from_voigt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> D.compliance_to_tensor(S6, reuter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb7-4"></span>
<span id="cb7-5"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Voigt stiffness 6x6: rank </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>matrix_rank(C6)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, condition number </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>cond(C6)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb7-6"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"max |S4_from_voigt - S4|: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(S4_from_voigt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> S4)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>Voigt stiffness 6x6: rank 6, condition number 39.9
max |S4_from_voigt - S4|: 1.29e-25</code></pre>
</div>
</div>
<p>The Voigt matrix is well conditioned and full rank, and it matches the multilinear pseudoinverse to the last digit.</p>
<p>Flattening is not the problem. Flattening <em>along the wrong structure</em> is the problem.</p>
</section>
<section id="the-mistake-voigt-still-lets-you-make" class="level3" data-number="4.2">
<h3 data-number="4.2" class="anchored" data-anchor-id="the-mistake-voigt-still-lets-you-make"><span class="header-section-number">4.2</span> The mistake Voigt still lets you make</h3>
<p>Voigt compliance uses <strong>engineering</strong> shear strains, <img src="https://latex.codecogs.com/png.latex?%5Cgamma_%7B12%7D%20=%202%5Cvarepsilon_%7B12%7D">. The tensor uses <img src="https://latex.codecogs.com/png.latex?%5Cvarepsilon_%7B12%7D"> instead. So converting the inverted <img src="https://latex.codecogs.com/png.latex?6%5Ctimes6"> back to a fourth-order tensor needs Reuter’s factors: divide by 2 for each shear index. Skip them and the shear terms come out 2 or 4 times too large.</p>
<div id="reuter" class="cell" data-execution_count="6">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1">bad <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> D.compliance_to_tensor(S6, reuter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb9-2">eps_bad <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> T.einstein(bad, sigma, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb9-3">err_bad <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.norm(T.einstein(C4, eps_bad, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> sigma) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> np.linalg.norm(sigma)</span>
<span id="cb9-4"></span>
<span id="cb9-5"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"strain without Reuter factors (x 1e-3)"</span>)</span>
<span id="cb9-6"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(np.array2string(eps_bad <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e3</span>, precision<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, suppress_small<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>))</span>
<span id="cb9-7"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">shear  ratio to correct: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>eps_bad[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> eps[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:+.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb9-8"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"normal ratio to correct: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>eps_bad[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> eps[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:+.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb9-9"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"round-trip relative error: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>err_bad<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>strain without Reuter factors (x 1e-3)
[[-1.2328  5.3348  0.    ]
 [ 5.3348 -1.0556  0.    ]
 [ 0.      0.      0.7883]]

shear  ratio to correct: -12.87
normal ratio to correct: -0.95
round-trip relative error: 3.16</code></pre>
</div>
</div>
<p>When the ply is turned off-axis, the error is not limited to shear. Turning the ply puts shear–extension terms into the compliance. If their scale is wrong, the error spreads through the whole solve. The normal strains then flip sign, and the round trip is wrong by more than 300%.</p>
<p>On the fibre axis those coupling terms are zero. The same mistake then makes the shear exactly 4 times too large, and leaves everything else correct.</p>
<div id="reuter-on-axis" class="cell" data-execution_count="7">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1">C4_0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> D.off_axis_stiffness(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>)</span>
<span id="cb11-2">eps_0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> T.einstein(T.einstein_pinv(C4_0, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>), sigma, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb11-3">S6_0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.inv(D.tensor_to_voigt_stiffness(C4_0))</span>
<span id="cb11-4">bad_0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> T.einstein(D.compliance_to_tensor(S6_0, reuter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>), sigma, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb11-5"></span>
<span id="cb11-6"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"on-axis shear  ratio: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>bad_0[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> eps_0[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb11-7"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"on-axis normal ratio: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>bad_0[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> eps_0[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>on-axis shear  ratio: 4.000
on-axis normal ratio: 1.000</code></pre>
</div>
</div>
<p>The pseudoinverse cannot make this mistake, because it never leaves the tensor. So there is a trade. Flattening gives you a smaller, better-behaved object. In exchange, you must apply a conversion rule by hand, and apply it correctly.</p>
</section>
</section>
<section id="hyperspectral-restoration-the-t-product" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="hyperspectral-restoration-the-t-product"><span class="header-section-number">5</span> Hyperspectral restoration: the t-product</h2>
<p><strong>Who does this.</strong> A remote-sensing analyst deblurring a data cube before classifying it.</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div id="fig-hsi-flow" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-hsi-flow-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div>
<pre class="mermaid mermaid-js" data-label="fig-hsi-flow">flowchart LR
  X["&lt;b&gt;𝒳&lt;/b&gt;&lt;br/&gt;clean cube&lt;br/&gt;128 × 128 × 103"] --&gt; A["&lt;b&gt;𝒜&lt;/b&gt;&lt;br/&gt;blur operator&lt;br/&gt;smear + band leak"]
  A --&gt; B["&lt;b&gt;ℬ = 𝒜 * 𝒳&lt;/b&gt;&lt;br/&gt;observed"]
  B --&gt; F["band-by-band&lt;br/&gt;invert slice 0 only"]
  B --&gt; TP["t-solve&lt;br/&gt;all 103 Fourier slices"]
  F --&gt; R1["spatially sharp,&lt;br/&gt;spectrally still mixed"]
  TP --&gt; R2["&lt;b&gt;𝒳̂&lt;/b&gt;"]
</pre>
</div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-hsi-flow-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: One t-solve against 103 separate 2D deblurring problems. The flattening keeps only the first frontal slice, and the band mixing lives in the ones it drops.
</figcaption>
</figure>
</div>
</div>
</div>
<p>The blur has two parts. The optics smear each band across space. The sensor also leaks signal between neighbouring wavelengths.</p>
<p>Under the t-product both parts fit into one operator <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BA%7D">. Frontal slice 0 holds the spatial smear, and slices <img src="https://latex.codecogs.com/png.latex?%5Cpm%201"> hold the leak.</p>
<div id="hsi-setup" class="cell" data-execution_count="8">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1">cube, meta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> D.pavia_crop()</span>
<span id="cb13-2">n, bands <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cube.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], cube.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]</span>
<span id="cb13-3">LEAK <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.12</span></span>
<span id="cb13-4">A <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> D.blur_operator(n, bands, width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.4</span>, leak<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>LEAK)</span>
<span id="cb13-5"></span>
<span id="cb13-6">rng <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.random.default_rng(T.SEED)</span>
<span id="cb13-7">observed <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> T.tprod(A, cube)</span>
<span id="cb13-8">observed <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> observed <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> rng.normal(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-3</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> observed.std(), observed.shape)</span>
<span id="cb13-9"></span>
<span id="cb13-10"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"crop </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>cube<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> from the </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(meta[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'full_shape'</span>])<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> scene at origin </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>meta[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'origin'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb13-11"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"operator </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>A<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">: spatial smear in slice 0, </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>LEAK<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.0%}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> band leak in slices 1 and -1"</span>)</span>
<span id="cb13-12"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"operator as a tensor:            </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>A<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>nbytes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e6</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:6.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> MB"</span>)</span>
<span id="cb13-13"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"same operator flattened to a matrix:      </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>(n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> bands) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e9</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:6.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> GB"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>crop (128, 128, 103) from the (610, 340, 103) scene at origin [150, 100]
operator (128, 128, 103): spatial smear in slice 0, 12% band leak in slices 1 and -1
operator as a tensor:              13.5 MB
same operator flattened to a matrix:         1.4 GB</code></pre>
</div>
</div>
<p>Two methods are compared below. <strong>Band-by-band</strong> deblurs each of the 103 bands on its own, which is the flattening. <strong>t-solve</strong> solves the whole cube at once in the t-product.</p>
<p>Undoing a blur is unstable. A small amount of noise in the input becomes a large amount of noise in the answer.</p>
<p>Both methods therefore get the same Tikhonov damping, and each one picks the <img src="https://latex.codecogs.com/png.latex?%5Clambda"> that suits it best. This is generous to the flattening. It also leaves the band mixing as the only real difference between the two methods.</p>
<div id="hsi-solve" class="cell" data-execution_count="9">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb15-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> relative(x):</span>
<span id="cb15-2">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> np.linalg.norm(x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> cube) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> np.linalg.norm(cube)</span>
<span id="cb15-3"></span>
<span id="cb15-4"></span>
<span id="cb15-5">lambdas <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.logspace(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">15</span>)</span>
<span id="cb15-6">results <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {}</span>
<span id="cb15-7"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name, solver <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (</span>
<span id="cb15-8">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"band-by-band"</span>, T.band_by_band_solve),</span>
<span id="cb15-9">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t-solve"</span>, T.treg_solve),</span>
<span id="cb15-10">):</span>
<span id="cb15-11">    err, lam <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>((relative(solver(A, observed, l)), l) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> l <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> lambdas)</span>
<span id="cb15-12">    start <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> time.perf_counter()</span>
<span id="cb15-13">    recovered <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> solver(A, observed, lam)</span>
<span id="cb15-14">    results[name] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb15-15">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cube"</span>: recovered,</span>
<span id="cb15-16">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lam"</span>: lam,</span>
<span id="cb15-17">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rel"</span>: err,</span>
<span id="cb15-18">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sam"</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(D.spectral_angle(cube, recovered).mean()),</span>
<span id="cb15-19">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"secs"</span>: time.perf_counter() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> start,</span>
<span id="cb15-20">    }</span>
<span id="cb15-21"></span>
<span id="cb15-22">header <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">''</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:&lt;13}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'lambda'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:&gt;8}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'rel error'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:&gt;10}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'mean SAM'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:&gt;9}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'seconds'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:&gt;8}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb15-23"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(header)</span>
<span id="cb15-24"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(header))</span>
<span id="cb15-25"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'observed'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:&lt;13}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">''</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:&gt;8}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>relative(observed)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:10.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> "</span></span>
<span id="cb15-26">      <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>D<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>spectral_angle(cube, observed)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>mean()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:8.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">° </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">''</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:&gt;8}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb15-27"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name, r <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> results.items():</span>
<span id="cb15-28">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:&lt;13}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>r[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'lam'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:8.0e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>r[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'rel'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:10.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>r[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'sam'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:8.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">° </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>r[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'secs'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:8.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>                lambda  rel error  mean SAM  seconds
----------------------------------------------------
observed                   0.2444     3.26°         
band-by-band     3e-01     0.1247     3.12°     0.06
t-solve          1e-04     0.0376     1.79°     0.14</code></pre>
</div>
</div>
<p>Band-by-band deblurring halves the relative error and then stops improving. Its best <img src="https://latex.codecogs.com/png.latex?%5Clambda"> sits at the smoothest end of the grid. A method chooses heavy smoothing when it cannot fix the real problem. Here the real problem is the band mixing, which this method threw away.</p>
<p>The t-solve gets about three times closer. The spectral angle also drops by 40%. Spectral angle is the measure that decides which material a pixel contains, so that is the number that matters.</p>
<div id="cell-fig-hsi-restore" class="cell" data-fig-height="6.4" data-fig-width="11" data-execution_count="10">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb17-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> rgb(c):</span>
<span id="cb17-2">    im <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.stack([c[:, :, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">46</span>], c[:, :, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">27</span>], c[:, :, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>]], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb17-3">    lo, hi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.percentile(im, [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">99</span>])</span>
<span id="cb17-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> np.clip((im <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> lo) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (hi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> lo), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb17-5"></span>
<span id="cb17-6"></span>
<span id="cb17-7">recovered <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {k: results[k][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cube"</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> k <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> results}</span>
<span id="cb17-8">sam <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb17-9">    name: D.spectral_angle(cube, data).reshape(n, n)</span>
<span id="cb17-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name, data <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> [(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"observed"</span>, observed), <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>recovered.items()]</span>
<span id="cb17-11">}</span>
<span id="cb17-12"></span>
<span id="cb17-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Mark a pixel where the two methods actually disagree, rather than an</span></span>
<span id="cb17-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># arbitrary one: the 97th percentile of the band-by-band minus t-solve gap,</span></span>
<span id="cb17-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># taken away from the border so the marker is not clipped.</span></span>
<span id="cb17-16">gap <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sam[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"band-by-band"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> sam[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t-solve"</span>]</span>
<span id="cb17-17">interior <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros_like(gap, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>)</span>
<span id="cb17-18">interior[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>:<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>:<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span>
<span id="cb17-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># argsort puts the masked-out border pixels first, so index from the top.</span></span>
<span id="cb17-20">ranked <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.argsort(np.where(interior, gap, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>np.inf).ravel())</span>
<span id="cb17-21">px, py <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.unravel_index(ranked[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.03</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> interior.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>())], (n, n))</span>
<span id="cb17-22"></span>
<span id="cb17-23">fig, axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">11</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6.0</span>), height_ratios<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.35</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span>
<span id="cb17-24">fig.subplots_adjust(hspace<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.28</span>)</span>
<span id="cb17-25"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> ax, (name, data) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(</span>
<span id="cb17-26">    axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], [(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"clean"</span>, cube), (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"observed"</span>, observed), <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>recovered.items()]</span>
<span id="cb17-27">):</span>
<span id="cb17-28">    ax.imshow(rgb(data))</span>
<span id="cb17-29">    title <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> name <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> name <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"clean"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">relative error </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>relative(data)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb17-30">    ax.set_title(title)</span>
<span id="cb17-31">    ax.plot(py, px, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"o"</span>, mfc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"none"</span>, mec<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>GOLD, mew<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.8</span>, ms<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">13</span>)</span>
<span id="cb17-32">    ax.set_xticks([])</span>
<span id="cb17-33">    ax.set_yticks([])</span>
<span id="cb17-34"></span>
<span id="cb17-35">spec <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb17-36">spec.plot(cube[px, py, :], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>INK, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"clean"</span>, zorder<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb17-37">spec.plot(observed[px, py, :], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>GOLD, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.2</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"observed"</span>)</span>
<span id="cb17-38">spec.plot(recovered[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"band-by-band"</span>][px, py, :], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>CORAL, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.3</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"band-by-band"</span>)</span>
<span id="cb17-39">spec.plot(recovered[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t-solve"</span>][px, py, :], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>TEAL, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.3</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t-solve"</span>)</span>
<span id="cb17-40">spec.set_title(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"spectrum at the marked pixel (</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>px<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>py<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">)"</span>)</span>
<span id="cb17-41">spec.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"band"</span>)</span>
<span id="cb17-42">spec.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"reflectance"</span>)</span>
<span id="cb17-43">spec.legend(frameon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>)</span>
<span id="cb17-44"></span>
<span id="cb17-45">vmax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(np.percentile(sam[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"observed"</span>], <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">99</span>))</span>
<span id="cb17-46"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> ax, name <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:], [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"observed"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"band-by-band"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t-solve"</span>]):</span>
<span id="cb17-47">    im <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ax.imshow(sam[name], cmap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"inferno"</span>, vmin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, vmax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>vmax)</span>
<span id="cb17-48">    ax.set_title(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">mean SAM </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>sam[name]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>mean()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">°"</span>)</span>
<span id="cb17-49">    ax.set_xticks([])</span>
<span id="cb17-50">    ax.set_yticks([])</span>
<span id="cb17-51">fig.colorbar(im, ax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:], shrink<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.85</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"spectral angle (deg)"</span>)</span>
<span id="cb17-52">plt.show()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-hsi-restore" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-hsi-restore-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/tensor-inverses-in-practice/index_files/figure-html/fig-hsi-restore-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-hsi-restore-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: Pavia University, a 128×128 crop. Top: an RGB view built from bands 46, 27 and 10. Bottom left: the spectrum at one marked pixel. Bottom right: the spectral angle at every pixel against the clean cube, all three on one scale. Band-by-band deblurring sharpens the picture but leaves the spectral-angle errors almost as large as in the blurred input. The t-solve reduces them.
</figcaption>
</figure>
</div>
</div>
</div>
<p>The spectra show this most clearly. The band-by-band curve keeps the shape of the clean curve, but it does not sit on top of it. It runs above the truth over some bands and below over others. Every band still carries 12% of each neighbour. The t-solve curve lies on the truth.</p>
<p>The spectral-angle maps show the same result across the whole scene. In the band-by-band map the errors are almost as large as in the blurred input. The largest errors sit on roofs and edges. Neighbouring bands differ most there, so the leak does most damage there. In the t-solve map the errors drop everywhere.</p>
<p>Look at the memory line above as well. The same operator takes 13.5 MB as a tensor and 1.4 GB after flattening into a matrix. So the t-product does more than give a better answer. On the full scene it is the only one of the two that fits in memory.</p>
</section>
<section id="the-t-inverse-stage-by-stage" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="the-t-inverse-stage-by-stage"><span class="header-section-number">6</span> The t-inverse, stage by stage</h2>
<p>The t-inverse takes four steps, and each step can be drawn. The widget below goes through them on a small <img src="https://latex.codecogs.com/png.latex?8%5Ctimes8%5Ctimes6"> tensor, built from the same random seed as the rest of the post.</p>
<div class="widget-container">
<div class="widget-header">
<p><span class="widget-title">The t-inverse in four stages</span> <span class="widget-badge">runs in the browser</span></p>
</div>
<div id="ti-widget">

</div>
<div class="widget-note">
Stage 2 is the important one. After the FFT along mode 3 the six slices become independent of each other, so one tensor inverse turns into six separate matrix inverses. Next, drag <b>Conditioning</b> to the right. Slice 2 and its conjugate partner slice 4 become singular together. The largest entry of the inverse climbs past 1e11, and the solve error rises to 2.6e6. One bad slice ruins the whole tensor. Now switch the solver to <b>pinv</b>. The pseudoinverse drops the bad direction. The identity check then fails, because A·A<sup>+</sup> sends that direction to zero instead of leaving it alone — but the solve error falls back to 0.09. That is the trade, and the two numbers disagree for a reason.
</div>
</div>
<p>The numbers in that note come from the same function that produced the widget’s data, so you can check them here:</p>
<div id="tinv-stages" class="cell" data-execution_count="11">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb18-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> level <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>):</span>
<span id="cb18-2">    row <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb18-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> solver <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"inv"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pinv"</span>):</span>
<span id="cb18-4">        s <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> T.widget_state(level, solver)</span>
<span id="cb18-5">        row.append((solver, s[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"residual"</span>], s[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"max_abs"</span>], s[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"solve_error"</span>]))</span>
<span id="cb18-6">    kappa <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> T.widget_state(level, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"inv"</span>)[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"conds"</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]</span>
<span id="cb18-7">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"conditioning </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>level<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">   kappa(slice 2) = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>kappa<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb18-8">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> solver, res, mx, solve <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> row:</span>
<span id="cb18-9">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"    </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>solver<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:&gt;4}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  identity residual </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>res<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:8.2e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">   max|X| </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>mx<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:8.2e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">   solve error </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>solve<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:8.2e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>conditioning 0.0   kappa(slice 2) = 6.93e+00
     inv  identity residual 5.26e-16   max|X| 5.67e-01   solve error 1.21e-05
    pinv  identity residual 1.33e-15   max|X| 5.67e-01   solve error 1.21e-05
conditioning 0.5   kappa(slice 2) = 6.93e+06
     inv  identity residual 2.01e-10   max|X| 1.67e+05   solve error 2.57e+00
    pinv  identity residual 2.99e-10   max|X| 1.67e+05   solve error 2.57e+00
conditioning 1.0   kappa(slice 2) = 6.92e+12
     inv  identity residual 1.97e-04   max|X| 1.68e+11   solve error 2.57e+06
    pinv  identity residual 2.04e-01   max|X| 5.51e-01   solve error 9.47e-02</code></pre>
</div>
</div>
<p>Two points.</p>
<p>First, the identity residual and the solve error disagree once the pseudoinverse starts dropping directions. The solve error is the one to read, because it measures the answer you actually wanted.</p>
<p>Second, slices 2 and 4 are a conjugate pair. In a tensor with real entries, one of them cannot become ill-conditioned on its own.</p>
</section>
<section id="task-maps-the-mode-n-pseudoinverse" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="task-maps-the-mode-n-pseudoinverse"><span class="header-section-number">7</span> Task maps: the mode-<img src="https://latex.codecogs.com/png.latex?n"> pseudoinverse</h2>
<p><strong>Who does this.</strong> A computational neuroscientist mapping a four-dimensional scan onto a task design.</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div id="fig-scan-flow" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-scan-flow-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div>
<pre class="mermaid mermaid-js" data-label="fig-scan-flow">flowchart LR
  Y["&lt;b&gt;𝒴&lt;/b&gt;&lt;br/&gt;scan&lt;br/&gt;16 × 16 × 16 × 120"] --&gt; U["flatten on mode 4&lt;br/&gt;120 × 4096"]
  X["&lt;b&gt;X&lt;/b&gt;&lt;br/&gt;design&lt;br/&gt;120 × 4"] --&gt; P["&lt;b&gt;X&lt;sup&gt;†&lt;/sup&gt;&lt;/b&gt;&lt;br/&gt;mode-n pseudoinverse"]
  U --&gt; M["&lt;b&gt;B = X&lt;sup&gt;†&lt;/sup&gt; 𝒴&lt;sub&gt;(4)&lt;/sub&gt;&lt;/b&gt;"]
  P --&gt; M
  M --&gt; R["coefficient maps&lt;br/&gt;4 × 4096"]
</pre>
</div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-scan-flow-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;4: Regression along one mode. Time is mode 4, and the three space modes become columns, so the solve is the size of the design rather than the size of the scan.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Readers misread this row of the table more often than any other. The mode-<img src="https://latex.codecogs.com/png.latex?n"> pseudoinverse inverts the <em>design matrix, on one mode</em>. It does not invert the scan. In fact no tensor is inverted here at all.</p>
<div id="scan" class="cell" data-execution_count="12">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb20" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb20-1">scan, design, beta_true <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> D.synthetic_scan()</span>
<span id="cb20-2">start <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> time.perf_counter()</span>
<span id="cb20-3">beta_hat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> T.mode_pinv_solve(scan, design, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb20-4">mode_secs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> time.perf_counter() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> start</span>
<span id="cb20-5">truth <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> beta_true.reshape(design.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb20-6"></span>
<span id="cb20-7"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"scan </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>scan<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, design </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>design<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, solve took </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>mode_secs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e3</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> ms"</span>)</span>
<span id="cb20-8">names <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"intercept"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"task A"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"task B"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"drift"</span>]</span>
<span id="cb20-9"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> p, name <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(names[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:], start<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb20-10">    corr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.corrcoef(beta_hat[p], truth[p])[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb20-11">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"  </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:&lt;10}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> correlation with truth </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>corr<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb20-12"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"  overall relative error </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>norm(beta_hat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> truth) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>norm(truth)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>scan (16, 16, 16, 120), design (120, 4), solve took 0.3 ms
  task A     correlation with truth 0.960
  task B     correlation with truth 0.903
  drift      correlation with truth 0.657
  overall relative error 0.230</code></pre>
</div>
</div>
<p>The two task maps come back with correlation 0.96 and 0.90 against the truth. The drift map is weaker, which is what you would expect. Over 120 frames, a slow ramp looks much like noise.</p>
<p>Now count the parameters. The mode-<img src="https://latex.codecogs.com/png.latex?n"> method solves one <img src="https://latex.codecogs.com/png.latex?120%5Ctimes4"> system, then reuses that solution across all 4,096 voxels.</p>
<div id="scan-counts" class="cell" data-execution_count="13">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb22" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb22-1">voxels, frames, params <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, design.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], design.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb22-2">vec_rows, vec_cols <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> voxels <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> frames, voxels <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> params</span>
<span id="cb22-3"></span>
<span id="cb22-4"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"mode-n:  solve is </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>frames<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> x </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>params<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, estimates </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>params <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> voxels<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:,}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> coefficients"</span>)</span>
<span id="cb22-5"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"flattened GLM design matrix: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>vec_rows<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:,}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> x </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>vec_cols<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:,}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb22-6"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"  = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>vec_rows <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> vec_cols<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> entries = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>vec_rows <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> vec_cols <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e9</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> GB"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>mode-n:  solve is 120 x 4, estimates 16,384 coefficients
flattened GLM design matrix: 491,520 x 16,384
  = 8.05e+09 entries = 64.4 GB</code></pre>
</div>
</div>
<p>The estimates are identical, and the design matrix is 64 GB. That matrix is the same <img src="https://latex.codecogs.com/png.latex?120%5Ctimes4"> block repeated 4,096 times along a diagonal. The mode-<img src="https://latex.codecogs.com/png.latex?n"> pseudoinverse is what you get once you notice the repetition and stop writing the copies down.</p>
<div id="cell-fig-scan" class="cell" data-fig-height="4.4" data-fig-width="10" data-execution_count="14">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb24" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb24-1">maps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> beta_hat.reshape(beta_true.shape)</span>
<span id="cb24-2">fig, axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.4</span>))</span>
<span id="cb24-3">slices <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)]</span>
<span id="cb24-4"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> col, (p, z) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(slices):</span>
<span id="cb24-5">    vmax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(beta_true[p]).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>())</span>
<span id="cb24-6">    axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, col].imshow(beta_true[p][:, :, z], cmap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"magma"</span>, vmin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, vmax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>vmax)</span>
<span id="cb24-7">    axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, col].imshow(maps[p][:, :, z], cmap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"magma"</span>, vmin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, vmax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>vmax)</span>
<span id="cb24-8">    corr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.corrcoef(beta_hat[p], truth[p])[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb24-9">    axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, col].set_title(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>names[p]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> — truth"</span>)</span>
<span id="cb24-10">    axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, col].set_title(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"recovered, r = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>corr<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb24-11"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> ax <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> axes.ravel():</span>
<span id="cb24-12">    ax.set_xticks([])</span>
<span id="cb24-13">    ax.set_yticks([])</span>
<span id="cb24-14">fig.tight_layout()</span>
<span id="cb24-15">plt.show()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-scan" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-scan-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/tensor-inverses-in-practice/index_files/figure-html/fig-scan-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-scan-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;5: Recovered coefficient maps against the truth, one slice through each blob. The two task maps come back cleanly. The drift map does not, because its regressor is a slow ramp that noise can imitate.
</figcaption>
</figure>
</div>
</div>
</div>
</section>
<section id="urban-demand-a-separable-precision" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="urban-demand-a-separable-precision"><span class="header-section-number">8</span> Urban demand: a separable precision</h2>
<p><strong>Who does this.</strong> An urban data scientist putting an interval on a forecast over a week <img src="https://latex.codecogs.com/png.latex?%5Ctimes"> area <img src="https://latex.codecogs.com/png.latex?%5Ctimes"> offence-type array.</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div id="fig-demand-flow" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-demand-flow-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div>
<pre class="mermaid mermaid-js" data-label="fig-demand-flow">flowchart LR
  Y["&lt;b&gt;𝒴&lt;/b&gt;&lt;br/&gt;52 × 24 × 8&lt;br/&gt;weekly counts"] --&gt; F["flatten to 192-vectors"]
  Y --&gt; S["keep the modes"]
  F --&gt; DC["dense Σ&lt;br/&gt;192 × 192&lt;br/&gt;rank 39 of 192"]
  S --&gt; K["Σ&lt;sub&gt;area&lt;/sub&gt; ⊗ Σ&lt;sub&gt;type&lt;/sub&gt;&lt;br/&gt;flip-flop MLE"]
  DC --&gt; R1["singular — needs a ridge"]
  K --&gt; R2["336 free parameters"]
</pre>
</div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-demand-flow-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;6: One dense covariance over the flattened array, or one covariance per mode. Forty weeks are enough to fit the second. They are not enough to fit the first.
</figcaption>
</figure>
</div>
</div>
</div>
<p>This is the one row of the table that is not an algebraic tensor inverse. Assuming that the covariance splits as <img src="https://latex.codecogs.com/png.latex?%5CSigma_%7B%5Ctext%7Barea%7D%7D%20%5Cotimes%20%5CSigma_%7B%5Ctext%7Btype%7D%7D"> is a claim you make about the world. The array does not tell you it is true.</p>
<p>It appears here for two reasons. People often call it “the tensor inverse” of a covariance, and it is not one. And on this data it does not pay off.</p>
<div id="demand-setup" class="cell" data-execution_count="15">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb25" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb25-1">counts, cmeta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> D.chicago_counts()</span>
<span id="cb25-2">Y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.sqrt(counts.astype(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>))  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># variance-stabilise Poisson-like counts</span></span>
<span id="cb25-3">train, test <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Y[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">40</span>], Y[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">40</span>:]</span>
<span id="cb25-4">centre <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> train.mean(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb25-5">Ztr, Zte <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> train <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> centre, test <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> centre</span>
<span id="cb25-6">dim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Ztr[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].size</span>
<span id="cb25-7"></span>
<span id="cb25-8"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>cmeta[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'n_incidents'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:,}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> incidents, </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>cmeta[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'year'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, community areas 1-</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(cmeta[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'areas'</span>])<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb25-9"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"array </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>counts<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> -&gt; </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(train)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> training weeks, </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(test)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> held out"</span>)</span>
<span id="cb25-10"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"one week flattened is a </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>dim<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">-vector; the dense sample covariance has rank "</span></span>
<span id="cb25-11">      <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>matrix_rank(Ztr.reshape(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(Ztr), <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>).T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> Ztr.reshape(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(Ztr), <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> of </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>dim<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>70,182 incidents, 2023, community areas 1-24
array (52, 24, 8) -&gt; 40 training weeks, 12 held out
one week flattened is a 192-vector; the dense sample covariance has rank 39 of 192</code></pre>
</div>
</div>
<p>Forty readings of a 192-dimensional vector cannot give a full-rank covariance. This is not bad luck with a small sample. It is arithmetic: 40 numbers cannot pin down 18,528.</p>
<p>The separable model fits one covariance per mode, using Dutilleul’s flip-flop algorithm. Whiten every other mode, take the sample covariance of the mode you are updating, then repeat until it settles.</p>
<div id="demand-fit" class="cell" data-execution_count="16">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb27" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb27-1">covs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> D.flip_flop(Ztr, iters<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>)</span>
<span id="cb27-2">logdet <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> D.separable_logdet(covs)</span>
<span id="cb27-3">quad <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> D.separable_quadform(Zte, covs)</span>
<span id="cb27-4">sep_ll <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(np.mean(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (dim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.log(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.pi) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> logdet <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> quad)))</span>
<span id="cb27-5">sep_params <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(c.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (c.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> c <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> covs)</span>
<span id="cb27-6"></span>
<span id="cb27-7">Xtr, Xte <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Ztr.reshape(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(Ztr), <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), Zte.reshape(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(Zte), <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb27-8">S <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Xtr.T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> Xtr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(Xtr)</span>
<span id="cb27-9">best_ll, best_ridge <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>np.inf, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb27-10"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> r <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> np.logspace(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">25</span>):</span>
<span id="cb27-11">    Sr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> S <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.trace(S) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> dim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.eye(dim)</span>
<span id="cb27-12">    _, ld <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.slogdet(Sr)</span>
<span id="cb27-13">    q <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.einsum(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"nd,de,ne-&gt;n"</span>, Xte, np.linalg.inv(Sr), Xte)</span>
<span id="cb27-14">    ll <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(np.mean(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (dim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.log(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.pi) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> ld <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> q)))</span>
<span id="cb27-15">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> ll <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> best_ll:</span>
<span id="cb27-16">        best_ll, best_ridge <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ll, r</span>
<span id="cb27-17"></span>
<span id="cb27-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Where does the gain come from? Keep the mode variances, drop the mode</span></span>
<span id="cb27-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># correlations; then drop the variances too.</span></span>
<span id="cb27-20"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> sep_loglik(cs):</span>
<span id="cb27-21">    q <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> D.separable_quadform(Zte, cs)</span>
<span id="cb27-22">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(np.mean(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (dim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.log(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.pi) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> D.separable_logdet(cs) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> q)))</span>
<span id="cb27-23"></span>
<span id="cb27-24"></span>
<span id="cb27-25">diag_ll <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sep_loglik([np.diag(np.diag(c)) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> c <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> covs])</span>
<span id="cb27-26">iso <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [np.eye(d) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> d <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> Ztr.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:]]</span>
<span id="cb27-27">iso[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.eye(Ztr.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (Ztr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>).mean()</span>
<span id="cb27-28">iso_ll <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sep_loglik(iso)</span>
<span id="cb27-29"></span>
<span id="cb27-30"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'model'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:&lt;32}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'held-out log-lik'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:&gt;17}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'free params'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:&gt;12}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb27-31"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">63</span>)</span>
<span id="cb27-32"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'separable, area x type'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:&lt;32}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>sep_ll<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:17.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>sep_params<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:12,}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb27-33"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'separable, variances only'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:&lt;32}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>diag_ll<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:17.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> "</span></span>
<span id="cb27-34">      <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(c.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> c <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> covs)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:12,}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb27-35"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'one variance for everything'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:&lt;32}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>iso_ll<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:17.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:12,}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb27-36"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'dense 192x192 + best ridge'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:&lt;32}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>best_ll<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:17.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>dim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (dim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:12,}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb27-37"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">the dense model's ridge (</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>best_ridge<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2g}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">) was chosen on the held-out weeks themselves"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>model                             held-out log-lik  free params
---------------------------------------------------------------
separable, area x type                      -192.7          336
separable, variances only                   -191.2           32
one variance for everything                 -192.7            1
dense 192x192 + best ridge                  -202.2       18,528

the dense model's ridge (1) was chosen on the held-out weeks themselves</code></pre>
</div>
</div>
<p>The separable model beats the ridged dense one, and it uses 55 times fewer parameters. The dense model was even allowed to choose its ridge using the test weeks, which no honest pipeline would permit.</p>
<p>But the margin is small: about nine nats per week. A nat is a unit of log-likelihood, so nine nats is a modest gain, not a large one.</p>
<p>Now look at the third row. <strong>One single variance for the whole array scores as well as the separable model.</strong> On this data, the mode structure is not worth the extra work.</p>
<p>The fitted correlations reach only about 0.3 between community areas and about 0.1 between offence types. Once the weekly means are removed, very little is left for a covariance to explain.</p>
<p>One modelling choice drives that result, and it should be stated. The numbers above subtract a separate weekly mean for each area and each offence type.</p>
<p>Subtract a single overall mean instead. The area and type effects then stay inside the covariance, the fitted correlations climb past 0.85, and the mode structure starts to look impressive. But the ridged dense model then wins instead. The covariance is now describing a mean, and the Kronecker assumption is the wrong shape for a mean.</p>
<p>Neither way of centring makes this array a good example of separable covariance.</p>
<p>So this row of the table delivers less than it usually promises. As a statistical argument it is weak here. What remains is that the structured model can be fitted at all, and at full size that alone decides the question.</p>
<div id="demand-counts" class="cell" data-execution_count="17">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb29" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb29-1">full <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> counts.size</span>
<span id="cb29-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"full array flattened: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>full<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:,}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">-vector"</span>)</span>
<span id="cb29-3"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"  dense covariance:   </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>full <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (full <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:,}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> free parameters, </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>full <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> full <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e9</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> GB"</span>)</span>
<span id="cb29-4"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"  separable, 3 modes: "</span></span>
<span id="cb29-5">      <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> d <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> counts.shape)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:,}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> free parameters, "</span></span>
<span id="cb29-6">      <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> d <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> d <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> counts.shape) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e3</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> kB"</span>)</span>
<span id="cb29-7"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"  and you have exactly 1 observation of that </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>full<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:,}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">-vector"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>full array flattened: 9,984-vector
  dense covariance:   49,845,120 free parameters, 0.8 GB
  separable, 3 modes: 1,714 free parameters, 26.8 kB
  and you have exactly 1 observation of that 9,984-vector</code></pre>
</div>
</div>
<div id="cell-fig-demand" class="cell" data-fig-height="3.9" data-fig-width="12.5" data-execution_count="18">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb31" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb31-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> to_corr(cov):</span>
<span id="cb31-2">    d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.sqrt(np.diag(cov))</span>
<span id="cb31-3">    R <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cov <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> np.outer(d, d)</span>
<span id="cb31-4">    np.fill_diagonal(R, np.nan)</span>
<span id="cb31-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> R</span>
<span id="cb31-6"></span>
<span id="cb31-7"></span>
<span id="cb31-8">labels <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [t.title().replace(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">" "</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> t <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> cmeta[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"types"</span>]]</span>
<span id="cb31-9">fig, axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12.5</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.9</span>), width_ratios<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.15</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.05</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>])</span>
<span id="cb31-10">cmap <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.get_cmap(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"RdBu_r"</span>).copy()</span>
<span id="cb31-11">cmap.set_bad(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#E9EBEF"</span>)</span>
<span id="cb31-12"></span>
<span id="cb31-13">im <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].imshow(to_corr(covs[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]), cmap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>cmap, vmin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3</span>, vmax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3</span>)</span>
<span id="cb31-14">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"community area correlation"</span>)</span>
<span id="cb31-15">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].set_xticks(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">24</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>), <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">25</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>))</span>
<span id="cb31-16">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].set_yticks(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">24</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>), <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">25</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>))</span>
<span id="cb31-17"></span>
<span id="cb31-18">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].imshow(to_corr(covs[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]), cmap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>cmap, vmin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3</span>, vmax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3</span>)</span>
<span id="cb31-19">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"offence-type correlation"</span>)</span>
<span id="cb31-20">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].set_xticks(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>), labels, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>, rotation<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">90</span>)</span>
<span id="cb31-21">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].set_yticks(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>), labels, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>)</span>
<span id="cb31-22">cbar <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fig.colorbar(im, ax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>axes[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>], shrink<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.82</span>, pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.16</span>,</span>
<span id="cb31-23">                    label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"correlation (diagonal masked)"</span>)</span>
<span id="cb31-24">cbar.ax.yaxis.set_label_position(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"left"</span>)</span>
<span id="cb31-25"></span>
<span id="cb31-26">sd_area <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.sqrt(np.diag(covs[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]))</span>
<span id="cb31-27">sd_type <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.sqrt(np.diag(covs[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]))</span>
<span id="cb31-28">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>].bar(np.arange(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">25</span>), sd_area <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> sd_area.mean(), color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>PURPLE, width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.75</span>)</span>
<span id="cb31-29">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>].set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"scale per community area"</span>)</span>
<span id="cb31-30">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>].set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"community area"</span>)</span>
<span id="cb31-31">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>].set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"std dev / mean"</span>)</span>
<span id="cb31-32">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>].axhline(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>INK, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>, ls<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--"</span>)</span>
<span id="cb31-33">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>].bar(np.arange(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>), sd_type <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> sd_type.mean(), color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>TEAL, width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span>)</span>
<span id="cb31-34">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>].set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"scale per offence type"</span>)</span>
<span id="cb31-35">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>].set_xticks(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>), labels, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>, rotation<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">90</span>)</span>
<span id="cb31-36">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>].axhline(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>INK, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>, ls<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--"</span>)</span>
<span id="cb31-37">plt.show()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-demand" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-demand-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/tensor-inverses-in-practice/index_files/figure-html/fig-demand-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-demand-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;7: What the separable model actually found, which is not much. Left two panels: the fitted mode correlations with the diagonal hidden, on a ±0.3 scale. Right two: the scale of each area and each offence type, all within about 20% of one another. The structure is real but too weak to pay for itself, which is why one variance for the whole array scores as well.
</figcaption>
</figure>
</div>
</div>
</div>
</section>
<section id="why-not-just-flatten" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="why-not-just-flatten"><span class="header-section-number">9</span> Why not just flatten</h2>
<p>Every number below was computed by a cell above.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 16%">
<col style="width: 16%">
<col style="width: 16%">
<col style="width: 16%">
<col style="width: 16%">
<col style="width: 16%">
</colgroup>
<thead>
<tr class="header">
<th>Problem</th>
<th>Product</th>
<th>Inverse used</th>
<th>Operator, as a tensor</th>
<th>Operator, flattened</th>
<th>What flattening costs</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Compliance</td>
<td>Einstein <img src="https://latex.codecogs.com/png.latex?%5Ccircledast"></td>
<td>multilinear Moore–Penrose</td>
<td>81 entries</td>
<td><img src="https://latex.codecogs.com/png.latex?9%5Ctimes9">, rank 6</td>
<td><strong>Nothing.</strong> Voigt drops exactly the null space. But converting back needs Reuter’s factors, and skipping them gives a 316% error off-axis</td>
</tr>
<tr class="even">
<td>Hyperspectral</td>
<td>t-product <img src="https://latex.codecogs.com/png.latex?*"></td>
<td>regularised t-solve</td>
<td>13.5 MB</td>
<td>1.4 GB block-circulant</td>
<td>relative error 0.125 against 0.038; spectral angle 3.12° against 1.79°</td>
</tr>
<tr class="odd">
<td>Task maps</td>
<td>mode-<img src="https://latex.codecogs.com/png.latex?n"> <img src="https://latex.codecogs.com/png.latex?%5Ctimes_n"></td>
<td>mode-<img src="https://latex.codecogs.com/png.latex?n"> pseudoinverse</td>
<td><img src="https://latex.codecogs.com/png.latex?120%5Ctimes4"> solve</td>
<td><img src="https://latex.codecogs.com/png.latex?491%7B,%7D520%20%5Ctimes%2016%7B,%7D384"> design, 64 GB</td>
<td>nothing, statistically. The flattening is the same estimator written out 4,096 times</td>
</tr>
<tr class="even">
<td>Urban demand</td>
<td>— (separable model)</td>
<td>one precision per mode</td>
<td>336 parameters</td>
<td>18,528 parameters, rank 39 of 192</td>
<td>at this size, <strong>almost nothing.</strong> 9 nats of held-out log-likelihood, and one single variance matches that. On the full three-mode array, 0.8 GB and 49.8M parameters from one reading</td>
</tr>
</tbody>
</table>
<p>Two of these four rows say that flattening is fine. A third says it is fine at this size and impossible at full size. Only one row is a clear win for the tensor.</p>
<p>So the question is never “tensor or matrix”. The question is whether your chosen flattening keeps the structure that the operator actually has.</p>
<ul>
<li><strong>Compliance.</strong> The flattening loses nothing, because the directions it throws away are the ones the operator sends to zero. You risk a conversion error, not a loss of information.</li>
<li><strong>Task maps.</strong> The flattened GLM gives identical estimates. It wastes memory, but it is not wrong.</li>
<li><strong>Hyperspectral.</strong> The flattening drops the cross-band slices of the operator, so it solves a different problem. The error it cannot get below is the band mixing it threw away.</li>
<li><strong>Urban demand.</strong> The flattening asks for more parameters than the data can support. At three modes it asks for 0.8 GB of them from a single reading. But on this array the structure buys a model you can fit, and almost no accuracy. Check a structural assumption against the data. Do not adopt one just because the array has three modes.</li>
</ul>
</section>
<section id="open-problems" class="level2" data-number="10">
<h2 data-number="10" class="anchored" data-anchor-id="open-problems"><span class="header-section-number">10</span> Open problems</h2>
<p>Matrix inverses are settled textbook material. Tensor inverses are not.</p>
<ul>
<li><strong>The list is still growing.</strong> Over roughly the last decade, researchers have carried the classical family of generalized inverses across to tensors under the Einstein product. That includes the Moore–Penrose inverse (Sun et al.&nbsp;2016), the Drazin inverse (Behera et al.&nbsp;2020), and the core and core-EP inverses (Sahoo et al.&nbsp;2020). Papers defining more of them keep appearing. Nobody has agreed on a standard set, the way <img src="https://latex.codecogs.com/png.latex?A%5E%7B-1%7D"> and <img src="https://latex.codecogs.com/png.latex?A%5E%7B%5Cdagger%7D"> are standard for matrices.</li>
<li><strong>The numerics are behind the algebra.</strong> We know when most of these inverses exist, and whether they are unique. We know much less about how the algorithms behave in floating point, or what they cost to run. For several of them there is no agreed condition number to quote.</li>
<li><strong>There are few tools.</strong> <a href="https://tensorly.org/">TensorLy</a> covers decompositions well. For the inverses, you mostly write your own code from the definition. That is what <code>src/tinv.py</code> in this post does.</li>
<li><strong>Do not overstate this.</strong> For elasticity, the Einstein-product inverse has been settled engineering practice for decades, under a different name. What is still open is the general theory and its numerics, not every use shown above.</li>
</ul>
</section>
<section id="next-steps" class="level2" data-number="11">
<h2 data-number="11" class="anchored" data-anchor-id="next-steps"><span class="header-section-number">11</span> Next steps</h2>
<ul>
<li><strong>t-product solvers.</strong> t-GMRES and tensor Golub–Kahan (El Guide et al.&nbsp;2021) find <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BX%7D"> without ever building <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BA%7D%5E%7B-1%7D">. On a full <img src="https://latex.codecogs.com/png.latex?610%5Ctimes340%5Ctimes103"> scene, that is the difference between a program that runs and one that does not.</li>
<li><strong>Tucker- and TT-format inverses.</strong> Worth timing when the operator itself will not fit in memory. The inverse usually has a higher rank than the operator, so the question that matters is how much higher, on your own problem.</li>
<li><strong>CP-based approximate inverses.</strong> These give a rough approximation, not an inverse. They are cheap, but nothing is guaranteed.</li>
<li><strong>Where TensorLy stops.</strong> It gives you CP, Tucker, TT and t-SVD. For the inverses above, the definitions are short and <code>numpy</code> is enough. The four in <code>src/tinv.py</code> are about a dozen lines each.</li>
</ul>
<p>Problems. Pick. Products. Inverses. Follow. Flattening. Sometimes. Works. Check. First.</p>
</section>
<section id="references" class="level2" data-number="12">
<h2 data-number="12" class="anchored" data-anchor-id="references"><span class="header-section-number">12</span> References</h2>
<ul>
<li>Brazell, M., Li, N., Navasca, C., and Tamon, C. (2013). Solving multilinear systems via tensor inversion. <em>SIAM Journal on Matrix Analysis and Applications</em> 34(2), 542–570.</li>
<li>Behera, R., Nandi, A. K., and Sahoo, J. K. (2020). <a href="https://doi.org/10.1002/nla.2317">Further results on the Drazin inverse of even-order tensors</a>. <em>Numerical Linear Algebra with Applications</em> 27(5), e2317.</li>
<li>Dutilleul, P. (1999). <a href="https://doi.org/10.1080/00949659908811970">The MLE algorithm for the matrix normal distribution</a>. <em>Journal of Statistical Computation and Simulation</em> 64(2), 105–123.</li>
<li>El Guide, M., El Ichi, A., Jbilou, K., and Sadaka, R. (2021). <a href="https://doi.org/10.13001/ela.2021.5471">On tensor GMRES and Golub–Kahan methods via the t-product for color image processing</a>. <em>The Electronic Journal of Linear Algebra</em> 37, 524–543.</li>
<li>Hoff, P. D. (2011). <a href="https://doi.org/10.1214/11-BA606">Separable covariance arrays via the Tucker product, with applications to multivariate relational data</a>. <em>Bayesian Analysis</em> 6(2), 179–196.</li>
<li>Kilmer, M. E., and Martin, C. D. (2011). Factorization strategies for third-order tensors. <em>Linear Algebra and its Applications</em> 435(3), 641–658.</li>
<li>Kolda, T. G., and Bader, B. W. (2009). Tensor decompositions and applications. <em>SIAM Review</em> 51(3), 455–500.</li>
<li>Kossaifi, J., Panagakis, Y., Anandkumar, A., and Pantic, M. (2019). <a href="https://tensorly.org/">TensorLy: tensor learning in Python</a>. <em>JMLR</em> 20(26), 1–6.</li>
<li>Sahoo, J. K., Behera, R., Stanimirović, P. S., Katsikis, V. N., and Ma, H. (2020). <a href="https://doi.org/10.1007/s40314-019-0983-5">Core and core-EP inverses of tensors</a>. <em>Computational and Applied Mathematics</em> 39, 9.</li>
<li>Sun, L., Zheng, B., Bu, C., and Wei, Y. (2016). <a href="https://doi.org/10.1080/03081087.2015.1083933">Moore–Penrose inverse of tensors via Einstein product</a>. <em>Linear and Multilinear Algebra</em> 64(4), 686–698.</li>
<li><a href="https://www.ehu.eus/ccwintco/index.php/Hyperspectral_Remote_Sensing_Scenes">Hyperspectral Remote Sensing Scenes</a> — Pavia University, ROSIS sensor; scenes provided by P. Gamba (University of Pavia), collection assembled by M. Graña, M. A. Veganzones and B. Ayerdi (UPV/EHU). Fetched here from an <a href="https://huggingface.co/datasets/danaroth/pavia">ungated mirror</a>.</li>
<li><a href="https://data.cityofchicago.org/Public-Safety/Crimes-2001-to-Present/ijzp-q8t2">Crimes — 2001 to Present</a> — City of Chicago open data portal.</li>
<li><a href="../tensor-factorizations/">Tensor Factorizations and Tensor Inverses</a> — CP, Tucker, TT, t-SVD, and the four inverses on a synthetic cube.</li>
<li><a href="../uses-of-tensor-factorizations/">Uses of Tensor Factorizations</a> — the compression case.</li>
</ul>
<script src="widget-data/data.js"></script>
<script src="widgets.js"></script>


</section>

 ]]></description>
  <category>Linear Algebra</category>
  <category>Machine Learning</category>
  <category>Numerical Methods</category>
  <category>Tensors</category>
  <guid>https://project-delphi.github.io/ml-blog/posts/tensor-inverses-in-practice/</guid>
  <pubDate>Sun, 30 Aug 2026 05:00:00 GMT</pubDate>
  <media:content url="https://project-delphi.github.io/ml-blog/posts/tensor-inverses-in-practice/cover.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Claude API with the Anthropic Python SDK</title>
  <dc:creator>Ravi Kalia</dc:creator>
  <link>https://project-delphi.github.io/ml-blog/posts/claude-api-python-sdk/</link>
  <description><![CDATA[ 





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/claude-api-python-sdk/cover.png" class="img-fluid figure-img"></p>
<figcaption>Claude API with the Anthropic Python SDK</figcaption>
</figure>
</div>
<p>The official <code>anthropic</code> Python SDK talks to Claude through one method, <code>client.messages.create()</code>. Roles, content blocks, tools, and retrieved documents are fields on that call — not separate endpoints.</p>
<section id="messages" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="messages"><span class="header-section-number">1</span> Messages</h2>
<p><code>Anthropic()</code> reads <code>ANTHROPIC_API_KEY</code> from the environment. Every request is stateless: resend the full <code>messages</code> list each turn. This post uses <code>claude-sonnet-5</code>, the current Sonnet API id.</p>
<p>On that model, <code>messages</code> accepts two roles:</p>
<ul>
<li><strong><code>user</code></strong> — the human turn, or a <code>tool_result</code> you are sending back.</li>
<li><strong><code>assistant</code></strong> — a prior model turn you are replaying.</li>
</ul>
<p>System instructions are a top-level <code>system</code> parameter, not a role in that list — a string, or a list of text blocks. <code>max_tokens</code> is required.</p>
<p>The Opus-tier models take a third role. On <code>claude-opus-5</code>, <code>claude-opus-4-8</code>, <code>claude-fable-5</code> and <code>claude-mythos-5</code> you can append <code>{"role": "system", "content": ...}</code> inside <code>messages</code> to give the model a new operator instruction part-way through a conversation. Rewriting the top-level <code>system</code> would say the same thing and throw the cached prefix away with it; a message appended at the end does not. It cannot be the first entry, and it has to be either the last one or followed by an assistant turn. <code>claude-sonnet-5</code> rejects the role.</p>
<div id="1877b150" class="cell" data-execution_count="1">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> anthropic</span>
<span id="cb1-2"></span>
<span id="cb1-3">client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> anthropic.Anthropic()</span>
<span id="cb1-4"></span>
<span id="cb1-5">response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.messages.create(</span>
<span id="cb1-6">    model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"claude-sonnet-5"</span>,</span>
<span id="cb1-7">    max_tokens<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1024</span>,</span>
<span id="cb1-8">    system<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Answer in one sentence."</span>,</span>
<span id="cb1-9">    messages<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"What is a content block?"</span>}],</span>
<span id="cb1-10">)</span></code></pre></div>
</div>
<p>A string <code>content</code> is shorthand for one <code>text</code> block. The response <code>content</code> is always a list; do not assume <code>response.content[0].text</code>. Streaming, timeouts, and <code>input_json_delta</code> are covered in <a href="../messages-api-streaming/">Waiting, and Not Waiting</a>. Schema-constrained JSON is covered in <a href="../structured-json-with-claude/">Stop Parsing Prose</a>.</p>
</section>
<section id="content-blocks" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="content-blocks"><span class="header-section-number">2</span> Content blocks</h2>
<p>Each block has a <code>type</code>. Inspect <code>block.type</code> before reading any other field. <code>claude-sonnet-5</code> may emit a <code>thinking</code> block before text; a tool call adds another.</p>
<p>Documented input and output types used in this post:</p>
<ul>
<li><strong><code>text</code></strong> — ordinary prose. String <code>content</code> expands to this.</li>
<li><strong><code>image</code></strong> — a <code>source</code> object (<code>base64</code>, <code>url</code>, or <code>file</code> / <code>file_id</code>).</li>
<li><strong><code>document</code></strong> — plain text, PDF, or custom content; optional <code>citations</code>.</li>
<li><strong><code>search_result</code></strong> — a retrieved hit you already have, also citation-capable.</li>
<li><strong><code>tool_use</code></strong> — model output: <code>id</code>, <code>name</code>, <code>input</code>.</li>
<li><strong><code>tool_result</code></strong> — your follow-up: <code>tool_use_id</code> plus <code>content</code>.</li>
<li><strong><code>thinking</code></strong> / <strong><code>redacted_thinking</code></strong> — reasoning blocks. Pass them back unchanged on the next turn if the API returned them.</li>
</ul>
<div id="6345f626" class="cell" data-execution_count="2">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> block <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> response.content:</span>
<span id="cb2-2">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> block.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"text"</span>:</span>
<span id="cb2-3">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(block.text)</span>
<span id="cb2-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> block.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tool_use"</span>:</span>
<span id="cb2-5">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(block.name, block.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">input</span>)</span></code></pre></div>
</div>
<p>Unknown types should be ignored or forwarded, not parsed as text.</p>
</section>
<section id="tools" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="tools"><span class="header-section-number">3</span> Tools</h2>
<p>A <strong>client tool</strong> is a function you run. Pass <code>name</code>, <code>description</code>, and a JSON Schema <code>input_schema</code>. The model returns <code>stop_reason="tool_use"</code> and one or more <code>tool_use</code> blocks. You execute the function, then send a <code>user</code> message whose <code>content</code> is a <code>tool_result</code> whose <code>tool_use_id</code> matches.</p>
<p>A <strong>server tool</strong> (<code>web_search</code>, <code>web_fetch</code>, <code>code_execution</code>, and similar) runs on Anthropic’s infrastructure. You declare it by <code>type</code> and <code>name</code>; you do not execute it or invent a <code>tool_result</code> for it.</p>
<p>The loop for a client tool is always: request → <code>tool_use</code> → your code → <code>tool_result</code> → request again, with both the assistant turn and the result appended.</p>
<section id="stop-reasons" class="level3" data-number="3.1">
<h3 data-number="3.1" class="anchored" data-anchor-id="stop-reasons"><span class="header-section-number">3.1</span> Stop reasons</h3>
<p><code>stop_reason</code> says why the model stopped. It has six values, not two:</p>
<ul>
<li><strong><code>end_turn</code></strong> — finished on its own. Read the text.</li>
<li><strong><code>stop_sequence</code></strong> — hit a stop sequence you configured. Also finished.</li>
<li><strong><code>tool_use</code></strong> — wants tools run. The loop above.</li>
<li><strong><code>max_tokens</code></strong> — cut off part-way. Retry with a bigger cap; do not read it as an answer.</li>
<li><strong><code>pause_turn</code></strong> — a server tool hit its own iteration limit. Send the history back unchanged to resume.</li>
<li><strong><code>refusal</code></strong> — declined on safety grounds. Only here is <code>stop_details</code> filled in; it is <code>null</code> for the other five.</li>
</ul>
<p>A loop written as <code>while response.stop_reason == "tool_use"</code> exits on the other five. Two of those exits are right — <code>end_turn</code> and <code>stop_sequence</code> really are finished. The remaining three are not, and each hands the caller a partial answer with no error. <a href="../claude-architect-prep-week-1/">The Orchestrator-Worker Loop</a> branches on all six and sets the budgets that stop a run going forever.</p>
</section>
<section id="tool-runner" class="level3" data-number="3.2">
<h3 data-number="3.2" class="anchored" data-anchor-id="tool-runner"><span class="header-section-number">3.2</span> Tool runner</h3>
<p>The SDK will run that loop for you. Decorate a function with <code>@beta_tool</code>, pass it to <code>client.beta.messages.tool_runner(...)</code>, and call <code>runner.until_done()</code>. It dispatches the calls, appends the results, and returns the final message. The helper lives on <code>client.beta.messages</code>, so it needs the beta namespace.</p>
<p>Write the loop by hand once, to see what the runner is doing. Use the runner in production.</p>
</section>
</section>
<section id="a-tool-use-round-trip" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="a-tool-use-round-trip"><span class="header-section-number">4</span> A tool-use round trip</h2>
<p>The dict below is synthetic. Three invented refund rows stand in for an internal policy store a support bot would query. A real policy table would be confidential, and it would be out of date by the time you read this. Neither matters here: what the example shows is the shape of the blocks going in and out, not the rows. No live system is called; this post has no API key, so the cell is not executed.</p>
<div id="283eabf5" class="cell" data-execution_count="3">
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> anthropic</span>
<span id="cb3-2"></span>
<span id="cb3-3">POLICIES <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb3-4">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"standard"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Refunds within 30 days of purchase, unused items only."</span>,</span>
<span id="cb3-5">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"plus"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Refunds within 60 days; opened software is excluded."</span>,</span>
<span id="cb3-6">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"enterprise"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Refunds require a ticket and manager approval within 90 days."</span>,</span>
<span id="cb3-7">}</span>
<span id="cb3-8"></span>
<span id="cb3-9"></span>
<span id="cb3-10"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> lookup_policy(plan: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb3-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> POLICIES.get(plan, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"No policy found for that plan."</span>)</span>
<span id="cb3-12"></span>
<span id="cb3-13"></span>
<span id="cb3-14">client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> anthropic.Anthropic()  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># reads ANTHROPIC_API_KEY</span></span>
<span id="cb3-15"></span>
<span id="cb3-16">tools <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb3-17">    {</span>
<span id="cb3-18">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"name"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lookup_policy"</span>,</span>
<span id="cb3-19">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"description"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Return the refund policy text for a subscription plan."</span>,</span>
<span id="cb3-20">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"input_schema"</span>: {</span>
<span id="cb3-21">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"type"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"object"</span>,</span>
<span id="cb3-22">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"properties"</span>: {</span>
<span id="cb3-23">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"plan"</span>: {</span>
<span id="cb3-24">                    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"type"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"string"</span>,</span>
<span id="cb3-25">                    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"enum"</span>: [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"standard"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"plus"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"enterprise"</span>],</span>
<span id="cb3-26">                    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"description"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"The customer's subscription plan."</span>,</span>
<span id="cb3-27">                }</span>
<span id="cb3-28">            },</span>
<span id="cb3-29">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"required"</span>: [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"plan"</span>],</span>
<span id="cb3-30">        },</span>
<span id="cb3-31">    }</span>
<span id="cb3-32">]</span>
<span id="cb3-33"></span>
<span id="cb3-34">messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb3-35">    {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"What is the refund window on the Plus plan?"</span>},</span>
<span id="cb3-36">]</span>
<span id="cb3-37"></span>
<span id="cb3-38">response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.messages.create(</span>
<span id="cb3-39">    model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"claude-sonnet-5"</span>,</span>
<span id="cb3-40">    max_tokens<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1024</span>,</span>
<span id="cb3-41">    system<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Answer only from lookup_policy. If the tool returns nothing, say so."</span>,</span>
<span id="cb3-42">    tools<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>tools,</span>
<span id="cb3-43">    messages<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>messages,</span>
<span id="cb3-44">)</span>
<span id="cb3-45"></span>
<span id="cb3-46"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> response.stop_reason <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tool_use"</span>:</span>
<span id="cb3-47">    messages.append({<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"assistant"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: response.content})</span>
<span id="cb3-48">    results <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb3-49">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> block <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> response.content:</span>
<span id="cb3-50">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> block.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tool_use"</span>:</span>
<span id="cb3-51">            results.append(</span>
<span id="cb3-52">                {</span>
<span id="cb3-53">                    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"type"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tool_result"</span>,</span>
<span id="cb3-54">                    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tool_use_id"</span>: block.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">id</span>,</span>
<span id="cb3-55">                    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: lookup_policy(block.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">input</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"plan"</span>]),</span>
<span id="cb3-56">                }</span>
<span id="cb3-57">            )</span>
<span id="cb3-58">    messages.append({<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: results})</span>
<span id="cb3-59">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.messages.create(</span>
<span id="cb3-60">        model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"claude-sonnet-5"</span>,</span>
<span id="cb3-61">        max_tokens<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1024</span>,</span>
<span id="cb3-62">        system<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Answer only from lookup_policy. If the tool returns nothing, say so."</span>,</span>
<span id="cb3-63">        tools<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>tools,</span>
<span id="cb3-64">        messages<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>messages,</span>
<span id="cb3-65">    )</span>
<span id="cb3-66"></span>
<span id="cb3-67"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(response)</span>
<span id="cb3-68">text <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">next</span>(b.text <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> b <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> response.content <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> b.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"text"</span>)</span>
<span id="cb3-69"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(text)</span></code></pre></div>
</div>
<p>The objects below are hand-written to show the two <code>Message</code> shapes that loop produces. They are not a captured transcript.</p>
<p>First return, <code>stop_reason="tool_use"</code>:</p>
<pre><code>Message(
    id='msg_01SimulatedFirstTurn000000001',
    type='message',
    role='assistant',
    model='claude-sonnet-5',
    content=[
        ToolUseBlock(
            id='toolu_01SimulatedLookup00000001',
            name='lookup_policy',
            input={'plan': 'plus'},
            type='tool_use',
        )
    ],
    stop_reason='tool_use',
    usage=Usage(input_tokens=412, output_tokens=56),
)</code></pre>
<p>Second return, after <code>tool_result</code>:</p>
<pre><code>Message(
    id='msg_01SimulatedSecondTurn00000001',
    type='message',
    role='assistant',
    model='claude-sonnet-5',
    content=[
        TextBlock(
            type='text',
            text='The Plus plan allows refunds within 60 days; opened software is excluded.',
        )
    ],
    stop_reason='end_turn',
    usage=Usage(input_tokens=481, output_tokens=24),
)</code></pre>
<p>Check <code>stop_reason</code> before reading text. This exchange only produces two of the six: <code>tool_use</code> means there is no final answer yet, <code>end_turn</code> is the text turn. A loop that meets the other four still needs the branches above.</p>
</section>
<section id="rag" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="rag"><span class="header-section-number">5</span> RAG</h2>
<p>The SDK has no vector store and no embeddings client. Retrieval is your job; Claude only sees what you put on the next request.</p>
<p>Three patterns the Messages API actually supports:</p>
<ol type="1">
<li><strong>Stuff the hits.</strong> Put retrieved text in the user turn as <code>document</code> or <code>search_result</code> blocks, then ask the question as a <code>text</code> block.</li>
<li><strong>Citations.</strong> Set <code>citations={"enabled": True}</code> on those documents (all or none in the request). The model can emit citation blocks pointing at spans. Citations cannot be combined with structured outputs (<code>output_config.format</code>).</li>
<li><strong>Retrieval as a tool.</strong> Expose <code>search_kb</code> (or similar) as a client tool. Claude decides when to call it; you run the retriever and return a <code>tool_result</code>. Same loop as <code>lookup_policy</code> above.</li>
</ol>
<p>A stable document prefix can carry <code>cache_control={"type": "ephemeral"}</code> so later turns reuse it at cache-read prices. Changing the prefix, the tool list, or the model id misses the cache.</p>
<section id="embeddings-and-stores" class="level3" data-number="5.1">
<h3 data-number="5.1" class="anchored" data-anchor-id="embeddings-and-stores"><span class="header-section-number">5.1</span> Embeddings and stores</h3>
<p><a href="https://platform.claude.com/docs/en/build-with-claude/embeddings">Anthropic does not offer an embedding model</a>. The docs name <a href="https://www.voyageai.com/">Voyage AI</a> as the provider to start with (<code>voyageai</code> package, <code>VOYAGE_API_KEY</code>). Use <code>voyage-4</code> for a general index; <code>input_type="document"</code> on ingest and <code>input_type="query"</code> at search. Assess other vendors if the domain needs it.</p>
<p>The SDK still has no vector store. Typical indexes for the Voyage vectors:</p>
<ul>
<li><strong>pgvector</strong> — default when you already run Postgres. Hosted as RDS, Cloud SQL, Azure Database for PostgreSQL, <a href="https://supabase.com/">Supabase</a>, or <a href="https://neon.tech/">Neon</a>.</li>
<li><strong>Pinecone</strong> — managed, serverless. Anthropic’s <a href="https://platform.claude.com/cookbook/capabilities-retrieval-augmented-generation-guide">RAG cookbook</a> uses it.</li>
<li><strong>Qdrant</strong> or <strong>Weaviate</strong> — self-host or their clouds when you want an OSS engine and hybrid (keyword + vector) search. <a href="../pinecone-vs-weaviate/">Weaviate vs Pinecone</a> compares the two; <a href="../retrieval-augmented-generation/">Retrieval-augmented generation</a> shows the FAISS-shaped core and why its recall bounds the answer.</li>
</ul>
</section>
<section id="hosting" class="level3" data-number="5.2">
<h3 data-number="5.2" class="anchored" data-anchor-id="hosting"><span class="header-section-number">5.2</span> Hosting</h3>
<p>Claude itself is not only <code>api.anthropic.com</code>. The same Python package exposes first-party plus cloud hosts:</p>
<ul>
<li><strong><code>Anthropic()</code></strong> — first-party Messages API.</li>
<li><strong><code>AnthropicAWS</code></strong> — Claude Platform on AWS (<code>anthropic[aws]</code>).</li>
<li><strong><code>AnthropicBedrockMantle</code></strong> — Amazon Bedrock (<code>anthropic[bedrock]</code>). <code>AnthropicBedrock</code> still works, but it is the older <code>bedrock-runtime</code> InvokeModel path; new code wants the Mantle client.</li>
<li><strong><code>AnthropicVertex</code></strong> — Google Vertex AI (<code>anthropic[vertex]</code>).</li>
<li><strong><code>AnthropicFoundry</code></strong> — Microsoft Foundry.</li>
</ul>
<p>Who runs the service decides what you get. Claude Platform on AWS is Anthropic-operated: AWS sign-in and billing, the first-party API surface, same-day feature parity, and plain model ids. Bedrock and Vertex are partner-operated, so each has its own prices, its own release lag, and a smaller feature set — and Bedrock prefixes the model id (<code>anthropic.claude-sonnet-5</code>). Foundry bills through the Microsoft Marketplace at first-party rates.</p>
<p>Voyage embeddings are a separate key: Voyage’s API, or Voyage on AWS Marketplace. The vector store is a third bill — Pinecone, Qdrant Cloud, Weaviate Cloud, or pgvector on the Postgres you already pay for.</p>
</section>
</section>
<section id="audio" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="audio"><span class="header-section-number">6</span> Audio</h2>
<p>The Messages API has no speech endpoint and no audio content block. The OpenAI-compatible surface ignores <code>audio</code> and <code>input_audio</code> fields.</p>
<p>Claude Code <code>/voice</code> dictation is a CLI product feature. It is not <code>client.messages.create()</code>.</p>
<p>Spoken I/O is a second vendor. Anthropic’s cookbook builds a low-latency voice loop with <a href="https://elevenlabs.io/">ElevenLabs</a> for STT and TTS around a streaming Claude turn. Keep Claude on text; send transcripts in and pipe tokens out to ElevenLabs. <a href="../voice-ai-architectures-2026/">Voice AI architectures (2026)</a> compares ElevenLabs to the rest of that market.</p>
</section>
<section id="claude.md" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="claude.md"><span class="header-section-number">7</span> CLAUDE.md</h2>
<p><code>CLAUDE.md</code> is a Claude Code convention: a markdown file the coding agent reads at session start and holds as project context. Analogues are <code>AGENTS.md</code> and editor rule files. This repository’s root <code>CLAUDE.md</code> is one — render commands, register, freeze rules.</p>
<p><code>client.messages.create()</code> does not load it. Nothing in the Python SDK opens the working tree or injects a file by that name.</p>
<p>For an API app, put the same rules in <code>system</code> (optionally as cached text blocks). For a coding agent, keep them in <code>CLAUDE.md</code> and let the agent host load them. Do not expect a <code>claude_md=</code> parameter.</p>
<p>Messages. Stay. Stateless. Voyage. Embeds. Stores. Host. Voice. Needs. ElevenLabs.</p>
</section>
<section id="references" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="references"><span class="header-section-number">8</span> References</h2>
<ul>
<li><a href="https://platform.claude.com/docs/en/api/messages">Anthropic: Messages API</a></li>
<li><a href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/overview">Anthropic: Tool use</a></li>
<li><a href="https://platform.claude.com/docs/en/build-with-claude/citations">Anthropic: Citations</a></li>
<li><a href="https://platform.claude.com/docs/en/build-with-claude/embeddings">Anthropic: Embeddings (Voyage)</a></li>
<li><a href="https://platform.claude.com/docs/en/cli-sdks-libraries/sdks/python">Anthropic: Python SDK</a></li>
<li><a href="https://platform.claude.com/cookbook/capabilities-retrieval-augmented-generation-guide">Anthropic cookbook: RAG</a></li>
<li><a href="https://platform.claude.com/cookbook/third-party-elevenlabs-low-latency-stt-claude-tts">Anthropic cookbook: ElevenLabs voice</a></li>
<li><a href="https://code.claude.com/docs/en/memory">Claude Code: Memory / CLAUDE.md</a></li>
<li><a href="../claude-architect-prep-week-1/">Claude Certified Architect, Week 1: The Orchestrator-Worker Loop</a> — this blog; the loop that branches on every <code>stop_reason</code> this post lists.</li>
</ul>


</section>

 ]]></description>
  <category>Development</category>
  <category>LLM</category>
  <guid>https://project-delphi.github.io/ml-blog/posts/claude-api-python-sdk/</guid>
  <pubDate>Sat, 29 Aug 2026 05:00:00 GMT</pubDate>
  <media:content url="https://project-delphi.github.io/ml-blog/posts/claude-api-python-sdk/cover.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Claude Certified Architect, Week 1: The Orchestrator-Worker Loop</title>
  <dc:creator>Ravi Kalia</dc:creator>
  <link>https://project-delphi.github.io/ml-blog/posts/claude-architect-prep-week-1/</link>
  <description><![CDATA[ 





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/claude-architect-prep-week-1/cover.png" class="img-fluid figure-img"></p>
<figcaption>Claude Certified Architect, Week 1</figcaption>
</figure>
</div>
<p>An agent loop is a <code>while</code> loop around one API call. Each time round, the model tells you why it stopped. There are six things it can say, and a loop that only handles one of them will hand back a half-finished answer and call it done.</p>
<p>These are notes from the first of a few weeks studying for the Claude Certified Architect – Foundations exam. Its biggest topic is Agentic Architecture and Orchestration.</p>
<p>The full implementation is <a href="./orchestrator.py"><code>orchestrator.py</code></a>, in this post’s directory. Apart from the single-call example, every code block is copied straight out of it; the only change is the indentation.</p>
<p>I learned to think this way at <a href="https://www.sofi.com/">SoFi</a> (Social Finance, Inc.), building apps that use LLMs to take over repetitive regulatory work. Nobody reads the loop’s output there before it goes into a filing. A wrong answer that looks finished does more damage than a crash.</p>
<section id="scope" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="scope"><span class="header-section-number">1</span> Scope</h2>
<p>This week covers one pattern properly instead of skimming all six exam domains.</p>
<ul>
<li><strong>Domain</strong>: Agentic Architecture and Orchestration — 27% of the exam, the largest single block.</li>
<li><strong>Pattern</strong>: orchestrator-worker — one model plans and splits the work, then hands each piece to a worker loop that can reach only the tools it needs.</li>
<li><strong>Surface</strong>: the Claude Messages API and a hand-written loop. The SDK’s <code>client.beta.messages.tool_runner</code> handles this loop for you and is the right default in production; writing it out by hand once is what makes the rest of the material make sense.</li>
<li><strong>Model</strong>: <code>claude-opus-5</code> with adaptive thinking, which is on by default. Depth and spend are set with <code>output_config.effort</code> — <code>low</code> through <code>max</code> — not with a token budget; <code>budget_tokens</code> is rejected on this model.</li>
</ul>
</section>
<section id="prompting-versus-architecture" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="prompting-versus-architecture"><span class="header-section-number">2</span> Prompting versus architecture</h2>
<p>Prompting and agent design break in different places. Moving from one to the other changes what you are on the hook for.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th></th>
<th>Ad-hoc prompting</th>
<th>Agentic architecture</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Unit of work</td>
<td>One request, one response</td>
<td>A loop over many requests</td>
</tr>
<tr class="even">
<td>Who decides the next step</td>
<td>A human reading the output</td>
<td>Your control flow, reading <code>stop_reason</code></td>
</tr>
<tr class="odd">
<td>Primary failure surface</td>
<td>Prompt wording</td>
<td>State handling, tool boundary, budgets</td>
</tr>
<tr class="even">
<td>Effect of a bad output</td>
<td>A human notices</td>
<td>A malformed argument reaches a database</td>
</tr>
<tr class="odd">
<td>Cost ceiling</td>
<td>One call</td>
<td>Unbounded until you impose one</td>
</tr>
</tbody>
</table>
<p>This does not make prompting matter less. It moves where it matters. The prompt is still a lever, and a wider one than plain text: <a href="../soft-vs-hard-prompts/">Soft Prompts vs.&nbsp;Hard Prompts</a> starts a prompt on GPT-2’s vocabulary and, sixty gradient steps later, it has drifted somewhere no sentence can reach. But no prompt of either kind tells your code what to do when the model stops mid-sentence.</p>
<p>So the model’s output stops being an answer. It becomes an <strong>instruction to your code</strong>.</p>
</section>
<section id="a-single-messages-api-call" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="a-single-messages-api-call"><span class="header-section-number">3</span> A single Messages API call</h2>
<p>The API is stateless, so every call carries the whole conversation. This is the call the loop wraps.</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> anthropic</span>
<span id="cb1-2"></span>
<span id="cb1-3">client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> anthropic.Anthropic()  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ANTHROPIC_API_KEY, or an `ant auth login` profile</span></span>
<span id="cb1-4"></span>
<span id="cb1-5">response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.messages.create(</span>
<span id="cb1-6">    model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"claude-opus-5"</span>,</span>
<span id="cb1-7">    max_tokens<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4096</span>,</span>
<span id="cb1-8">    system<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Answer in one sentence."</span>,</span>
<span id="cb1-9">    messages<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Why should an agent loop check stop_reason?"</span>}],</span>
<span id="cb1-10">)</span>
<span id="cb1-11"></span>
<span id="cb1-12"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(response.stop_reason)          <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># "end_turn"</span></span>
<span id="cb1-13"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(response.usage.output_tokens)</span>
<span id="cb1-14"></span>
<span id="cb1-15"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> block <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> response.content:       <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># a list of typed blocks, not a string</span></span>
<span id="cb1-16">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> block.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"text"</span>:</span>
<span id="cb1-17">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(block.text)</span></code></pre></div>
<p>Three things in that response matter later.</p>
<ul>
<li><code>stop_reason</code> rides on every response. The next sections are the six values it can take, and what your code must do about them.</li>
<li><code>content</code> is a list of typed blocks, not a string. On <code>claude-opus-5</code> thinking is on by default, so the list can hold <code>thinking</code> blocks beside <code>text</code> ones — the loop checks <code>block.type</code>, and appends the list rather than the text.</li>
<li><code>messages</code> is the entire history, sent again every call. Nothing is kept server-side.</li>
</ul>
<p>The rest of that call — the roles <code>messages</code> takes, the content-block types, how a tool is defined, how retrieved text goes in as a <code>document</code> block — is surveyed in <a href="../claude-api-python-sdk/">Claude API with the Anthropic Python SDK</a>. This post assumes that surface and stays on the loop around it.</p>
<p>The loop that wraps this call is the one <a href="../llm-agents-from-first-principles/">LLM Agents from First Principles</a> builds from scratch, against a raw model with its own text parsing. The Messages API changes what is hard: a tool call arrives as a typed <code>tool_use</code> block instead of text to be picked apart, so the parsing step disappears and the branching step is what is left to get right.</p>
</section>
<section id="the-four-stage-lifecycle" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="the-four-stage-lifecycle"><span class="header-section-number">4</span> The four-stage lifecycle</h2>
<p>One turn of an agent loop has four stages. Three of the four are code you own; only the first is prompting.</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    A["Stage 1: initial prompt&lt;br/&gt;system + tools + history"] --&gt; B{"Stage 2&lt;br/&gt;why did it stop?"}
    B --&gt;|end_turn / stop_sequence| Z["Return to caller"]
    B --&gt;|refusal| R["Stop here:&lt;br/&gt;read stop_details"]
    B --&gt;|max_tokens| T["Retry bigger,&lt;br/&gt;then fail loudly"]
    B --&gt;|pause_turn| P["Resume,&lt;br/&gt;capped"]
    B --&gt;|tool_use| C["Stage 3: tool execution&lt;br/&gt;check, run, catch"]
    C --&gt; D["Stage 4: state hand-off&lt;br/&gt;append content + results"]
    D --&gt; A
    P --&gt; A
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<section id="stage-1-initial-prompt" class="level3" data-number="4.1">
<h3 data-number="4.1" class="anchored" data-anchor-id="stage-1-initial-prompt"><span class="header-section-number">4.1</span> Stage 1: initial prompt</h3>
<p>The request carries the system prompt, the tool definitions, and the entire history — the API is stateless.</p>
<ul>
<li>Put the parts that never change first (fixed system text, a tool list in a fixed order) and the parts that change every call last, so the cache still hits.</li>
<li>A timestamp or a request id in the system prompt quietly breaks the cache on every call. If <code>usage.cache_read_input_tokens</code> is zero on calls that should share a prefix, that is what happened.</li>
</ul>
</section>
<section id="stage-2-stop-reason-check" class="level3" data-number="4.2">
<h3 data-number="4.2" class="anchored" data-anchor-id="stage-2-stop-reason-check"><span class="header-section-number">4.2</span> Stage 2: stop reason check</h3>
<p>The model tells you why it stopped. Everything else in the loop hangs off this one check, and it is the part most code gets wrong.</p>
</section>
<section id="stage-3-tool-execution" class="level3" data-number="4.3">
<h3 data-number="4.3" class="anchored" data-anchor-id="stage-3-tool-execution"><span class="header-section-number">4.3</span> Stage 3: tool execution</h3>
<p>Each <code>tool_use</code> block names a tool and carries already-parsed JSON input.</p>
<ul>
<li>Check the input before you run anything. <code>strict: true</code> on the tool definition makes the API reject bad input before it ever reaches your handler.</li>
<li>One turn can carry several <code>tool_use</code> blocks. Running them concurrently is the point of that, though this loop dispatches them in order to keep the error path readable.</li>
<li>Never match on the raw JSON text; models escape Unicode and forward slashes differently.</li>
</ul>
</section>
<section id="stage-4-state-hand-off" class="level3" data-number="4.4">
<h3 data-number="4.4" class="anchored" data-anchor-id="stage-4-state-hand-off"><span class="header-section-number">4.4</span> Stage 4: state hand-off</h3>
<p>The results go back as the next user turn, and this stage has three hard rules.</p>
<ol type="1">
<li>Append the <strong>whole</strong> <code>response.content</code> list, not just its text. Dropping the <code>tool_use</code> and <code>thinking</code> blocks makes the next request invalid.</li>
<li>Every <code>tool_use_id</code> from the assistant turn needs a matching <code>tool_result</code>. Miss one and the next request is rejected.</li>
<li>All results from one turn go in a <strong>single</strong> user message. Splitting them trains the model out of making parallel tool calls.</li>
</ol>
</section>
</section>
<section id="stop-reasons" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="stop-reasons"><span class="header-section-number">5</span> Stop reasons</h2>
<p>Six values. Each one needs different code.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th><code>stop_reason</code></th>
<th>Meaning</th>
<th>Required action</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>end_turn</code></td>
<td>Finished naturally</td>
<td>Return the text</td>
</tr>
<tr class="even">
<td><code>stop_sequence</code></td>
<td>Hit a configured stop sequence</td>
<td>Return the text</td>
</tr>
<tr class="odd">
<td><code>tool_use</code></td>
<td>Wants one or more tools run</td>
<td>Run them, hand the results back, go round again</td>
</tr>
<tr class="even">
<td><code>max_tokens</code></td>
<td>Output cut off part-way</td>
<td>Retry with a bigger cap, then fail loudly</td>
</tr>
<tr class="odd">
<td><code>pause_turn</code></td>
<td>A server-side tool hit its own limit</td>
<td>Send the history back unchanged to resume, and cap the resumes</td>
</tr>
<tr class="even">
<td><code>refusal</code></td>
<td>Turned the request down on safety grounds</td>
<td>Stop here; read <code>stop_details</code></td>
</tr>
</tbody>
</table>
<p><code>stop_details</code> is filled in <strong>only</strong> for <code>refusal</code>. It is <code>null</code> for every other reason, so check before you read it.</p>
<p>The loop gives every value its own outcome, and sets limits the model never sees:</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Outcome(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, Enum):</span>
<span id="cb2-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""How a loop ended. Every value maps to one branch in `run_loop`."""</span></span>
<span id="cb2-3"></span>
<span id="cb2-4">    COMPLETED <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"completed"</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># stop_reason end_turn / stop_sequence</span></span>
<span id="cb2-5">    TRUNCATED <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"truncated"</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># stop_reason max_tokens, retry exhausted</span></span>
<span id="cb2-6">    REFUSED <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"refused"</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># stop_reason refusal</span></span>
<span id="cb2-7">    TURN_LIMIT <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"turn_limit"</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># our own ceiling, not the model's</span></span>
<span id="cb2-8">    STALLED <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"stalled"</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># same tool call repeated, no progress</span></span>
<span id="cb2-9">    ABORTED <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"aborted"</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ToolAbort from a handler</span></span>
<span id="cb2-10">    UNKNOWN_STOP <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"unknown_stop"</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># a stop_reason this code has never seen</span></span>
<span id="cb2-11"></span>
<span id="cb2-12"></span>
<span id="cb2-13"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@dataclass</span>(frozen<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb2-14"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Budget:</span>
<span id="cb2-15">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Ceilings the model cannot see and therefore cannot talk its way past."""</span></span>
<span id="cb2-16"></span>
<span id="cb2-17">    max_turns: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span></span>
<span id="cb2-18">    max_tool_calls: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">40</span></span>
<span id="cb2-19">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#: `pause_turn` means a server-side tool loop hit its own iteration cap.</span></span>
<span id="cb2-20">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#: Resuming is correct; resuming without a bound is an infinite loop.</span></span>
<span id="cb2-21">    max_pause_resumes: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span></span>
<span id="cb2-22">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#: One retry at a larger `max_tokens` before declaring truncation.</span></span>
<span id="cb2-23">    max_token_retries: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb2-24">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#: Identical (name, input) calls tolerated before the run is called stalled.</span></span>
<span id="cb2-25">    max_repeats: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span></code></pre></div>
<p>The checks are a flat ladder, and nothing drops off the bottom. A reason this code has never seen — one the API adds after it ships — stops the run instead of being taken for success:</p>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">stop <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> response.stop_reason</span>
<span id="cb3-2">log.debug(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"turn </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%d</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> stop_reason=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%s</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>, result.turns, stop)</span>
<span id="cb3-3"></span>
<span id="cb3-4"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> stop <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"end_turn"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"stop_sequence"</span>):</span>
<span id="cb3-5">    result.outcome <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Outcome.COMPLETED</span>
<span id="cb3-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> result</span>
<span id="cb3-7"></span>
<span id="cb3-8"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> stop <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"refusal"</span>:</span>
<span id="cb3-9">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># `stop_details` is populated only for refusals; guard before use.</span></span>
<span id="cb3-10">    details <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> response.stop_details</span>
<span id="cb3-11">    result.outcome <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Outcome.REFUSED</span>
<span id="cb3-12">    result.detail <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">getattr</span>(details, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"explanation"</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"no explanation"</span></span>
<span id="cb3-13">    log.error(</span>
<span id="cb3-14">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"refused (</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%s</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">): </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%s</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>,</span>
<span id="cb3-15">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">getattr</span>(details, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"category"</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>),</span>
<span id="cb3-16">        result.detail,</span>
<span id="cb3-17">    )</span>
<span id="cb3-18">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> result</span>
<span id="cb3-19"></span>
<span id="cb3-20"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> stop <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"max_tokens"</span>:</span>
<span id="cb3-21">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Output was cut mid-sentence. Continuing as if it were complete is</span></span>
<span id="cb3-22">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># how a half-written tool argument reaches a database.</span></span>
<span id="cb3-23">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> token_retries <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> budget.max_token_retries:</span>
<span id="cb3-24">        token_retries <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb3-25">        turn_max_tokens <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb3-26">        messages.pop()  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># discard the truncated turn before retrying</span></span>
<span id="cb3-27">        result.text <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> previous_text  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ... and the text it contributed</span></span>
<span id="cb3-28">        log.warning(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"truncated; retrying at max_tokens=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%d</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>, turn_max_tokens)</span>
<span id="cb3-29">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">continue</span></span>
<span id="cb3-30">    result.outcome <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Outcome.TRUNCATED</span>
<span id="cb3-31">    result.detail <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"still truncated after </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>token_retries<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> retry(ies)"</span></span>
<span id="cb3-32">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> result</span>
<span id="cb3-33"></span>
<span id="cb3-34"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> stop <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pause_turn"</span>:</span>
<span id="cb3-35">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># A server-side tool (web search, code execution) hit its own</span></span>
<span id="cb3-36">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># iteration limit. Re-send the history unchanged -- the API sees the</span></span>
<span id="cb3-37">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># trailing server_tool_use block and resumes; an added "continue"</span></span>
<span id="cb3-38">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># message confuses it.</span></span>
<span id="cb3-39">    pause_resumes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb3-40">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> pause_resumes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> budget.max_pause_resumes:</span>
<span id="cb3-41">        result.outcome <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Outcome.TURN_LIMIT</span>
<span id="cb3-42">        result.detail <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"paused </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>pause_resumes<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> times without finishing"</span></span>
<span id="cb3-43">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> result</span>
<span id="cb3-44">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">continue</span></span>
<span id="cb3-45"></span>
<span id="cb3-46"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> stop <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tool_use"</span>:</span>
<span id="cb3-47">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># A reason added to the API after this code was written. Stopping</span></span>
<span id="cb3-48">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># is the honest response; falling through would invent an answer.</span></span>
<span id="cb3-49">    result.outcome <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Outcome.UNKNOWN_STOP</span>
<span id="cb3-50">    result.detail <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"unhandled stop_reason </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>stop<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!r}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb3-51">    log.error(result.detail)</span>
<span id="cb3-52">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> result</span></code></pre></div>
<p>Note the <code>messages.pop()</code>. The cut-off turn is thrown away before the retry. Leave it in and the retry just adds a second turn carrying on from half a sentence.</p>
</section>
<section id="tool-execution-and-errors" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="tool-execution-and-errors"><span class="header-section-number">6</span> Tool execution and errors</h2>
<p>A broken tool is something you tell the model, not an exception that tears down the loop. Whatever goes wrong — no such tool, bad arguments, the handler blew up, it took too long — comes back as a <code>tool_result</code> marked <code>is_error: True</code>. The model reads it and tries something else.</p>
<div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1">started <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> time.monotonic()</span>
<span id="cb4-2"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb4-3">    output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> _call_with_deadline(</span>
<span id="cb4-4">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span>: tool.handler(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>block.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">input</span>),</span>
<span id="cb4-5">        tool.timeout_s,</span>
<span id="cb4-6">    )</span>
<span id="cb4-7"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">TimeoutError</span>:</span>
<span id="cb4-8">    log.warning(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%s</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> exceeded </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%g</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">s; abandoned"</span>, tool.name, tool.timeout_s)</span>
<span id="cb4-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> _error_result(</span>
<span id="cb4-10">        block.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">id</span>,</span>
<span id="cb4-11">        <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>tool<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> did not finish within </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>tool<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>timeout_s<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:g}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">s and was "</span></span>
<span id="cb4-12">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"abandoned. Narrow the request and try again."</span>,</span>
<span id="cb4-13">    )</span>
<span id="cb4-14"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> ToolFailure <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> exc:</span>
<span id="cb4-15">    log.warning(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%s</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> failed: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%s</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>, tool.name, exc)</span>
<span id="cb4-16">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> _error_result(block.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">id</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(exc))</span>
<span id="cb4-17"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> ToolAbort:</span>
<span id="cb4-18">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span></span>
<span id="cb4-19"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Exception</span> <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> exc:  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># noqa: BLE001 - a handler bug must not kill the run</span></span>
<span id="cb4-20">    log.exception(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%s</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> raised"</span>, tool.name)</span>
<span id="cb4-21">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> _error_result(</span>
<span id="cb4-22">        block.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">id</span>,</span>
<span id="cb4-23">        <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>tool<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> failed with </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span>(exc)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">__name__</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>exc<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>,</span>
<span id="cb4-24">    )</span>
<span id="cb4-25"></span>
<span id="cb4-26">log.debug(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%s</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> ran in </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%.2f</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">s"</span>, tool.name, time.monotonic() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> started)</span>
<span id="cb4-27"></span>
<span id="cb4-28"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {</span>
<span id="cb4-29">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"type"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tool_result"</span>,</span>
<span id="cb4-30">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tool_use_id"</span>: block.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">id</span>,</span>
<span id="cb4-31">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: output <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(output, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> json.dumps(output),</span>
<span id="cb4-32">}</span></code></pre></div>
<p><code>ToolAbort</code> is the exception: dead credentials, a missing binary, a tripped breaker. The model cannot fix any of those, so the run stops instead of retrying against something that is already broken.</p>
<p>Error results carry the same <code>tool_use_id</code> as any other:</p>
<div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _error_result(tool_use_id: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, message: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, Any]:</span>
<span id="cb5-2">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {</span>
<span id="cb5-3">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"type"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tool_result"</span>,</span>
<span id="cb5-4">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tool_use_id"</span>: tool_use_id,</span>
<span id="cb5-5">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: message,</span>
<span id="cb5-6">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"is_error"</span>: <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>,</span>
<span id="cb5-7">    }</span></code></pre></div>
</section>
<section id="loop-limits" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="loop-limits"><span class="header-section-number">7</span> Loop limits</h2>
<p>Two limits in the <code>tool_use</code> branch stop a run going forever: a hard cap on tool calls, and a check for repeats.</p>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># --- stop_reason == "tool_use" -------------------------------------</span></span>
<span id="cb6-2">calls <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [b <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> b <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> response.content <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> b.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tool_use"</span>]</span>
<span id="cb6-3">granted <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {t.get(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"name"</span>) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> t <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> tools}</span>
<span id="cb6-4">tool_results: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, Any]] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb6-5">terminal: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>[Outcome, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb6-6"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> block <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> calls:</span>
<span id="cb6-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> result.tool_calls <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> budget.max_tool_calls:</span>
<span id="cb6-8">        terminal <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (</span>
<span id="cb6-9">            Outcome.TURN_LIMIT,</span>
<span id="cb6-10">            <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"exceeded </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>budget<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>max_tool_calls<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> tool calls"</span>,</span>
<span id="cb6-11">        )</span>
<span id="cb6-12">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">break</span></span>
<span id="cb6-13"></span>
<span id="cb6-14">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># An agent that repeats a call verbatim is not making progress; it</span></span>
<span id="cb6-15">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># is usually re-reading a resource whose result it misread. Say so</span></span>
<span id="cb6-16">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># in-band before the turn ceiling burns the whole budget.</span></span>
<span id="cb6-17">    signature <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (block.name, json.dumps(block.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">input</span>, sort_keys<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>))</span>
<span id="cb6-18">    seen_calls[signature] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> seen_calls.get(signature, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb6-19">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> seen_calls[signature] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> budget.max_repeats:</span>
<span id="cb6-20">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Answer the block anyway -- every tool_use id in the turn needs</span></span>
<span id="cb6-21">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># a result or the history cannot be resent -- then stop.</span></span>
<span id="cb6-22">        tool_results.append(</span>
<span id="cb6-23">            _error_result(</span>
<span id="cb6-24">                block.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">id</span>,</span>
<span id="cb6-25">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>block<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> has already been called with these exact "</span></span>
<span id="cb6-26">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"arguments and returned the same result. Use a different "</span></span>
<span id="cb6-27">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"approach or state what is blocking you."</span>,</span>
<span id="cb6-28">            ),</span>
<span id="cb6-29">        )</span>
<span id="cb6-30">        terminal <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (</span>
<span id="cb6-31">            Outcome.STALLED,</span>
<span id="cb6-32">            <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>block<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> repeated </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>seen_calls[signature]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> times"</span>,</span>
<span id="cb6-33">        )</span>
<span id="cb6-34">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">break</span></span>
<span id="cb6-35"></span>
<span id="cb6-36">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb6-37">        result.tool_calls <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># counted only when it actually runs</span></span>
<span id="cb6-38">        tool_results.append(registry.dispatch(block, granted))</span>
<span id="cb6-39">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> ToolAbort <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> exc:</span>
<span id="cb6-40">        terminal <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (Outcome.ABORTED, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(exc))</span>
<span id="cb6-41">        log.error(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"aborted by </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%s</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%s</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>, block.name, exc)</span>
<span id="cb6-42">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">break</span></span>
<span id="cb6-43"></span>
<span id="cb6-44"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># However the turn ended, every tool_use id in it needs a result or</span></span>
<span id="cb6-45"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># the history cannot be resent -- so answer the blocks that never ran</span></span>
<span id="cb6-46"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># before handing it back. This is why the loop breaks rather than</span></span>
<span id="cb6-47"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># returning from inside the loop.</span></span>
<span id="cb6-48">answered <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {r[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tool_use_id"</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> r <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> tool_results}</span>
<span id="cb6-49">tool_results.extend(</span>
<span id="cb6-50">    _error_result(b.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">id</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Not run: the loop stopped part-way through this turn."</span>)</span>
<span id="cb6-51">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> b <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> calls</span>
<span id="cb6-52">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> b.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">id</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> answered</span>
<span id="cb6-53">)</span>
<span id="cb6-54"></span>
<span id="cb6-55"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># All results go back in one user message. Splitting them across</span></span>
<span id="cb6-56"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># several messages trains the model out of parallel tool calls.</span></span>
<span id="cb6-57">messages.append({<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: tool_results})</span>
<span id="cb6-58"></span>
<span id="cb6-59"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> terminal <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb6-60">    result.outcome, result.detail <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> terminal</span>
<span id="cb6-61">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> result</span></code></pre></div>
<p>The repeat check is the cheaper of the two. An agent calling the same tool with the same arguments is stuck, usually re-reading something whose answer it misread the first time. Saying so in the tool result gives it a chance to recover before the turn limit eats the whole budget.</p>
</section>
<section id="delegation-as-a-tool-call" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="delegation-as-a-tool-call"><span class="header-section-number">8</span> Delegation as a tool call</h2>
<p>The orchestrator has one tool, and it starts a worker loop. Because handing off work is a plain tool call and not a special case, it gets that error path: a worker that gets stuck, cut off, or refused comes back as an <code>is_error</code> result the planner can work around.</p>
<div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> delegate(task: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, tools: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb7-2">    spend[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"workers"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb7-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> spend[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"workers"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> max_workers:</span>
<span id="cb7-4">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Its own ceiling, deliberately below the planner's tool-call cap.</span></span>
<span id="cb7-5">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Sharing that cap made this unreachable: run_loop stops before it</span></span>
<span id="cb7-6">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># dispatches the call that would exceed it.</span></span>
<span id="cb7-7">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> ToolAbort(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"worker budget exhausted after </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>max_workers<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> workers"</span>)</span>
<span id="cb7-8"></span>
<span id="cb7-9">    unknown <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [name <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> tools <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> name <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> registry.names]</span>
<span id="cb7-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> unknown:</span>
<span id="cb7-11">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> ToolFailure(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"no such tool(s): </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">', '</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>join(unknown)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb7-12"></span>
<span id="cb7-13">    worker <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> run_loop(</span>
<span id="cb7-14">        client,</span>
<span id="cb7-15">        system<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>worker_system,</span>
<span id="cb7-16">        messages<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: task}],</span>
<span id="cb7-17">        tools<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>registry.params(tools),  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># run_loop enforces this grant</span></span>
<span id="cb7-18">        registry<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>registry,</span>
<span id="cb7-19">        budget<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>worker_budget,</span>
<span id="cb7-20">        model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>model,</span>
<span id="cb7-21">    )</span>
<span id="cb7-22">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> worker.ok:</span>
<span id="cb7-23">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Surfaced to the planner as a tool failure, with the outcome name</span></span>
<span id="cb7-24">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># so it can distinguish "ran out of turns" from "was refused".</span></span>
<span id="cb7-25">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> ToolFailure(</span>
<span id="cb7-26">            <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"worker ended as </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>worker<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>outcome<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>value<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> (</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>worker<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>detail<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">). "</span></span>
<span id="cb7-27">            <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Partial output: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>worker<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>text[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">500</span>] <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'(none)'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>,</span>
<span id="cb7-28">        )</span>
<span id="cb7-29">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> worker.text</span></code></pre></div>
<p>Two things fall out of this.</p>
<ul>
<li><strong>The worker starts clean.</strong> It sees only the <code>task</code> string, so a long planner history never bloats worker requests.</li>
<li><strong>A worker gets only what it needs.</strong> The planner names the tools that worker may call, and <code>run_loop</code> derives the allowed set from the definitions it sent, so a call to anything else comes back refused. Narrowing the definitions alone would not do it: a model can name a tool it was never shown, either by hallucinating a name from an earlier result or because injected text told it to.</li>
</ul>
</section>
<section id="anti-patterns-and-production-failure-modes" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="anti-patterns-and-production-failure-modes"><span class="header-section-number">9</span> Anti-patterns and production failure modes</h2>
<p>All eight pass a demo and fail in production. The first two hurt most in regulatory work, where nothing about a cut-off answer looks different from a finished one.</p>
<ol type="1">
<li><strong><code>while response.stop_reason == "tool_use"</code>.</strong> The most common bug by far. A <code>max_tokens</code> truncation, a <code>refusal</code>, and a <code>pause_turn</code> all exit the loop as though the model had finished, and the caller gets a partial answer with no error. Branch on every value by name.</li>
<li><strong>Unhandled schema validation errors.</strong> A handler that raises on a bad argument tears down the loop and throws away a turn you could have saved. Worse, catching it and dropping the block leaves a <code>tool_use_id</code> unanswered, and the <em>next</em> request comes back as a 400 that names neither the tool nor the turn. Always send a result, and set <code>is_error</code>.</li>
<li><strong>Infinite execution loops.</strong> Three separate causes: no turn limit, resuming <code>pause_turn</code> forever, and a model retrying the same call forever. All three need separate limits. One <code>max_turns</code> hides the other two until the bill shows up.</li>
<li><strong>Appending only the text.</strong> <code>messages.append({"role": "assistant", "content": response.text})</code> loses the <code>tool_use</code> and <code>thinking</code> blocks. It breaks on the next request, well away from the line that caused it.</li>
<li><strong>Splitting parallel tool results.</strong> The API accepts each <code>tool_result</code> in its own user message, and doing that quietly teaches the model to stop batching calls. Things get slower over a session; the API never complains.</li>
<li><strong>One broad <code>except</code>.</strong> Catching <code>APIStatusError</code> alone lumps the errors worth retrying (429, 5xx, connection) in with the ones that will never succeed (400, 404). Catch the specific ones first.</li>
<li><strong>A volatile cache prefix.</strong> A clock, a UUID, or an unsorted <code>json.dumps()</code> in the system prompt or tool list breaks the prefix on every call. The bill triples and nothing errors.</li>
<li><strong>Uniform tool grants.</strong> Give every worker the full tool list and one bad call can reach anything.</li>
</ol>
</section>
<section id="takeaways" class="level2" data-number="10">
<h2 data-number="10" class="anchored" data-anchor-id="takeaways"><span class="header-section-number">10</span> Takeaways</h2>
<ul>
<li>Treat <code>stop_reason</code> as six named cases, not a yes or no.</li>
<li>A broken tool sends a message back. A broken dependency raises. Use different exception types for the two.</li>
<li>Limit turns, tool calls, pause resumes, and repeated calls separately. They run away in four different ways.</li>
<li>Give a worker the smallest tool set its job needs, and one self-contained instruction instead of the whole history.</li>
<li>Use the SDK’s tool runner in production. Write the loop by hand once, to see what it is doing for you.</li>
<li>Check caching with <code>usage.cache_read_input_tokens</code>. Do not assume you put the breakpoint in the right place.</li>
</ul>
<p>Stop. Reasons. Branch. Errors. Return. Inline. Budgets. Bound. Loops. Delegate. Narrowly.</p>
</section>
<section id="references" class="level2" data-number="11">
<h2 data-number="11" class="anchored" data-anchor-id="references"><span class="header-section-number">11</span> References</h2>
<ul>
<li><a href="https://platform.claude.com/docs/en/build-with-claude/handling-stop-reasons">Handling stop reasons</a> — Anthropic docs; what to do for each stop reason.</li>
<li><a href="https://www.anthropic.com/engineering/building-effective-agents">Building effective agents</a> — Anthropic engineering; where orchestrator-worker sits among the agent patterns.</li>
<li><a href="https://github.com/anthropics/anthropic-sdk-python">anthropic-sdk-python</a> — the SDK, including <code>client.beta.messages.tool_runner</code>.</li>
<li><em>Claude Certified Architect – Foundations exam guide</em> (Anthropic) — where the 27% figure comes from.</li>
<li><a href="../soft-vs-hard-prompts/">Soft Prompts vs.&nbsp;Hard Prompts</a> — this blog; the hard-versus-soft prompt distinction.</li>
<li><a href="../llm-agents-from-first-principles/">LLM Agents from First Principles</a> — this blog; the same loop built from scratch against a raw model.</li>
<li><a href="../claude-api-python-sdk/">Claude API with the Anthropic Python SDK</a> — this blog; the request surface this loop wraps.</li>
<li><a href="./orchestrator.py"><code>orchestrator.py</code></a> — the full code the blocks here are copied from.</li>
</ul>


</section>

 ]]></description>
  <category>AI</category>
  <category>Claude</category>
  <category>Development</category>
  <category>Personal</category>
  <guid>https://project-delphi.github.io/ml-blog/posts/claude-architect-prep-week-1/</guid>
  <pubDate>Sat, 29 Aug 2026 05:00:00 GMT</pubDate>
  <media:content url="https://project-delphi.github.io/ml-blog/posts/claude-architect-prep-week-1/cover.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>GitHub vs GitLab: Origins, Dominance, and AI-Native Hosts</title>
  <dc:creator>Ravi Kalia</dc:creator>
  <link>https://project-delphi.github.io/ml-blog/posts/github-vs-gitlab/</link>
  <description><![CDATA[ 





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/github-vs-gitlab/cover.png" class="img-fluid figure-img"></p>
<figcaption>GitHub vs GitLab: Origins, Dominance, and AI-Native Hosts</figcaption>
</figure>
</div>
<p>GitHub and GitLab differ by who built them, who owns them, and who leads today — and four AI-native platforms are now reshaping git hosting around them. The comparison assumes GitHub as the default workflow most open-source projects and individual developers already use.</p>
<section id="origins-and-ownership" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="origins-and-ownership"><span class="header-section-number">1</span> Origins and ownership</h2>
<p>Both platforms wrap the same Git protocol. Their histories explain why one feels like a social network for code and the other like a single-vendor DevOps stack.</p>
<ul>
<li><strong>GitHub (2008):</strong> Tom Preston-Werner, Chris Wanstrath, P. J. Hyett, and Scott Chacon; San Francisco. Bootstrapped profitably, then VC-backed. <strong>Microsoft</strong> acquired it for <strong>$7.5B</strong> (closed Oct 2018); wholly owned subsidiary since, with Copilot as the clearest AI bet on the platform.</li>
<li><strong>GitLab (2011 OSS, 2014 Inc.):</strong> Dmitriy Zaporozhets started the project in Ukraine; Sytse Sijbrandij built the company from the Netherlands. Y Combinator 2015. <strong>Public on Nasdaq (GTLB)</strong> since Oct 2021 IPO; Sijbrandij executive chairman.</li>
</ul>
</section>
<section id="dominance-today" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="dominance-today"><span class="header-section-number">2</span> Dominance today</h2>
<p>Git won the version-control war. The competition is between forges — services that host remotes, run CI, and gate merges.</p>
<ul>
<li><strong>GitHub leads</strong> on adoption: ~<strong>38% SCM market share</strong> vs GitLab ~<strong>16%</strong>; roughly 2–3× on surveys and open-source gravity (Stack Overflow 2025: ~81% GitHub vs ~36% GitLab).</li>
<li><strong>GitLab’s wedge:</strong> enterprise DevOps — integrated CI/security, self-managed installs, regulated buyers; higher spend per seat despite lower headcount share.</li>
<li><strong>Third place:</strong> Bitbucket (~9% revenue) inside Atlassian/Jira shops.</li>
</ul>
<p>For most teams starting today, GitHub is the path of least resistance: contributors already have accounts, and the Actions marketplace covers most CI without a second vendor.</p>
</section>
<section id="product-split" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="product-split"><span class="header-section-number">3</span> Product split</h2>
<p>Both are Git forges: remotes, PRs/MRs, review, CI, permissions. The split is how much of the delivery pipeline each ships in the box.</p>
<ul>
<li><strong>GitHub:</strong> <strong>Actions</strong> marketplace, Pages, Dependabot, Copilot on-host. Wins on ecosystem depth and the assumption that the repo lives on GitHub.</li>
<li><strong>GitLab:</strong> CI, registry, scanning, environments in one product; <strong>GitLab Duo</strong> in-UI. Wins on single-vendor DevOps and self-hosting.</li>
</ul>
<p>Pick GitHub when open-source reach and third-party Actions matter. Pick GitLab when you want the full stack on your infrastructure without stitching point tools.</p>
</section>
<section id="ai-native-platforms" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="ai-native-platforms"><span class="header-section-number">4</span> AI-native platforms</h2>
<p>None of these replaces Git overnight. They change where code is hosted, how fast agents can read it, and what gets versioned besides commits. Legacy forges were built for human-paced PRs; agent fleets generate read traffic and merge volume those systems were not designed for.</p>
<ul>
<li><strong><a href="https://cursor.com/docs/origin">Cursor Origin</a></strong> (Aug 2026 beta): git forge inside <a href="../../posts/cursor_ai/">Cursor</a> — repos, PRs, code browse, <strong>GitHub sync/mirror</strong>; paid plans. Agent-scale hosting inside the editor; GitHub remains system of record for mirrored repos.</li>
<li><strong><a href="https://www.harness.io/blog/agent-ready-code-repository-ai-code-review">Harness Code Repository</a></strong> (Aug 2026): SCM for <strong>agent-scale PR volume</strong>; paired <strong>AI Code Review</strong> (risk-ranked diffs); <strong>MCP + CLI</strong>; OPA gates; free tier; migrates from GitHub/GitLab.</li>
<li><strong><a href="https://entire.io/">Entire</a></strong> (Jul 2026 preview): <strong>Thomas Dohmke</strong> (ex-GitHub CEO); <strong>distributed Git network</strong> — regional mirrors (US/EU/AU) offload agent clone/pull traffic from GitHub; native repos on roadmap; session memory in-repo.</li>
<li><strong><a href="https://zed.dev/blog/introducing-deltadb">Zed DeltaDB</a></strong> (beta 2026): <strong>CRDT delta stream</strong> — versions every edit and linked agent conversation, not just commits; <strong>runs alongside Git</strong>; Delta client for multiplayer threads.</li>
</ul>
<p>Git. Won. GitHub. Leads. GitLab. Integrated. Agents. Stress. Origin. Harness. Entire. Delta.</p>
</section>
<section id="references" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="references"><span class="header-section-number">5</span> References</h2>
<ul>
<li><a href="https://en.wikipedia.org/wiki/GitHub">GitHub — Wikipedia</a> (founding, Microsoft acquisition)</li>
<li><a href="https://handbook.gitlab.com/handbook/company/history/">History of GitLab — GitLab Handbook</a></li>
<li><a href="https://jonmoshier.com/notes/code-review-tooling-market-github-gitlab-and-the-pr-review-bottleneck/">Code review tooling market — Jon Moshier</a> (SCM share figures)</li>
<li><a href="https://cursor.com/docs/origin">Cursor Origin documentation</a></li>
<li><a href="https://www.harness.io/blog/agent-ready-code-repository-ai-code-review">Harness Agent-Ready Code Repository — blog</a></li>
<li><a href="https://thenewstack.io/entire-git-for-agents/">Entire distributed Git network — The New Stack</a></li>
<li><a href="https://zed.dev/blog/introducing-deltadb">Introducing DeltaDB — Zed Blog</a></li>
</ul>


</section>

 ]]></description>
  <category>Tooling</category>
  <guid>https://project-delphi.github.io/ml-blog/posts/github-vs-gitlab/</guid>
  <pubDate>Sat, 29 Aug 2026 05:00:00 GMT</pubDate>
  <media:content url="https://project-delphi.github.io/ml-blog/posts/github-vs-gitlab/cover.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Uses of Tensor Factorizations</title>
  <dc:creator>Ravi Kalia</dc:creator>
  <link>https://project-delphi.github.io/ml-blog/posts/uses-of-tensor-factorizations/</link>
  <description><![CDATA[ 





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/uses-of-tensor-factorizations/cover.png" class="img-fluid figure-img"></p>
<figcaption>Uses of Tensor Factorizations</figcaption>
</figure>
</div>
<p>The main application of tensor factorizations is compression: images and video, or the parameter tensors inside a neural net — a VGG conv kernel, a transformer dense map.</p>
<p>The algebra is in <a href="../tensor-factorizations/">Tensor Factorizations and Tensor Inverses</a>. A post on tensor inverses is coming.</p>
<p>NumPy for CP, Tucker, and truncated HOSVD is first. Then the two compressions, then an unmixing cube: flattening loses the dyes.</p>
<section id="tensor-data" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="tensor-data"><span class="header-section-number">1</span> Tensor data</h2>
<ul>
<li><strong>Film clip.</strong> Five seconds of the 1962 <em>Lawrence of Arabia</em> theatrical trailer (<a href="https://commons.wikimedia.org/wiki/File:Lawrence_Of_Arabia_(1962)_-_Trailer.webm">public domain, Wikimedia Commons</a>). Colour, picture and sound. Cut at 00:00:55 (train explosion, then the cut that follows). Files: <code>media/clip.mp4</code>, <code>still.png</code>, <code>frames.npy</code>, <code>clip.wav</code>.</li>
<li><strong>Conv kernel.</strong> Synthetic <img src="https://latex.codecogs.com/png.latex?3%5Ctimes%203%5Ctimes%2064%5Ctimes%2064"> CP rank-16 weight plus noise, seed 7. Stands in for one VGG-16 <code>conv5</code> layer. The 512-channel counts are closed-form, not a downloaded VGG.</li>
<li><strong>Dense map.</strong> Synthetic <img src="https://latex.codecogs.com/png.latex?256%5Ctimes%20256"> TT-rank-4 matrix plus noise. Stands in for a <img src="https://latex.codecogs.com/png.latex?4096%5Ctimes%204096"> transformer projection. The <img src="https://latex.codecogs.com/png.latex?4096"> counts use the same formula.</li>
<li><strong>Mixing cube.</strong> Synthetic <img src="https://latex.codecogs.com/png.latex?20%5Ctimes%2024%5Ctimes%2018"> table, three dyes plus noise: sample × emission colour × excitation colour. Seed 7. Stands in for a fluorescence excitation–emission stack.</li>
</ul>
<p>Figures and the slider read <code>widget-data/curves.json</code>, written from the same seed.</p>
<video controls="" width="480" src="media/clip.mp4">
</video>
</section>
<section id="factorizations" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="factorizations"><span class="header-section-number">2</span> Factorizations</h2>
<p>CP, Tucker, and truncated HOSVD in NumPy. The algebra is in <a href="../tensor-factorizations/">Tensor Factorizations and Tensor Inverses</a>. Later sections call these same functions.</p>
<section id="toy-cube" class="level3" data-number="2.1">
<h3 data-number="2.1" class="anchored" data-anchor-id="toy-cube"><span class="header-section-number">2.1</span> Toy cube</h3>
<p>This section uses a synthetic <img src="https://latex.codecogs.com/png.latex?8%5Ctimes%207%5Ctimes%206"> array.</p>
<ul>
<li>Rank-2 CP: two outer products of Gaussian bumps, plus i.i.d. Gaussian noise at <img src="https://latex.codecogs.com/png.latex?0.08"> times the clean scale, seed 7.</li>
<li>Stands in for a small 3-way table (sample × feature × condition). The size is so the factors fit on one screen.</li>
<li>Objective: recover the two components. Report <img src="https://latex.codecogs.com/png.latex?%5C%7CX-%5Chat%20X%5C%7C_F/%5C%7CX%5C%7C_F">.</li>
<li>A flattened SVD mixes the two bumps. An overspecified CP rank invents a third. Either error mis-assigns which condition drives which feature.</li>
<li>Tensor methods fit because Kruskal uniqueness (CP) and per-mode truncation (HOSVD / Tucker) are properties of this layout, not of any matrix obtained from it.</li>
</ul>
<div id="0fb4e932" class="cell" data-execution_count="2">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> bump_1d(n, center, width):</span>
<span id="cb1-2">    grid <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linspace(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, n)</span>
<span id="cb1-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> np.exp(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> ((grid <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> center) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> width) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb1-4"></span>
<span id="cb1-5"></span>
<span id="cb1-6">rng_toy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.random.default_rng(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>)</span>
<span id="cb1-7">shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>)</span>
<span id="cb1-8">terms <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb1-9"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> center <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> ((<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.28</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.22</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.30</span>), (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.72</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.75</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.70</span>)):</span>
<span id="cb1-10">    a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> bump_1d(shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], center[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.16</span>)</span>
<span id="cb1-11">    b <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> bump_1d(shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], center[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.16</span>)</span>
<span id="cb1-12">    c <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> bump_1d(shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>], center[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>], <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.16</span>)</span>
<span id="cb1-13">    terms.append(a[:, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> b[<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, :, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> c[<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, :])</span>
<span id="cb1-14">X_clean <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(terms)</span>
<span id="cb1-15">X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X_clean <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.08</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(X_clean.std()) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> rng_toy.normal(size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>X_clean.shape)</span>
<span id="cb1-16"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"X.shape"</span>, X.shape, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"entries"</span>, X.size)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>X.shape (8, 7, 6) entries 336</code></pre>
</div>
</div>
</section>
<section id="unfolding" class="level3" data-number="2.2">
<h3 data-number="2.2" class="anchored" data-anchor-id="unfolding"><span class="header-section-number">2.2</span> Unfolding</h3>
<p>Mode-<img src="https://latex.codecogs.com/png.latex?n"> unfolding <img src="https://latex.codecogs.com/png.latex?X_%7B(n)%7D"> puts mode <img src="https://latex.codecogs.com/png.latex?n"> on the rows. SVD of that matrix is Eckart–Young for the unfolding, not for <img src="https://latex.codecogs.com/png.latex?X">. Mode-<img src="https://latex.codecogs.com/png.latex?n"> product <img src="https://latex.codecogs.com/png.latex?X%5Ctimes_n%20M"> is <img src="https://latex.codecogs.com/png.latex?M"> times that unfolding, folded back.</p>
<div id="6becafc2" class="cell" data-execution_count="3">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> unfold(X, n):</span>
<span id="cb3-2">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> np.moveaxis(X, n, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>).reshape(X.shape[n], <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb3-3"></span>
<span id="cb3-4"></span>
<span id="cb3-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> fold(mat, n, shape):</span>
<span id="cb3-6">    full <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [shape[n]] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> [s <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, s <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(shape) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> n]</span>
<span id="cb3-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> np.moveaxis(mat.reshape(full), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, n)</span>
<span id="cb3-8"></span>
<span id="cb3-9"></span>
<span id="cb3-10"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> mode_prod(X, M, n):</span>
<span id="cb3-11">    shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(X.shape)</span>
<span id="cb3-12">    shape[n] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> M.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb3-13">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> fold(M <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> unfold(X, n), n, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(shape))</span></code></pre></div>
</details>
</div>
</section>
<section id="truncated-hosvd" class="level3" data-number="2.3">
<h3 data-number="2.3" class="anchored" data-anchor-id="truncated-hosvd"><span class="header-section-number">2.3</span> Truncated HOSVD</h3>
<p>Higher-order SVD (De Lathauwer, De Moor, Vandewalle 2000): one truncated SVD per unfolding; the core is <img src="https://latex.codecogs.com/png.latex?X"> projected onto those bases. The three truncations are not jointly optimal for the Tucker loss.</p>
<div id="ae04d696" class="cell" data-execution_count="4">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> truncated_hosvd(X, ranks):</span>
<span id="cb4-2">    factors <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb4-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> n, r <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(ranks):</span>
<span id="cb4-4">        u, _, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.svd(unfold(X, n), full_matrices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb4-5">        factors.append(u[:, :r])</span>
<span id="cb4-6">    core <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X</span>
<span id="cb4-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> n, U <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(factors):</span>
<span id="cb4-8">        core <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mode_prod(core, U.T, n)</span>
<span id="cb4-9">    recon <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> core</span>
<span id="cb4-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> n, U <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(factors):</span>
<span id="cb4-11">        recon <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mode_prod(recon, U, n)</span>
<span id="cb4-12">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> recon, core, factors</span>
<span id="cb4-13"></span>
<span id="cb4-14"></span>
<span id="cb4-15"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> hosvd_approx(tensor, ranks):</span>
<span id="cb4-16">    recon, _, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> truncated_hosvd(tensor, ranks)</span>
<span id="cb4-17">    n_params <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(</span>
<span id="cb4-18">        np.prod(ranks) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(s <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> r <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> s, r <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(tensor.shape, ranks))</span>
<span id="cb4-19">    )</span>
<span id="cb4-20">    err <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(np.linalg.norm(tensor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> recon) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> np.linalg.norm(tensor))</span>
<span id="cb4-21">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> recon, n_params, err</span>
<span id="cb4-22"></span>
<span id="cb4-23"></span>
<span id="cb4-24">X_hosvd, G_hosvd, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> truncated_hosvd(X, (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>))</span>
<span id="cb4-25"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"HOSVD core"</span>, G_hosvd.shape)</span>
<span id="cb4-26"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"HOSVD relative error </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>T<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>rel_fro(X, X_hosvd)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>HOSVD core (2, 2, 2)
HOSVD relative error 0.0619</code></pre>
</div>
</div>
</section>
<section id="tucker" class="level3" data-number="2.4">
<h3 data-number="2.4" class="anchored" data-anchor-id="tucker"><span class="header-section-number">2.4</span> Tucker</h3>
<p>HOOI starts from that HOSVD and cycles modes: contract the others, replace the factor by the leading left singular vectors (Tucker 1966). TensorLy’s <code>tucker(..., init="svd")</code> is this pipeline.</p>
<div id="cce848bd" class="cell" data-execution_count="5">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> tucker_als(X, ranks, n_iter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">15</span>):</span>
<span id="cb6-2">    _, _, factors <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> truncated_hosvd(X, ranks)</span>
<span id="cb6-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n_iter):</span>
<span id="cb6-4">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> n, r <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(ranks):</span>
<span id="cb6-5">            Y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X</span>
<span id="cb6-6">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> m, U <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(factors):</span>
<span id="cb6-7">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> n:</span>
<span id="cb6-8">                    Y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mode_prod(Y, U.T, m)</span>
<span id="cb6-9">            u, _, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.svd(unfold(Y, n), full_matrices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb6-10">            factors[n] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> u[:, :r]</span>
<span id="cb6-11">    core <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X</span>
<span id="cb6-12">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> n, U <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(factors):</span>
<span id="cb6-13">        core <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mode_prod(core, U.T, n)</span>
<span id="cb6-14">    recon <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> core</span>
<span id="cb6-15">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> n, U <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(factors):</span>
<span id="cb6-16">        recon <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mode_prod(recon, U, n)</span>
<span id="cb6-17">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> recon, core, factors</span>
<span id="cb6-18"></span>
<span id="cb6-19"></span>
<span id="cb6-20">X_tucker, G_tucker, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tucker_als(X, (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>))</span>
<span id="cb6-21"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Tucker core"</span>, G_tucker.shape)</span>
<span id="cb6-22"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Tucker relative error </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>T<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>rel_fro(X, X_tucker)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>Tucker core (2, 2, 2)
Tucker relative error 0.0619</code></pre>
</div>
</div>
</section>
<section id="cp" class="level3" data-number="2.5">
<h3 data-number="2.5" class="anchored" data-anchor-id="cp"><span class="header-section-number">2.5</span> CP</h3>
<p>CANDECOMP/PARAFAC writes <img src="https://latex.codecogs.com/png.latex?X"> as a sum of rank-1 outer products. ALS: fix every factor but one, solve a Khatri–Rao least-squares problem, cycle (Harshman 1970; Kolda and Bader 2009). CP rank can exceed a mode size; extra columns are random if the unfolding SVD runs out of vectors.</p>
<div id="1926c052" class="cell" data-execution_count="6">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> khatri_rao_except(factors, skip):</span>
<span id="cb8-2">    mats <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [F <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, F <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(factors) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> skip]</span>
<span id="cb8-3">    out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mats[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb8-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> F <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> mats[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:]:</span>
<span id="cb8-5">        out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.einsum(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ir,jr-&gt;ijr"</span>, out, F).reshape(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, out.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span>
<span id="cb8-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> out</span>
<span id="cb8-7"></span>
<span id="cb8-8"></span>
<span id="cb8-9"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> cp_reconstruct(factors):</span>
<span id="cb8-10">    subs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">","</span>.join(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">chr</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">105</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> n)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">r"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> n <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(factors)))</span>
<span id="cb8-11">    out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>.join(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">chr</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">105</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> n) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> n <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(factors)))</span>
<span id="cb8-12">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> np.einsum(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>subs<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">-&gt;</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>out<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>factors)</span>
<span id="cb8-13"></span>
<span id="cb8-14"></span>
<span id="cb8-15"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> cp_als(X, rank, n_iter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">50</span>, seed<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>):</span>
<span id="cb8-16">    rng <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.random.default_rng(seed)</span>
<span id="cb8-17">    factors <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb8-18">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> n <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(X.ndim):</span>
<span id="cb8-19">        u, _, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.svd(unfold(X, n), full_matrices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb8-20">        U <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rng.normal(size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(X.shape[n], rank))</span>
<span id="cb8-21">        keep <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(rank, u.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span>
<span id="cb8-22">        U[:, :keep] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> u[:, :keep]</span>
<span id="cb8-23">        factors.append(U)</span>
<span id="cb8-24">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n_iter):</span>
<span id="cb8-25">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> n <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(X.ndim):</span>
<span id="cb8-26">            gram <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.ones((rank, rank))</span>
<span id="cb8-27">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> m, F <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(factors):</span>
<span id="cb8-28">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> n:</span>
<span id="cb8-29">                    gram <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*=</span> F.T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> F</span>
<span id="cb8-30">            factors[n] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (</span>
<span id="cb8-31">                unfold(X, n) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> khatri_rao_except(factors, n) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> np.linalg.pinv(gram)</span>
<span id="cb8-32">            )</span>
<span id="cb8-33">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> n <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(X.ndim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb8-34">            scale <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.norm(factors[n], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, keepdims<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-12</span></span>
<span id="cb8-35">            factors[n] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/=</span> scale</span>
<span id="cb8-36">            factors[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*=</span> scale</span>
<span id="cb8-37">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> factors</span>
<span id="cb8-38"></span>
<span id="cb8-39"></span>
<span id="cb8-40">cp_factors <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cp_als(X, rank<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, n_iter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">40</span>)</span>
<span id="cb8-41">X_cp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cp_reconstruct(cp_factors)</span>
<span id="cb8-42"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"CP factor shapes"</span>, [F.shape <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> F <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> cp_factors])</span>
<span id="cb8-43"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"CP relative error </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>T<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>rel_fro(X, X_cp)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>CP factor shapes [(8, 2), (7, 2), (6, 2)]
CP relative error 0.0619</code></pre>
</div>
</div>
<p>Noise was added at <img src="https://latex.codecogs.com/png.latex?0.08"> times the clean scale. A residual near <img src="https://latex.codecogs.com/png.latex?0.06"> is that noise, not a missed component. The three fits land on the same floor because the cube <em>is</em> rank-2 CP.</p>
<div id="cell-fig-toy-factorizations" class="cell" data-fig-height="2.8" data-fig-width="10" data-execution_count="7">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1">k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span></span>
<span id="cb10-2">panels <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (</span>
<span id="cb10-3">    (X[:, :, k], <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"data $X$"</span>),</span>
<span id="cb10-4">    (X_hosvd[:, :, k], <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"HOSVD"</span>),</span>
<span id="cb10-5">    (X_tucker[:, :, k], <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Tucker"</span>),</span>
<span id="cb10-6">    (X_cp[:, :, k], <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"CP"</span>),</span>
<span id="cb10-7">)</span>
<span id="cb10-8">fig, axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.8</span>))</span>
<span id="cb10-9">vmax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(p[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>() <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> p <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> panels)</span>
<span id="cb10-10"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> ax, (sl, title) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(axes, panels):</span>
<span id="cb10-11">    ax.imshow(sl, origin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lower"</span>, cmap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"magma"</span>, vmin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, vmax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>vmax)</span>
<span id="cb10-12">    ax.set_title(title)</span>
<span id="cb10-13">    ax.set_xticks([])</span>
<span id="cb10-14">    ax.set_yticks([])</span>
<span id="cb10-15">fig.tight_layout()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-toy-factorizations" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-toy-factorizations-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/uses-of-tensor-factorizations/index_files/figure-html/fig-toy-factorizations-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-toy-factorizations-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: One slice of the <img src="https://latex.codecogs.com/png.latex?8%5Ctimes%207%5Ctimes%206"> cube (third mode, index 3). Left to right: data, truncated HOSVD, Tucker (HOOI), CP-ALS. Rank <img src="https://latex.codecogs.com/png.latex?(2,2,2)"> / CP rank <img src="https://latex.codecogs.com/png.latex?2">.
</figcaption>
</figure>
</div>
</div>
</div>
</section>
</section>
<section id="cp-convolution" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="cp-convolution"><span class="header-section-number">3</span> CP convolution</h2>
<p>VGG-16 (Simonyan and Zisserman 2015) uses <img src="https://latex.codecogs.com/png.latex?3%5Ctimes%203"> convolutions throughout. After the fourth pool the feature map is <img src="https://latex.codecogs.com/png.latex?14%5Ctimes%2014">. Each of <code>conv5_1</code>, <code>conv5_2</code>, <code>conv5_3</code> maps 512 channels to 512 channels.</p>
<p><strong>Before.</strong> That kernel is a 4-way array <img src="https://latex.codecogs.com/png.latex?3%5Ctimes%203%5Ctimes%20512%5Ctimes%20512">: one <img src="https://latex.codecogs.com/png.latex?3%5Ctimes%203"> patch for every input–output pair. Storage <img src="https://latex.codecogs.com/png.latex?3%5Ccdot%203%5Ccdot%20512%5Ccdot%20512=2%7B,%7D359%7B,%7D296"> weights. Every spatial site on the <img src="https://latex.codecogs.com/png.latex?14%5Ctimes%2014"> map pays that cost, so one forward pass is <img src="https://latex.codecogs.com/png.latex?9%5Ccdot%20512%5Ccdot%20512%5Ccdot%2014%5Ccdot%2014%5Capprox%20462"> million multiply-adds.</p>
<p><strong>What CP changes.</strong> Canonical polyadic decomposition writes that 4-D stack as a short sum of separable pieces. In the network that becomes four skinny convolutions in a row (Lebedev et al.&nbsp;2015):</p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?1%5Ctimes%201"> squeeze: 512 channels down to <img src="https://latex.codecogs.com/png.latex?R">.</li>
<li>Depthwise <img src="https://latex.codecogs.com/png.latex?3%5Ctimes%201">: smear vertically, one channel at a time.</li>
<li>Depthwise <img src="https://latex.codecogs.com/png.latex?1%5Ctimes%203">: smear horizontally.</li>
<li><img src="https://latex.codecogs.com/png.latex?1%5Ctimes%201"> expand: <img src="https://latex.codecogs.com/png.latex?R"> channels back to 512.</li>
</ul>
<p>Same <img src="https://latex.codecogs.com/png.latex?3%5Ctimes%203"> receptive field as VGG. Far fewer weights. Too small an <img src="https://latex.codecogs.com/png.latex?R"> mixes input channels that should stay separate — that shows up as relative error, not as a storage bug. The usual workflow is compress a trained net, then fine-tune.</p>
<p><strong>After.</strong> Rank <img src="https://latex.codecogs.com/png.latex?64"> stores <img src="https://latex.codecogs.com/png.latex?65%7B,%7D920"> weights — <img src="https://latex.codecogs.com/png.latex?35.8%5Ctimes"> fewer. Multiply-adds drop by the same factor, because the spatial size of the map cancels. Formula: <img src="https://latex.codecogs.com/png.latex?R(2d+C_%7B%5Cmathrm%7Bin%7D%7D+C_%7B%5Cmathrm%7Bout%7D%7D)"> against <img src="https://latex.codecogs.com/png.latex?d%5E%7B2%7DC_%7B%5Cmathrm%7Bin%7D%7DC_%7B%5Cmathrm%7Bout%7D%7D">.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AW_%7Bijkl%7D%5Capprox%5Csum_%7Br=1%7D%5E%7BR%7Da_%7Bir%7D%5C,b_%7Bjr%7D%5C,c_%7Bkr%7D%5C,d_%7Blr%7D.%0A"></p>
<p>Storage is exact. Fit quality is relative error <img src="https://latex.codecogs.com/png.latex?%5C%7CW-%5Chat%20W%5C%7C_F/%5C%7CW%5C%7C_F">: the leftover fraction of the kernel. A trained VGG layer is not exact CP, so some relative error remains even at rank <img src="https://latex.codecogs.com/png.latex?64">; that is why Lebedev et al.&nbsp;fine-tune.</p>
<p>The 512-channel drop is a closed-form count (no VGG is fitted). Toy relative error on a <img src="https://latex.codecogs.com/png.latex?3%5Ctimes%203%5Ctimes%2064%5Ctimes%2064"> kernel (true rank 16, noise <img src="https://latex.codecogs.com/png.latex?0.08">): rank 4 is <img src="https://latex.codecogs.com/png.latex?0.62">; rank 16 is <img src="https://latex.codecogs.com/png.latex?0.077"> (the noise); rank 64 is <img src="https://latex.codecogs.com/png.latex?0.066">. Rank 16 on the toy stores <img src="https://latex.codecogs.com/png.latex?2%7B,%7D144"> weights instead of <img src="https://latex.codecogs.com/png.latex?36%7B,%7D864"> (<img src="https://latex.codecogs.com/png.latex?17.2%5Ctimes">).</p>
<div id="28da9ee7" class="cell" data-execution_count="8">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1">W, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> T.make_cp_kernel(np.random.default_rng(T.SEED))</span>
<span id="cb11-2">W_hat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cp_reconstruct(cp_als(W, rank<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>, n_iter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">40</span>))</span>
<span id="cb11-3">n_cp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> T.cp_conv_params(W.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], W.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>], W.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>], <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>)</span>
<span id="cb11-4"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"kernel"</span>, W.shape)</span>
<span id="cb11-5"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(</span>
<span id="cb11-6">    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"CP rank 16: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>n_cp<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:,}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> / </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>W<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>size<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:,}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> weights "</span></span>
<span id="cb11-7">    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"(</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>W<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>size <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> n_cp<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">x), "</span></span>
<span id="cb11-8">    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"relative error </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>T<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>rel_fro(W, W_hat)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb11-9">)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>kernel (3, 3, 64, 64)
CP rank 16: 2,144 / 36,864 weights (17.2x), relative error 0.077</code></pre>
</div>
</div>
</section>
<section id="tt-matrix-dense-layers" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="tt-matrix-dense-layers"><span class="header-section-number">4</span> TT-matrix dense layers</h2>
<p>A transformer block at model dimension <img src="https://latex.codecogs.com/png.latex?4096"> stores square maps of that width: the output projection of multi-head attention <img src="https://latex.codecogs.com/png.latex?W_O%5Cin%5Cmathbb%7BR%7D%5E%7B4096%5Ctimes%204096%7D"> (Vaswani et al.&nbsp;2017). Novikov et al.&nbsp;(2015) write a dense matrix of this kind as a tensor-train.</p>
<p><strong>Before.</strong> Mapping 4096 numbers to 4096 numbers stores <img src="https://latex.codecogs.com/png.latex?4096%5E%7B2%7D=16%7B,%7D777%7B,%7D216"> weights. Multiplying a residual-stream vector by <img src="https://latex.codecogs.com/png.latex?W_O"> costs <img src="https://latex.codecogs.com/png.latex?O(N%5E%7B2%7D)">.</p>
<p><strong>What a TT-matrix changes.</strong> Factor <img src="https://latex.codecogs.com/png.latex?4096=8%5Ctimes%208%5Ctimes%208%5Ctimes%208">. Fold the rectangle into a higher-order array and write it as a chain of small cores <img src="https://latex.codecogs.com/png.latex?G_k%5Cin%5Cmathbb%7BR%7D%5E%7Br_%7Bk-1%7D%5Ctimes%208%5Ctimes%208%5Ctimes%20r_k%7D"> (Novikov et al.&nbsp;2015; Oseledets 2011). Multiplying a vector is a sweep along that chain, not one huge matmul. Storage at equal mode size <img src="https://latex.codecogs.com/png.latex?n"> and internal rank <img src="https://latex.codecogs.com/png.latex?r"> is <img src="https://latex.codecogs.com/png.latex?O(d%20n%5E%7B2%7D%20r%5E%7B2%7D)">, not <img src="https://latex.codecogs.com/png.latex?O(N%5E%7B2%7D)">.</p>
<p><strong>After.</strong> Rank <img src="https://latex.codecogs.com/png.latex?16"> stores <img src="https://latex.codecogs.com/png.latex?34%7B,%7D816"> weights — <img src="https://latex.codecogs.com/png.latex?481.9%5Ctimes"> fewer. Apply cost drops to <img src="https://latex.codecogs.com/png.latex?O(d%20r%5E%7B2%7D%20n%20N)">. Ordinary SVD of the unfolded rectangle cannot see that chain: a map that is low-rank <em>after folding</em> still looks high-rank as a matrix, so its relative error stays large.</p>
<p>The 4096-wide drop is closed-form (no transformer is fitted). Figure&nbsp;2 is both layers at those ranks. Toy relative error on a <img src="https://latex.codecogs.com/png.latex?256%5Ctimes%20256"> map built at TT-rank 4 plus noise <img src="https://latex.codecogs.com/png.latex?0.08">: TT-rank 4 is <img src="https://latex.codecogs.com/png.latex?0.079"> on <img src="https://latex.codecogs.com/png.latex?640"> weights; SVD rank 4 is <img src="https://latex.codecogs.com/png.latex?0.932"> on <img src="https://latex.codecogs.com/png.latex?2%7B,%7D052"> weights; SVD rank 64 is <img src="https://latex.codecogs.com/png.latex?0.436"> on <img src="https://latex.codecogs.com/png.latex?32%7B,%7D832"> weights.</p>
<div id="59934103" class="cell" data-execution_count="9">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1">ms <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ns <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [T.TT_MODE] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> T.TT_ORDER</span>
<span id="cb13-2">M, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> T.make_tt_matrix(np.random.default_rng(T.SEED), ms, ns)</span>
<span id="cb13-3">cores <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> T.tt_matrix_svd(M, ms, ns, max_rank<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>)</span>
<span id="cb13-4">M_tt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> T.tt_matrix_to_dense(cores)</span>
<span id="cb13-5"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"matrix"</span>, M.shape, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"TT cores"</span>, [c.shape <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> c <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> cores])</span>
<span id="cb13-6"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"TT-rank 4 relative error </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>T<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>rel_fro(M, M_tt)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>matrix (256, 256) TT cores [(1, 4, 4, 4), (4, 4, 4, 4), (4, 4, 4, 4), (4, 4, 4, 1)]
TT-rank 4 relative error 0.079</code></pre>
</div>
</div>
<div id="cell-fig-param-impact" class="cell" data-fig-height="3.8" data-fig-width="10" data-execution_count="10">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb15-1">fig, axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.8</span>))</span>
<span id="cb15-2">layer_impact(</span>
<span id="cb15-3">    axes,</span>
<span id="cb15-4">    [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"VGG-16 conv5"</span>, <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"Transformer $W_O$"</span>],</span>
<span id="cb15-5">    [CP[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"headline"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dense_params"</span>], TT[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"headline"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dense_params"</span>]],</span>
<span id="cb15-6">    [CP[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"headline"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cp_params"</span>], TT[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"headline"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tt_params"</span>]],</span>
<span id="cb15-7">    [ACCENT, TEAL],</span>
<span id="cb15-8">)</span>
<span id="cb15-9">fig.tight_layout()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-param-impact" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-param-impact-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/uses-of-tensor-factorizations/index_files/figure-html/fig-param-impact-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-param-impact-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: Closed-form storage at the ranks in the two sections. Left: weights, dense vs kept. Middle: compression (dense / kept). Right: fraction of the dense layer that remains. No network is fitted.
</figcaption>
</figure>
</div>
</div>
</div>
<p>The slider below varies rank. Relative error there is the toys.</p>
<div class="widget-container">
<div class="widget-header">
<p><span class="widget-title">Seven ranks — weights and relative error</span> <span class="widget-badge">runs in the browser</span></p>
</div>
<div id="tf-widget">

</div>
<div class="widget-note">
Seven ranks. Defaults are the ranks the toys were built at (CP 16, TT 4) — the gold band on the error plot. Weight bars are VGG-16 conv5 / transformer <img src="https://latex.codecogs.com/png.latex?W_O"> closed-form counts, not the toys.
</div>
</div>
</section>
<section id="complexity" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="complexity"><span class="header-section-number">5</span> Complexity</h2>
<p>The table answers two questions: how many numbers you store, and how much arithmetic one forward pass costs. <img src="https://latex.codecogs.com/png.latex?H,W"> are the spatial size of a feature map. <img src="https://latex.codecogs.com/png.latex?N=n%5E%7Bd%7D"> is one side of a square TT-matrix with <img src="https://latex.codecogs.com/png.latex?d"> equal modes. Apply is the thing you run at inference — a convolution, or <img src="https://latex.codecogs.com/png.latex?Wx">.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Method</th>
<th>Storage</th>
<th>Apply</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Dense conv</td>
<td><img src="https://latex.codecogs.com/png.latex?O(d%5E%7B2%7DC_%7B%5Cmathrm%7Bin%7D%7DC_%7B%5Cmathrm%7Bout%7D%7D)"></td>
<td><img src="https://latex.codecogs.com/png.latex?O(d%5E%7B2%7DC_%7B%5Cmathrm%7Bin%7D%7DC_%7B%5Cmathrm%7Bout%7D%7DHW)"></td>
</tr>
<tr class="even">
<td>CP-conv rank <img src="https://latex.codecogs.com/png.latex?R"></td>
<td><img src="https://latex.codecogs.com/png.latex?O(R(2d+C_%7B%5Cmathrm%7Bin%7D%7D+C_%7B%5Cmathrm%7Bout%7D%7D))"></td>
<td><img src="https://latex.codecogs.com/png.latex?O(R(C_%7B%5Cmathrm%7Bin%7D%7D+2d+C_%7B%5Cmathrm%7Bout%7D%7D)HW)"></td>
</tr>
<tr class="odd">
<td>Dense <img src="https://latex.codecogs.com/png.latex?M%5Ctimes%20N"></td>
<td><img src="https://latex.codecogs.com/png.latex?O(MN)"></td>
<td><img src="https://latex.codecogs.com/png.latex?O(MN)"></td>
</tr>
<tr class="even">
<td>SVD rank <img src="https://latex.codecogs.com/png.latex?k"></td>
<td><img src="https://latex.codecogs.com/png.latex?O(k(M+N))"></td>
<td><img src="https://latex.codecogs.com/png.latex?O(k(M+N))"></td>
</tr>
<tr class="odd">
<td>TT-matrix rank <img src="https://latex.codecogs.com/png.latex?r">, <img src="https://latex.codecogs.com/png.latex?d"> modes of size <img src="https://latex.codecogs.com/png.latex?n"></td>
<td><img src="https://latex.codecogs.com/png.latex?O(d%20n%5E%7B2%7D%20r%5E%7B2%7D)"></td>
<td><img src="https://latex.codecogs.com/png.latex?O(d%20r%5E%7B2%7D%20n%20N)"></td>
</tr>
</tbody>
</table>
<p>The VGG-16 <code>conv5</code> counts and the <img src="https://latex.codecogs.com/png.latex?4096"> transformer map are the closed-form counts from the two sections above. The clock below is NumPy on the toy 64-channel kernel and a <img src="https://latex.codecogs.com/png.latex?16%5Ctimes%2016"> map — not a cuDNN GEMM. Factorized is still fewer multiply-adds; wall-clock can go the other way on a GPU once the chain of small contractions becomes memory-bound.</p>
<div id="conv-timing" class="cell" data-execution_count="11">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb16-1">rng <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.random.default_rng(T.SEED)</span>
<span id="cb16-2">W, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> T.make_cp_kernel(rng)</span>
<span id="cb16-3">weights, factors <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> parafac(W, rank<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>, n_iter_max<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">40</span>, init<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"svd"</span>)</span>
<span id="cb16-4">X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rng.normal(size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">64</span>))</span>
<span id="cb16-5">d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span></span>
<span id="cb16-6">A_f, B_f, C_f, D_f <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> factors</span>
<span id="cb16-7"></span>
<span id="cb16-8"></span>
<span id="cb16-9"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> dense_conv():</span>
<span id="cb16-10">    out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">14</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">14</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">64</span>))</span>
<span id="cb16-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(d):</span>
<span id="cb16-12">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(d):</span>
<span id="cb16-13">            out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> X[i : i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">14</span>, j : j <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">14</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> W[i, j]</span>
<span id="cb16-14">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> out</span>
<span id="cb16-15"></span>
<span id="cb16-16"></span>
<span id="cb16-17"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> cp_conv():</span>
<span id="cb16-18">    z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> C_f</span>
<span id="cb16-19">    y1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">14</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>))</span>
<span id="cb16-20">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(d):</span>
<span id="cb16-21">        y1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> z[i : i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">14</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> A_f[i]</span>
<span id="cb16-22">    y2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">14</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">14</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>))</span>
<span id="cb16-23">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(d):</span>
<span id="cb16-24">        y2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> y1[:, j : j <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">14</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> B_f[j]</span>
<span id="cb16-25">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> y2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> D_f.T</span>
<span id="cb16-26"></span>
<span id="cb16-27"></span>
<span id="cb16-28">yd, yc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dense_conv(), cp_conv()</span>
<span id="cb16-29">t_dense <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> bench(dense_conv)</span>
<span id="cb16-30">t_cp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> bench(cp_conv)</span>
<span id="cb16-31"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(</span>
<span id="cb16-32">    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"conv rel error </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>norm(yd <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> yc) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>norm(yd)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">; "</span></span>
<span id="cb16-33">    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"dense </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>t_dense <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e3</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> ms; CP </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>t_cp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e3</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> ms; "</span></span>
<span id="cb16-34">    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"speedup </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>t_dense <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> t_cp<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">x"</span></span>
<span id="cb16-35">)</span>
<span id="cb16-36"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(</span>
<span id="cb16-37">    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"headline MACs 512-ch 14x14: dense </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">512</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">512</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">196</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:,}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> vs "</span></span>
<span id="cb16-38">    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"CP </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">64</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">512</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">512</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">196</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:,}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> (</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">512</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">512</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">64</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1030</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">x)"</span></span>
<span id="cb16-39">)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>conv rel error 0.0948; dense 0.13 ms; CP 0.05 ms; speedup 2.7x
headline MACs 512-ch 14x14: dense 462,422,016 vs CP 12,920,320 (35.8x)</code></pre>
</div>
</div>
<section id="a-film-clip" class="level3" data-number="5.1">
<h3 data-number="5.1" class="anchored" data-anchor-id="a-film-clip"><span class="header-section-number">5.1</span> A film clip</h3>
<p>A pixel is three numbers: red, green, blue. Height and width stack those triples into an image. Colour is a third mode of the array; time is a fourth. The clip is those four modes in one tensor.</p>
<div id="cell-fig-tensor-containers" class="cell" data-fig-height="4.15" data-fig-width="12.2" data-execution_count="13">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb18-1">CHAN <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#C0392B"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#1E8449"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#2471A3"</span>]</span>
<span id="cb18-2">rh, rw <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span></span>
<span id="cb18-3">sat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> still.astype(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>).std(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb18-4">best, pos <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb18-5"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, still.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> rh, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>):</span>
<span id="cb18-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, still.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> rw, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>):</span>
<span id="cb18-7">        score <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(sat[i : i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> rh, j : j <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> rw].mean())</span>
<span id="cb18-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> score <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> best:</span>
<span id="cb18-9">            best, pos <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> score, (i, j)</span>
<span id="cb18-10">r0, c0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pos</span>
<span id="cb18-11">pix <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> still[r0 : r0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> rh, c0 : c0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> rw]</span>
<span id="cb18-12">crop_box <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (c0, r0, rw, rh)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># x, y, w, h in image coords</span></span>
<span id="cb18-13">H_img, W_img <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> still.shape[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]</span>
<span id="cb18-14">n_t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> frames.shape[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb18-15">t_idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, n_t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, n_t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb18-16"></span>
<span id="cb18-17">fig <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.figure(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12.2</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.15</span>))</span>
<span id="cb18-18">gs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fig.add_gridspec(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, width_ratios<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.05</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.12</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.22</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.55</span>], wspace<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.22</span>)</span>
<span id="cb18-19"></span>
<span id="cb18-20">ax0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fig.add_subplot(gs[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span>
<span id="cb18-21">ax0.imshow(pix.astype(np.uint8), interpolation<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"nearest"</span>)</span>
<span id="cb18-22">ny, nx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pix.shape[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]</span>
<span id="cb18-23">ax0.set_xticks(np.arange(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, nx, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), minor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb18-24">ax0.set_yticks(np.arange(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, ny, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), minor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb18-25">ax0.grid(which<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"minor"</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"white"</span>, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.55</span>)</span>
<span id="cb18-26">ax0.tick_params(which<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"both"</span>, bottom<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, left<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, labelbottom<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, labelleft<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb18-27">ax0.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pixels"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">11</span>, pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb18-28">ax0.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"each cell is (R, G, B)"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#5F6672"</span>)</span>
<span id="cb18-29"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> spine <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> ax0.spines.values():</span>
<span id="cb18-30">    spine.set_color(INK)</span>
<span id="cb18-31">    spine.set_linewidth(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>)</span>
<span id="cb18-32"></span>
<span id="cb18-33">ax1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fig.add_subplot(gs[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span>
<span id="cb18-34">ax1.imshow(still.astype(np.uint8))</span>
<span id="cb18-35">ax1.add_patch(</span>
<span id="cb18-36">    Rectangle(</span>
<span id="cb18-37">        (crop_box[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, crop_box[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>),</span>
<span id="cb18-38">        crop_box[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>],</span>
<span id="cb18-39">        crop_box[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>],</span>
<span id="cb18-40">        fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>,</span>
<span id="cb18-41">        edgecolor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>CORAL,</span>
<span id="cb18-42">        lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.4</span>,</span>
<span id="cb18-43">    )</span>
<span id="cb18-44">)</span>
<span id="cb18-45">ax1.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"image"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">11</span>, pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb18-46">ax1.tick_params(bottom<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, left<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, labelbottom<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, labelleft<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb18-47">ax1.annotate(</span>
<span id="cb18-48">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>,</span>
<span id="cb18-49">    xy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.04</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span>),</span>
<span id="cb18-50">    xytext<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.04</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.98</span>),</span>
<span id="cb18-51">    xycoords<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"axes fraction"</span>,</span>
<span id="cb18-52">    textcoords<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"axes fraction"</span>,</span>
<span id="cb18-53">    arrowprops<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(arrowstyle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&lt;-&gt;"</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ACCENT, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.4</span>),</span>
<span id="cb18-54">)</span>
<span id="cb18-55">ax1.text(</span>
<span id="cb18-56">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.10</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"height"</span>, rotation<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">90</span>, va<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"center"</span>, ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"center"</span>,</span>
<span id="cb18-57">    color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ACCENT, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, transform<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ax1.transAxes,</span>
<span id="cb18-58">)</span>
<span id="cb18-59">ax1.annotate(</span>
<span id="cb18-60">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>,</span>
<span id="cb18-61">    xy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.04</span>),</span>
<span id="cb18-62">    xytext<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.98</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.04</span>),</span>
<span id="cb18-63">    xycoords<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"axes fraction"</span>,</span>
<span id="cb18-64">    textcoords<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"axes fraction"</span>,</span>
<span id="cb18-65">    arrowprops<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(arrowstyle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&lt;-&gt;"</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>TEAL, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.4</span>),</span>
<span id="cb18-66">)</span>
<span id="cb18-67">ax1.text(</span>
<span id="cb18-68">    <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.12</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"width"</span>, ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"center"</span>, va<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top"</span>,</span>
<span id="cb18-69">    color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>TEAL, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, transform<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ax1.transAxes,</span>
<span id="cb18-70">)</span>
<span id="cb18-71">ax1.text(</span>
<span id="cb18-72">    <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.22</span>, <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">rf"$H \times W = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>H_img<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">\times </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>W_img<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">$"</span>,</span>
<span id="cb18-73">    ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"center"</span>, va<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#5F6672"</span>,</span>
<span id="cb18-74">    transform<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ax1.transAxes,</span>
<span id="cb18-75">)</span>
<span id="cb18-76"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> spine <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> ax1.spines.values():</span>
<span id="cb18-77">    spine.set_visible(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb18-78"></span>
<span id="cb18-79">ax2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fig.add_subplot(gs[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>])</span>
<span id="cb18-80">ax2.set_xlim(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.55</span>)</span>
<span id="cb18-81">ax2.set_ylim(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.35</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.35</span>)</span>
<span id="cb18-82">ax2.set_aspect(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"equal"</span>)</span>
<span id="cb18-83">ax2.axis(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"off"</span>)</span>
<span id="cb18-84">ax2.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"3-tensor"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">11</span>, pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb18-85">c2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> draw_mode_cube(</span>
<span id="cb18-86">    ax2, origin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>np.array([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.22</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.28</span>]), image<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>still,</span>
<span id="cb18-87">    size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.55</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.55</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.42</span>), depth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.42</span>, scale<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, chan_colors<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>CHAN,</span>
<span id="cb18-88">)</span>
<span id="cb18-89">dim_arrow(ax2, c2[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bl"</span>], c2[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"br"</span>], <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"width"</span>, TEAL, outward<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>)</span>
<span id="cb18-90">dim_arrow(ax2, c2[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bl"</span>], c2[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tl"</span>], <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"height"</span>, ACCENT, outward<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>)</span>
<span id="cb18-91">dim_arrow(ax2, c2[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"br"</span>], c2[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"brz"</span>], <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"color"</span>, CORAL, outward<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.15</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb18-92">ax2.text(</span>
<span id="cb18-93">    <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.28</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.28</span>, <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"$H \times W \times C$"</span>,</span>
<span id="cb18-94">    ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"center"</span>, va<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#5F6672"</span>,</span>
<span id="cb18-95">)</span>
<span id="cb18-96"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, lab <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"RGB"</span>):</span>
<span id="cb18-97">    ax2.text(</span>
<span id="cb18-98">        c2[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"brz"</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.10</span>,</span>
<span id="cb18-99">        c2[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"br"</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (c2[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tr"</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> c2[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"br"</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.18</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.32</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> i),</span>
<span id="cb18-100">        lab,</span>
<span id="cb18-101">        color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>CHAN[i],</span>
<span id="cb18-102">        fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>,</span>
<span id="cb18-103">        ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"left"</span>,</span>
<span id="cb18-104">        va<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"center"</span>,</span>
<span id="cb18-105">        fontweight<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bold"</span>,</span>
<span id="cb18-106">    )</span>
<span id="cb18-107"></span>
<span id="cb18-108">ax3 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fig.add_subplot(gs[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>])</span>
<span id="cb18-109">ax3.set_xlim(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.35</span>)</span>
<span id="cb18-110">ax3.set_ylim(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.45</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.58</span>)</span>
<span id="cb18-111">ax3.set_aspect(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"equal"</span>)</span>
<span id="cb18-112">ax3.axis(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"off"</span>)</span>
<span id="cb18-113">ax3.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"4-tensor"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">11</span>, pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb18-114">origins <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [np.array([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.12</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.18</span>]), np.array([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.78</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.46</span>]), np.array([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.44</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.74</span>])]</span>
<span id="cb18-115">cubes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span></span>
<span id="cb18-116"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">reversed</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)):</span>
<span id="cb18-117">    cubes[i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> draw_mode_cube(</span>
<span id="cb18-118">        ax3, origin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>origins[i], image<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>frames[:, :, :, t_idx[i]],</span>
<span id="cb18-119">        size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.05</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.05</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.28</span>), depth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.28</span>, scale<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, chan_colors<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>CHAN,</span>
<span id="cb18-120">    )</span>
<span id="cb18-121">c0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cubes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb18-122">dim_arrow(ax3, c0[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bl"</span>], c0[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"br"</span>], <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"width"</span>, TEAL, outward<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.05</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb18-123">dim_arrow(ax3, c0[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bl"</span>], c0[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tl"</span>], <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"height"</span>, ACCENT, outward<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.05</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb18-124">dim_arrow(ax3, c0[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"br"</span>], c0[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"brz"</span>], <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"color"</span>, CORAL, outward<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.2</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb18-125">ax3.annotate(</span>
<span id="cb18-126">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>,</span>
<span id="cb18-127">    xy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>cubes[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"trz"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> np.array([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.08</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.12</span>]),</span>
<span id="cb18-128">    xytext<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>c0[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tlz"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> np.array([<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.12</span>]),</span>
<span id="cb18-129">    arrowprops<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(arrowstyle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-&gt;"</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>GOLD, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.45</span>),</span>
<span id="cb18-130">)</span>
<span id="cb18-131">ax3.text(</span>
<span id="cb18-132">    <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.95</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.22</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"time"</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>GOLD, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"center"</span>, va<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bottom"</span>,</span>
<span id="cb18-133">)</span>
<span id="cb18-134"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> cube, t <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(cubes, t_idx):</span>
<span id="cb18-135">    ax3.text(</span>
<span id="cb18-136">        <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (cube[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tl"</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> cube[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tr"</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]),</span>
<span id="cb18-137">        cube[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tlz"</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.10</span>,</span>
<span id="cb18-138">        <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"t = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>t<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>,</span>
<span id="cb18-139">        fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>,</span>
<span id="cb18-140">        color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#5F6672"</span>,</span>
<span id="cb18-141">        ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"center"</span>,</span>
<span id="cb18-142">        va<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bottom"</span>,</span>
<span id="cb18-143">    )</span>
<span id="cb18-144">ax3.text(</span>
<span id="cb18-145">    <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.65</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.38</span>, <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"$H \times W \times C \times T$"</span>,</span>
<span id="cb18-146">    ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"center"</span>, va<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#5F6672"</span>,</span>
<span id="cb18-147">)</span>
<span id="cb18-148">ax3.text(</span>
<span id="cb18-149">    <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.65</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.58</span>, <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">rf"$120\times 160\times 3\times </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>n_t<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">$"</span>,</span>
<span id="cb18-150">    ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"center"</span>, va<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#5F6672"</span>,</span>
<span id="cb18-151">)</span>
<span id="cb18-152"></span>
<span id="cb18-153">fig.subplots_adjust(left<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.04</span>, right<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.99</span>, top<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.86</span>, bottom<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.16</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-tensor-containers" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-tensor-containers-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/uses-of-tensor-factorizations/index_files/figure-html/fig-tensor-containers-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-tensor-containers-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: Pixel triples stacked into tensor containers. Height and width make an image; colour is a third mode; time stacks frames into a 4-tensor.
</figcaption>
</figure>
</div>
</div>
</div>
<p>The source clip is in Tensor data: 5 s, picture and sound. Truncated HOSVD (one SVD per mode, no HOOI) compresses the still, the RGB video, and the soundtrack STFT. Ranks target relative error about <img src="https://latex.codecogs.com/png.latex?0.05">. Reconstructed picture and sound are muxed into <code>media/clip-hosvd.mp4</code>. It is the same <code>truncated_hosvd</code> as in Factorizations.</p>
<div id="20c36afb" class="cell" data-execution_count="14">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb19-1">IMG_RANKS <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">28</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb19-2">VID_RANKS <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">80</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">30</span>)</span>
<span id="cb19-3">AUD_RANKS <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">88</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">140</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb19-4">still_hat, still_core, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> truncated_hosvd(still, IMG_RANKS)</span>
<span id="cb19-5"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"still"</span>, still.shape, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"→ core"</span>, still_core.shape)</span>
<span id="cb19-6"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"still relative error </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>T<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>rel_fro(still, still_hat)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>still (240, 320, 3) → core (20, 28, 3)
still relative error 0.049</code></pre>
</div>
</div>
</section>
<section id="video" class="level3" data-number="5.2">
<h3 data-number="5.2" class="anchored" data-anchor-id="video"><span class="header-section-number">5.2</span> Video</h3>
<p>Still <img src="https://latex.codecogs.com/png.latex?240%5Ctimes%20320%5Ctimes%203">, ranks <img src="https://latex.codecogs.com/png.latex?(20,28,3)">. Clip <img src="https://latex.codecogs.com/png.latex?120%5Ctimes%20160%5Ctimes%203%5Ctimes%2060">, ranks <img src="https://latex.codecogs.com/png.latex?(60,80,3,30)">. Time is its own mode, so motion is not smeared into space.</p>
<div id="cell-fig-film-video-stats" class="cell" data-fig-height="3.6" data-fig-width="10" data-execution_count="16">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb21" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb21-1">fig, axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.6</span>))</span>
<span id="cb21-2">clip_tradeoff(</span>
<span id="cb21-3">    axes,</span>
<span id="cb21-4">    [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Still"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Video"</span>],</span>
<span id="cb21-5">    [still.size, frames.size],</span>
<span id="cb21-6">    [img_params, vid_params],</span>
<span id="cb21-7">    [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Still"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Video"</span>],</span>
<span id="cb21-8">    [img_err, vid_err],</span>
<span id="cb21-9">    ACCENT,</span>
<span id="cb21-10">)</span>
<span id="cb21-11">fig.tight_layout()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-film-video-stats" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-film-video-stats-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/uses-of-tensor-factorizations/index_files/figure-html/fig-film-video-stats-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-film-video-stats-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;4: Still and video after HOSVD. Left: numbers stored, dense vs kept. Middle: compression (dense / kept). Right: relative error.
</figcaption>
</figure>
</div>
</div>
</div>
<ul>
<li>Still: <img src="https://latex.codecogs.com/png.latex?15,449"> / <img src="https://latex.codecogs.com/png.latex?230,400"> numbers (<img src="https://latex.codecogs.com/png.latex?14.9%5Ctimes">), relative error <img src="https://latex.codecogs.com/png.latex?0.049">.</li>
<li>Video: <img src="https://latex.codecogs.com/png.latex?453,809"> / <img src="https://latex.codecogs.com/png.latex?3,456,000"> (<img src="https://latex.codecogs.com/png.latex?7.6%5Ctimes">), relative error <img src="https://latex.codecogs.com/png.latex?0.049">.</li>
</ul>
<div id="cell-fig-film-still" class="cell" data-fig-height="3.8" data-fig-width="10" data-execution_count="18">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb22" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb22-1">fig, axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.8</span>))</span>
<span id="cb22-2"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> ax, arr, title <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (</span>
<span id="cb22-3">    (axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], still, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"original"</span>),</span>
<span id="cb22-4">    (axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], np.clip(img_hat, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">255</span>), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"compressed"</span>),</span>
<span id="cb22-5">):</span>
<span id="cb22-6">    ax.imshow(arr.astype(np.uint8))</span>
<span id="cb22-7">    ax.set_title(title)</span>
<span id="cb22-8">    ax.set_axis_off()</span>
<span id="cb22-9">fig.tight_layout()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-film-still" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-film-still-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/uses-of-tensor-factorizations/index_files/figure-html/fig-film-still-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-film-still-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;5: One trailer frame. Left: original. Right: compressed (keep 20 height pieces, 28 width pieces, and all 3 colour channels).
</figcaption>
</figure>
</div>
</div>
</div>
<p>Original 5 s clip (picture and sound):</p>
<video controls="" width="480" src="media/clip.mp4">
</video>
<p>After HOSVD. Video relative error <img src="https://latex.codecogs.com/png.latex?0.049">.</p>
<video controls="" width="480" src="media/clip-hosvd.mp4">
</video>
<div id="cell-fig-film-video" class="cell" data-fig-height="4.4" data-fig-width="10" data-execution_count="20">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb23" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb23-1">n_t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> frames.shape[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb23-2">idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, n_t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, n_t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb23-3">fig, axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.4</span>))</span>
<span id="cb23-4"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j, t <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(idx):</span>
<span id="cb23-5">    axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, j].imshow(np.clip(frames[:, :, :, t], <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">255</span>).astype(np.uint8))</span>
<span id="cb23-6">    axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j].imshow(np.clip(vid_hat[:, :, :, t], <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">255</span>).astype(np.uint8))</span>
<span id="cb23-7">    axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, j].set_title(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"original, frame </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>t<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb23-8">    axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j].set_title(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"compressed, frame </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>t<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb23-9">    axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, j].set_axis_off()</span>
<span id="cb23-10">    axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j].set_axis_off()</span>
<span id="cb23-11">fig.tight_layout()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-film-video" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-film-video-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/uses-of-tensor-factorizations/index_files/figure-html/fig-film-video-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-film-video-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;6: Three frames from the 5 s clip. Top: original. Bottom: compressed. Time is kept as its own axis, so motion is not smeared into space.
</figcaption>
</figure>
</div>
</div>
</div>
</section>
<section id="audio" class="level3" data-number="5.3">
<h3 data-number="5.3" class="anchored" data-anchor-id="audio"><span class="header-section-number">5.3</span> Audio</h3>
<p>A microphone stores air pressure as one number per sample. That list is a 1-tensor: one mode, time. Factorizing it is a 1-D SVD — there is no second mode to separate.</p>
<p>The short-time Fourier transform (STFT) cuts the list into overlapping windows and writes each window as a spectrum. Stack the spectra along time. The array is frequency <img src="https://latex.codecogs.com/png.latex?%5Ctimes"> time, a 2-tensor — the same layout as a greyscale image. Each bin is complex, so split real and imaginary into a third mode. Frequency <img src="https://latex.codecogs.com/png.latex?%5Ctimes"> time <img src="https://latex.codecogs.com/png.latex?%5Ctimes"> {real, imag} is a 3-tensor, the same layout as an RGB still.</p>
<p>This clip: 5 s mono at 8 kHz. Window <img src="https://latex.codecogs.com/png.latex?n=256">, hop <img src="https://latex.codecogs.com/png.latex?128">. Truncated HOSVD ranks <img src="https://latex.codecogs.com/png.latex?(88,140,2)">.</p>
<ul>
<li><strong>samples</strong> — each cell is one pressure number.</li>
<li><strong>1-tensor</strong> — the waveform. Overlapping windows are the STFT cuts.</li>
<li><strong>2-tensor</strong> — those spectra stacked: frequency <img src="https://latex.codecogs.com/png.latex?%5Ctimes"> time.</li>
<li><strong>3-tensor</strong> — add {real, imag}, the way colour is added to an image.</li>
</ul>
<div id="cell-fig-audio-containers" class="cell" data-fig-height="4.2" data-fig-width="12.2" data-execution_count="22">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb24" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb24-1">NPER <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">256</span></span>
<span id="cb24-2">HOP <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> NPER <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb24-3">peak <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(np.argmax(np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(wav)))</span>
<span id="cb24-4">n_show <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span></span>
<span id="cb24-5">i_samp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, peak <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> n_show <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb24-6">samps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> wav[i_samp : i_samp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> n_show]</span>
<span id="cb24-7">view <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.28</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> sr)</span>
<span id="cb24-8">i0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, peak <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> view <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb24-9">i1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(wav.size, i0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> view)</span>
<span id="cb24-10">win_starts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb24-11">    s <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> s <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, wav.size <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> NPER <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, HOP)</span>
<span id="cb24-12">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> s <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> NPER <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> i0 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> s <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> i1</span>
<span id="cb24-13">][:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>]</span>
<span id="cb24-14">win_cols <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [ACCENT, TEAL, CORAL, GOLD, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#5F6672"</span>]</span>
<span id="cb24-15">REIM <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [CORAL, TEAL]</span>
<span id="cb24-16">spec_img <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> spec_to_rgb(Z)</span>
<span id="cb24-17"></span>
<span id="cb24-18">fig <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.figure(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12.2</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.2</span>))</span>
<span id="cb24-19">gs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fig.add_gridspec(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, width_ratios<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.02</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.18</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.22</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.45</span>], wspace<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.28</span>)</span>
<span id="cb24-20"></span>
<span id="cb24-21">ax0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fig.add_subplot(gs[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span>
<span id="cb24-22">vmax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(samps))) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-9</span></span>
<span id="cb24-23">ax0.imshow(</span>
<span id="cb24-24">    samps[np.newaxis, :],</span>
<span id="cb24-25">    cmap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"coolwarm"</span>,</span>
<span id="cb24-26">    vmin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span>vmax,</span>
<span id="cb24-27">    vmax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>vmax,</span>
<span id="cb24-28">    interpolation<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"nearest"</span>,</span>
<span id="cb24-29">    aspect<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"auto"</span>,</span>
<span id="cb24-30">)</span>
<span id="cb24-31">ax0.set_xticks(np.arange(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, n_show, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), minor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb24-32">ax0.set_yticks([<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>], minor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb24-33">ax0.grid(which<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"minor"</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"white"</span>, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span>)</span>
<span id="cb24-34">ax0.tick_params(which<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"both"</span>, bottom<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, left<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, labelbottom<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, labelleft<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb24-35">ax0.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"samples"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">11</span>, pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb24-36">ax0.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"each cell is one number"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#5F6672"</span>)</span>
<span id="cb24-37"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> spine <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> ax0.spines.values():</span>
<span id="cb24-38">    spine.set_color(INK)</span>
<span id="cb24-39">    spine.set_linewidth(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>)</span>
<span id="cb24-40"></span>
<span id="cb24-41">ax1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fig.add_subplot(gs[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span>
<span id="cb24-42">tt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.arange(i0, i1) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> sr</span>
<span id="cb24-43">ax1.plot(tt, wav[i0:i1], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>INK, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.05</span>, zorder<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb24-44"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> k, s <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(win_starts):</span>
<span id="cb24-45">    ax1.axvspan(</span>
<span id="cb24-46">        s <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> sr, (s <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> NPER) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> sr,</span>
<span id="cb24-47">        color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>win_cols[k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(win_cols)], alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.22</span>, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, zorder<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,</span>
<span id="cb24-48">    )</span>
<span id="cb24-49">ax1.axvline(i_samp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> sr, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>CORAL, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, ls<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">":"</span>, zorder<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>)</span>
<span id="cb24-50">ax1.axvline((i_samp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> n_show) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> sr, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>CORAL, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, ls<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">":"</span>, zorder<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>)</span>
<span id="cb24-51">ax1.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1-tensor"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">11</span>, pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb24-52">ax1.tick_params(length<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb24-53">ax1.set_yticks([])</span>
<span id="cb24-54">ax1.annotate(</span>
<span id="cb24-55">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>,</span>
<span id="cb24-56">    xy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.08</span>),</span>
<span id="cb24-57">    xytext<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.98</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.08</span>),</span>
<span id="cb24-58">    xycoords<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"axes fraction"</span>,</span>
<span id="cb24-59">    textcoords<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"axes fraction"</span>,</span>
<span id="cb24-60">    arrowprops<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(arrowstyle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&lt;-&gt;"</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>TEAL, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.4</span>),</span>
<span id="cb24-61">)</span>
<span id="cb24-62">ax1.text(</span>
<span id="cb24-63">    <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.18</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"time"</span>, ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"center"</span>, va<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top"</span>,</span>
<span id="cb24-64">    color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>TEAL, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, transform<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ax1.transAxes,</span>
<span id="cb24-65">)</span>
<span id="cb24-66">ax1.text(</span>
<span id="cb24-67">    <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.30</span>, <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">rf"$N = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>wav<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>size<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">$ samples"</span>,</span>
<span id="cb24-68">    ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"center"</span>, va<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#5F6672"</span>,</span>
<span id="cb24-69">    transform<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ax1.transAxes,</span>
<span id="cb24-70">)</span>
<span id="cb24-71"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> spine <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> ax1.spines.values():</span>
<span id="cb24-72">    spine.set_visible(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb24-73">ax1.set_xlim(tt[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], tt[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span>
<span id="cb24-74"></span>
<span id="cb24-75">ax2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fig.add_subplot(gs[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>])</span>
<span id="cb24-76">ax2.pcolormesh(</span>
<span id="cb24-77">    stft_times, stft_freq, spec_db(Z),</span>
<span id="cb24-78">    cmap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"magma"</span>, shading<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"auto"</span>,</span>
<span id="cb24-79">)</span>
<span id="cb24-80">ax2.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2-tensor"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">11</span>, pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb24-81">ax2.tick_params(length<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, labelsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb24-82">ax2.set_xticks([])</span>
<span id="cb24-83">ax2.set_yticks([])</span>
<span id="cb24-84">ax2.annotate(</span>
<span id="cb24-85">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>,</span>
<span id="cb24-86">    xy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.04</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span>),</span>
<span id="cb24-87">    xytext<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.04</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.98</span>),</span>
<span id="cb24-88">    xycoords<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"axes fraction"</span>,</span>
<span id="cb24-89">    textcoords<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"axes fraction"</span>,</span>
<span id="cb24-90">    arrowprops<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(arrowstyle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&lt;-&gt;"</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ACCENT, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.4</span>),</span>
<span id="cb24-91">)</span>
<span id="cb24-92">ax2.text(</span>
<span id="cb24-93">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.12</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"frequency"</span>, rotation<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">90</span>, va<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"center"</span>, ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"center"</span>,</span>
<span id="cb24-94">    color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ACCENT, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, transform<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ax2.transAxes,</span>
<span id="cb24-95">)</span>
<span id="cb24-96">ax2.annotate(</span>
<span id="cb24-97">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>,</span>
<span id="cb24-98">    xy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.04</span>),</span>
<span id="cb24-99">    xytext<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.98</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.04</span>),</span>
<span id="cb24-100">    xycoords<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"axes fraction"</span>,</span>
<span id="cb24-101">    textcoords<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"axes fraction"</span>,</span>
<span id="cb24-102">    arrowprops<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(arrowstyle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&lt;-&gt;"</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>TEAL, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.4</span>),</span>
<span id="cb24-103">)</span>
<span id="cb24-104">ax2.text(</span>
<span id="cb24-105">    <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.14</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"time"</span>, ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"center"</span>, va<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top"</span>,</span>
<span id="cb24-106">    color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>TEAL, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, transform<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ax2.transAxes,</span>
<span id="cb24-107">)</span>
<span id="cb24-108">ax2.text(</span>
<span id="cb24-109">    <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.26</span>, <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">rf"$F \times T = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>Z<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">\times </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>Z<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">$"</span>,</span>
<span id="cb24-110">    ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"center"</span>, va<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#5F6672"</span>,</span>
<span id="cb24-111">    transform<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ax2.transAxes,</span>
<span id="cb24-112">)</span>
<span id="cb24-113"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> spine <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> ax2.spines.values():</span>
<span id="cb24-114">    spine.set_visible(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb24-115"></span>
<span id="cb24-116">ax3 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fig.add_subplot(gs[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>])</span>
<span id="cb24-117">ax3.set_xlim(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.55</span>)</span>
<span id="cb24-118">ax3.set_ylim(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.40</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.38</span>)</span>
<span id="cb24-119">ax3.set_aspect(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"equal"</span>)</span>
<span id="cb24-120">ax3.axis(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"off"</span>)</span>
<span id="cb24-121">ax3.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"3-tensor"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">11</span>, pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb24-122">c3 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> draw_mode_cube(</span>
<span id="cb24-123">    ax3, origin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>np.array([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.22</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.32</span>]), image<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>spec_img,</span>
<span id="cb24-124">    size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.55</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.55</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.38</span>), depth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.38</span>, scale<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, chan_colors<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>REIM,</span>
<span id="cb24-125">)</span>
<span id="cb24-126">dim_arrow(ax3, c3[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bl"</span>], c3[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"br"</span>], <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"time"</span>, TEAL, outward<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>)</span>
<span id="cb24-127">dim_arrow(ax3, c3[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bl"</span>], c3[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tl"</span>], <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"frequency"</span>, ACCENT, outward<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>)</span>
<span id="cb24-128">dim_arrow(ax3, c3[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"br"</span>], c3[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"brz"</span>], <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"real / imag"</span>, CORAL, outward<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.2</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb24-129">ax3.text(</span>
<span id="cb24-130">    <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.28</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.32</span>, <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"$F \times T \times 2$"</span>,</span>
<span id="cb24-131">    ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"center"</span>, va<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#5F6672"</span>,</span>
<span id="cb24-132">)</span>
<span id="cb24-133"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, lab <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>((<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Re"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Im"</span>)):</span>
<span id="cb24-134">    ax3.text(</span>
<span id="cb24-135">        c3[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"brz"</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.10</span>,</span>
<span id="cb24-136">        c3[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"br"</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (c3[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tr"</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> c3[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"br"</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.28</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.44</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> i),</span>
<span id="cb24-137">        lab,</span>
<span id="cb24-138">        color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>REIM[i],</span>
<span id="cb24-139">        fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>,</span>
<span id="cb24-140">        ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"left"</span>,</span>
<span id="cb24-141">        va<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"center"</span>,</span>
<span id="cb24-142">        fontweight<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bold"</span>,</span>
<span id="cb24-143">    )</span>
<span id="cb24-144"></span>
<span id="cb24-145">fig.subplots_adjust(left<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</span>, right<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.99</span>, top<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.86</span>, bottom<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.18</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-audio-containers" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-audio-containers-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/uses-of-tensor-factorizations/index_files/figure-html/fig-audio-containers-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-audio-containers-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;7: Pressure samples stacked into tensor containers. The waveform is a 1-tensor. The STFT makes a 2-tensor (frequency × time). Real and imaginary parts are a third mode, as colour is for an image.
</figcaption>
</figure>
</div>
</div>
</div>
<div id="cell-fig-film-audio-stats" class="cell" data-fig-height="3.6" data-fig-width="10" data-execution_count="23">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb25" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb25-1">fig, axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.6</span>))</span>
<span id="cb25-2">clip_tradeoff(</span>
<span id="cb25-3">    axes,</span>
<span id="cb25-4">    [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"STFT"</span>],</span>
<span id="cb25-5">    [stft_t.size],</span>
<span id="cb25-6">    [aud_params],</span>
<span id="cb25-7">    [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"STFT tensor"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Complex"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Waveform"</span>],</span>
<span id="cb25-8">    [aud_err[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"stft_tensor"</span>], aud_err[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"stft_complex"</span>], aud_err[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"waveform"</span>]],</span>
<span id="cb25-9">    TEAL,</span>
<span id="cb25-10">)</span>
<span id="cb25-11">fig.tight_layout()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-film-audio-stats" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-film-audio-stats-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/uses-of-tensor-factorizations/index_files/figure-html/fig-film-audio-stats-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-film-audio-stats-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;8: Soundtrack STFT after HOSVD. Left: numbers stored, dense vs kept. Middle: compression (dense / kept). Right: relative error on the STFT tensor, the complex STFT, and the waveform.
</figcaption>
</figure>
</div>
</div>
</div>
<ul>
<li>STFT: <img src="https://latex.codecogs.com/png.latex?79,956"> / <img src="https://latex.codecogs.com/png.latex?81,012"> numbers (<img src="https://latex.codecogs.com/png.latex?1.01%5Ctimes">).</li>
<li>Relative error: STFT tensor <img src="https://latex.codecogs.com/png.latex?0.056">, complex <img src="https://latex.codecogs.com/png.latex?0.056">, waveform <img src="https://latex.codecogs.com/png.latex?0.039">.</li>
</ul>
<div id="cell-fig-film-audio" class="cell" data-fig-height="5" data-fig-width="10" data-execution_count="25">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb26" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb26-1">t_wav <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.arange(wav.size) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> sr</span>
<span id="cb26-2">fig, axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.0</span>), sharex<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"col"</span>, sharey<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"row"</span>)</span>
<span id="cb26-3"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> ax, z, title <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (</span>
<span id="cb26-4">    (axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], Z, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"original"</span>),</span>
<span id="cb26-5">    (axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], Z_hat, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"compressed"</span>),</span>
<span id="cb26-6">):</span>
<span id="cb26-7">    im <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ax.pcolormesh(</span>
<span id="cb26-8">        stft_times, stft_freq, spec_db(z),</span>
<span id="cb26-9">        cmap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"magma"</span>, shading<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"auto"</span>,</span>
<span id="cb26-10">    )</span>
<span id="cb26-11">    ax.set_title(title)</span>
<span id="cb26-12">    ax.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"frequency (Hz)"</span>)</span>
<span id="cb26-13"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> ax, y <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (</span>
<span id="cb26-14">    (axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], wav),</span>
<span id="cb26-15">    (axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], wav_hat),</span>
<span id="cb26-16">):</span>
<span id="cb26-17">    ax.plot(t_wav, y, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>INK, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.55</span>)</span>
<span id="cb26-18">    ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"time (s)"</span>)</span>
<span id="cb26-19">    ax.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pressure"</span>)</span>
<span id="cb26-20">    ax.set_xlim(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, t_wav[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span>
<span id="cb26-21">fig.colorbar(im, ax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], fraction<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.03</span>, pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"loudness (dB)"</span>)</span>
<span id="cb26-22">fig.tight_layout()</span></code></pre></div>
</details>
<div class="cell-output cell-output-stderr">
<pre><code>/var/folders/p9/vwq0gfs15vb07tg6xw1r14180000gn/T/ipykernel_12694/2602618714.py:22: UserWarning: This figure includes Axes that are not compatible with tight_layout, so results might be incorrect.
  fig.tight_layout()</code></pre>
</div>
<div class="cell-output cell-output-display">
<div id="fig-film-audio" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-film-audio-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/uses-of-tensor-factorizations/index_files/figure-html/fig-film-audio-output-2.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-film-audio-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;9: Original vs compressed. Top: STFT magnitude. Bottom: waveform after inverting the compressed STFT.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Original soundtrack:</p>
<audio controls="" src="media/clip.wav">
</audio>
<p>Compressed soundtrack. Relative error <img src="https://latex.codecogs.com/png.latex?0.039"> (waveform), <img src="https://latex.codecogs.com/png.latex?0.056"> (STFT tensor).</p>
<audio controls="" src="media/clip-cp.wav">
</audio>
</section>
</section>
<section id="unmixing" class="level2 page-columns page-full" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="unmixing"><span class="header-section-number">6</span> Unmixing</h2>
<section id="assay" class="level3 page-columns page-full" data-number="6.1">
<h3 data-number="6.1" class="anchored" data-anchor-id="assay"><span class="header-section-number">6.1</span> Assay</h3>
<p>You want how much of each dye is in each well. The dyes are already mixed; you cannot pipette them apart.</p>
<p>The instrument shines one excitation colour into one well and records how bright that well is at one emission colour. That number is <strong>intensity</strong>. Sweep both colours and one well becomes a map (emission colour × excitation colour). Twenty wells become a stack of twenty maps.</p>
<p>That stack is a 3-way tensor <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BX%7D%5Cin%5Cmathbb%7BR%7D%5E%7B20%5Ctimes%2024%5Ctimes%2018%7D">.</p>
<ul>
<li><strong>Sample</strong> (20). Which well.</li>
<li><strong>Emission colour</strong> (24). Colour coming out.</li>
<li><strong>Excitation colour</strong> (18). Colour shone in.</li>
<li><strong>Intensity.</strong> The value in each cell, not a fourth axis.</li>
</ul>
<p>Dye is not an axis of <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BX%7D">. Each dye is one rank-1 tensor: amount × emission spectrum × excitation spectrum. The observed cube is the sum of three of those, plus noise.</p>
<p>The maps do not give the amounts. The three fingerprints overlap, so a bright spot is a mix. Flatten the two colour axes into one long row and the cube becomes a <img src="https://latex.codecogs.com/png.latex?20%5Ctimes%20432"> matrix: SVD then returns mixed dyes. CP keeps the three axes and recovers the amounts.</p>
<p>This cube is synthetic (seed 7, noise <img src="https://latex.codecogs.com/png.latex?0.08">). It stands in for a fluorescence excitation–emission stack. No wet-lab data.</p>
<p>Figure&nbsp;10 is the whole assay: settings in, one observation out, that tensor, and what the two splits return.</p>
<div class="page-columns page-full">
<div id="fig-mixing-assay" class="quarto-float quarto-figure quarto-figure-center anchored page-columns page-full" alt="Drawn assay poster in five numbered stages. One, the samples: a scientist in a lab coat and goggles pipettes three dyes into a twenty-well plate, one well ringed. Two, the settings: a console listing well 11 of 20, excitation colour 10 of 18, emission colour 13 of 24. Three, the instrument: lamp, prism and slit picking the excitation colour, the well, then emission read at ninety degrees through a second prism and slit. Four, the observation: a detector and a single intensity, 0.95. Five, the tensor: the swept map for that well, then twenty such maps stacked into a 20 by 24 by 18 cube. Middle band: the three rank-1 dyes, each an emission-by-excitation map with its two spectra, and their overlapping amounts. Bottom: branch A, CP returns the three dye maps and matching amounts; branch B, flattening then SVD returns mixed maps and amounts that go negative.">
<figure class="quarto-float quarto-float-fig figure page-columns page-full">
<div aria-describedby="fig-mixing-assay-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca" class="page-columns page-full">
<img src="https://project-delphi.github.io/ml-blog/posts/uses-of-tensor-factorizations/media/mixing-assay.png" class="img-fluid figure-img column-page" alt="Drawn assay poster in five numbered stages. One, the samples: a scientist in a lab coat and goggles pipettes three dyes into a twenty-well plate, one well ringed. Two, the settings: a console listing well 11 of 20, excitation colour 10 of 18, emission colour 13 of 24. Three, the instrument: lamp, prism and slit picking the excitation colour, the well, then emission read at ninety degrees through a second prism and slit. Four, the observation: a detector and a single intensity, 0.95. Five, the tensor: the swept map for that well, then twenty such maps stacked into a 20 by 24 by 18 cube. Middle band: the three rank-1 dyes, each an emission-by-excitation map with its two spectra, and their overlapping amounts. Bottom: branch A, CP returns the three dye maps and matching amounts; branch B, flattening then SVD returns mixed maps and amounts that go negative.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-mixing-assay-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;10: You want dye amounts; you measure intensity. Top: twenty wells of mixed dye; three settings — which well, which excitation colour, which emission colour; the instrument returns one observation. Sweeping the two colour settings gives one map per well, and stacking the wells gives <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BX%7D%5Cin%5Cmathbb%7BR%7D%5E%7B20%5Ctimes%2024%5Ctimes%2018%7D">, 8,640 readings. Middle: the three rank-1 dyes the cube is made of; their amounts overlap. Bottom: CP returns the dyes, flatten-then-SVD returns mixes. Drawn; the cube is synthetic, seed 7.
</figcaption>
</figure>
</div>
</div>
</section>
<section id="recoveries" class="level3" data-number="6.2">
<h3 data-number="6.2" class="anchored" data-anchor-id="recoveries"><span class="header-section-number">6.2</span> Recoveries</h3>
<p>Figure&nbsp;11 is CP versus flatten-then-SVD on that cube.</p>
<div id="fig-mixing" class="quarto-float quarto-figure quarto-figure-center anchored" alt="Animation: a drawn fluorimeter reads one intensity, then three dyes build a cube of emission-excitation maps, then CP recovers the dyes and flatten-then-SVD mixes them.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-mixing-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/uses-of-tensor-factorizations/media/mixing.gif" class="img-fluid figure-img" alt="Animation: a drawn fluorimeter reads one intensity, then three dyes build a cube of emission-excitation maps, then CP recovers the dyes and flatten-then-SVD mixes them.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-mixing-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;11: It opens on the instrument, then builds the cube dye by dye. Each sample is an emission × excitation map; the needle marks that sample. CP recovers the three dyes. Flatten-then-SVD returns mixed dyes.
</figcaption>
</figure>
</div>
<div id="d89711fc" class="cell" data-execution_count="28">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb28" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb28-1">cube, true <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> T.make_mixing_cube(np.random.default_rng(T.SEED))</span>
<span id="cb28-2">mix_factors <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cp_als(cube, rank<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, n_iter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">80</span>)</span>
<span id="cb28-3">X_cp_mix <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cp_reconstruct(mix_factors)</span>
<span id="cb28-4">_, cp_corr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> T.align_factors(true[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], mix_factors[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span>
<span id="cb28-5">u, _, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.svd(cube.reshape(cube.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), full_matrices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb28-6">_, svd_corr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> T.align_factors(true[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], u[:, :<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>])</span>
<span id="cb28-7"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cube"</span>, cube.shape)</span>
<span id="cb28-8"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(</span>
<span id="cb28-9">    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"CP relative error </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>T<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>rel_fro(cube, X_cp_mix)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, "</span></span>
<span id="cb28-10">    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"mean |corr| </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(np.mean(cp_corr))<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb28-11">)</span>
<span id="cb28-12"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"flatten-SVD mean |corr| </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(np.mean(svd_corr))<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>cube (20, 24, 18)
CP relative error 0.071, mean |corr| 1.00
flatten-SVD mean |corr| 0.536</code></pre>
</div>
</div>
<ul>
<li><strong>What you measure</strong> (the sweep). One map per sample. Bright spots move as the mix changes. The methods see only those maps.</li>
<li><strong>CP.</strong> Write the cube as three outer products, one per dye. Amount correlation <img src="https://latex.codecogs.com/png.latex?1.00">. Leftover error <img src="https://latex.codecogs.com/png.latex?0.071"> is the noise that was added. Kruskal’s condition holds (<img src="https://latex.codecogs.com/png.latex?k_A+k_B+k_C%5Cge%202R+2"> at rank <img src="https://latex.codecogs.com/png.latex?3">), so this split is unique up to renaming and scaling the dyes.</li>
<li><strong>Flatten, then SVD.</strong> Stack each map into one long row. The cube becomes a <img src="https://latex.codecogs.com/png.latex?20%5Ctimes%20432"> matrix. SVD finds three directions among the samples; they are mixes of the dyes (correlation <img src="https://latex.codecogs.com/png.latex?0.536">). Amounts go negative. The maps are not the dyes.</li>
</ul>
<p>An unfolding SVD can reconstruct the matrix well and still not return the sources.</p>
</section>
</section>
<section id="constraints" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="constraints"><span class="header-section-number">7</span> Constraints</h2>
<ul>
<li><strong>GEMM vs contractions.</strong> One large matrix multiply is replaced by a chain of small tensor contractions. On GPUs that chain is often memory-bound; theoretical MAC drop is not wall-clock. <a href="https://developer.nvidia.com/cutensor">cuTENSOR</a> and <a href="https://tensorly.org/torch/dev/">TensorLy-Torch</a> exist to close part of that gap (Kossaifi et al.&nbsp;2019).</li>
<li><strong>Lossy fit.</strong> Truncation discards higher-order mass. Lebedev et al.&nbsp;compress then fine-tune; a raw CP or TT drop-in lowers accuracy.</li>
<li><strong>Rank search.</strong> Exact CP rank is NP-hard. ALS can split a component or stall. The synthetic residuals above flatten at the noise floor only because the generating rank is known.</li>
</ul>
<p>Don’t. Flatten. Modes. Carry. Meaning. Ranks. Trade. Memory. Factorizations. Almost. Always.</p>
</section>
<section id="references" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="references"><span class="header-section-number">8</span> References</h2>
<ul>
<li>Håstad, J. (1990). Tensor rank is NP-complete. <em>Journal of Algorithms</em> 11(4), 644–654.</li>
<li>Harshman, R. A. (1970). Foundations of the PARAFAC procedure. <em>UCLA Working Papers in Phonetics</em> 16, 1–84.</li>
<li>Tucker, L. R. (1966). Some mathematical notes on three-mode factor analysis. <em>Psychometrika</em> 31(3), 279–311.</li>
<li>De Lathauwer, L., De Moor, B., and Vandewalle, J. (2000). A multilinear singular value decomposition. <em>SIAM Journal on Matrix Analysis and Applications</em> 21(4), 1253–1278.</li>
<li>Kolda, T. G., and Bader, B. W. (2009). Tensor decompositions and applications. <em>SIAM Review</em> 51(3), 455–500.</li>
<li>Oseledets, I. V. (2011). Tensor-train decomposition. <em>SIAM Journal on Scientific Computing</em> 33(5), 2295–2317.</li>
<li>Lebedev, V., Ganin, Y., Rakhuba, M., Oseledets, I., and Lempitsky, V. (2015). <a href="https://arxiv.org/abs/1412.6553">Speeding-up convolutional neural networks using fine-tuned CP-decomposition</a>. ICLR workshop.</li>
<li>Simonyan, K., and Zisserman, A. (2015). <a href="https://arxiv.org/abs/1409.1556">Very Deep Convolutional Networks for Large-Scale Image Recognition</a>. ICLR.</li>
<li>Novikov, A., Podoprikhin, D., Osokin, A., and Vetrov, D. (2015). Tensorizing neural networks. <em>NeurIPS</em>.</li>
<li>Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., Gomez, A. N., Kaiser, Ł., and Polosukhin, I. (2017). Attention is all you need. <em>NeurIPS</em>.</li>
<li>Kossaifi, J., Panagakis, Y., Anandkumar, A., and Pantic, M. (2019). <a href="https://tensorly.org/">TensorLy: tensor learning in Python</a>. <em>JMLR</em> 20(26), 1–6.</li>
<li><a href="https://commons.wikimedia.org/wiki/File:Lawrence_Of_Arabia_(1962)_-_Trailer.webm">Lawrence of Arabia (1962) theatrical trailer</a> — public domain; 5 s clip in <code>media/</code>.</li>
<li><a href="../tensor-factorizations/">Tensor Factorizations and Tensor Inverses</a> — CP, Tucker, TT, t-SVD, and the four inverses.</li>
<li><a href="https://developer.nvidia.com/cutensor">cuTENSOR</a>; <a href="https://tensorly.org/torch/dev/">TensorLy-Torch</a>.</li>
</ul>
<div id="widget-injection" class="cell" data-execution_count="29">
<script id="tf-data" type="application/json">{"cp":{"d":3,"cin":64,"cout":64,"true_rank":16,"noise_frac":0.08,"dense_params":36864,"ranks":[4,8,16,24,32,48,64],"params":[536,1072,2144,3216,4288,6432,8576],"rel_error":[0.6226594837507916,0.38407828837202634,0.07721838391345424,0.07525506975335904,0.07362430216363729,0.06812010876166105,0.0660550853160118],"compression":[68.77611940298507,34.38805970149254,17.19402985074627,11.462686567164178,8.597014925373134,5.731343283582089,4.298507462686567],"headline":{"d":3,"cin":512,"cout":512,"rank":64,"dense_params":2359296,"cp_params":65920,"params":[4120,8240,16480,24720,32960,49440,65920]}},"tt":{"ms":[4,4,4,4],"ns":[4,4,4,4],"true_rank":4,"noise_frac":0.08,"dense_params":65536,"ranks":[1,2,4,6,8,12,16],"params":[64,192,640,1344,2304,4992,8704],"rel_error":[0.9347759538917871,0.7846234481152172,0.0793731364775934,0.07879203185624238,0.07794295125819295,0.0754238004479042,0.07172264631466674],"compression":[1024.0,341.3333333333333,102.4,48.76190476190476,28.444444444444443,13.128205128205128,7.529411764705882],"svd_ranks":[1,2,4,8,16,32,64],"svd_params":[513,1026,2052,4104,8208,16416,32832],"svd_rel_error":[0.9816505152189615,0.9640828322404761,0.9315373449163201,0.8720734863451693,0.778723918214117,0.6393709139268994,0.43628556406931324],"headline":{"ms":[8,8,8,8],"ns":[8,8,8,8],"rank":16,"dense_params":16777216,"tt_params":34816,"params":[256,768,2560,5376,9216,19968,34816]}},"mixing":{"cp_rel_error":0.07212263580501534,"cp_sample":[[0.7718505336511694,0.12800174957008006,-0.00028542245145383213],[1.11430451743385,0.2583600085508423,0.0020715150096932682],[1.5393648733883623,0.436149225112389,0.04902071104291799],[1.9924087650484532,0.7119458730740075,0.08398968694054963],[2.3701527837223892,1.0972868716008874,0.18448898567113725],[2.661886589275244,1.5274429762905914,0.3089427907245605],[2.7563890304484415,2.0096533599539743,0.5396555062299964],[2.6740639335993026,2.4789470856992635,0.8160723085126194],[2.4402482805991874,2.8685026374976905,1.181855095697548],[2.0370431996637333,3.0788120901073794,1.607608820280019],[1.619635707214235,3.068056089378356,2.047739239375107],[1.1800927889799326,2.869098856975889,2.425391533177038],[0.820792953209063,2.48333862340905,2.6647169326813263],[0.5244569263397267,2.018138740446447,2.745785235576535],[0.33656738699910366,1.5244748120951686,2.6593108214977197],[0.16339732429311873,1.102343436735797,2.362156833647263],[0.07996396604574799,0.7349245793311251,1.9797864078205856],[0.04423289539533702,0.4345983303579439,1.556377334174702],[0.0234225658039346,0.25851688608108164,1.123390636259985],[0.0018776347703707563,0.14983047380359948,0.7535627130926762]],"true_sample":[[0.2780373004531942,0.04393693362340742,0.0030887154082367718],[0.4091909312883345,0.08194300765669912,0.007299939659558192],[0.561918255807905,0.14259958497207362,0.016098479202680607],[0.7200198814029413,0.2315521551735149,0.0331264301020803],[0.8608748888907012,0.35083555375172676,0.06360461476453416],[0.9604169060706609,0.49600102520262995,0.11395327773772101],[0.9997784179047401,0.6543133099303097,0.19049747416297216],[0.971117820001401,0.8054028093870947,0.29715006288565343],[0.8801654046321276,0.9250489839431286,0.43250052222583457],[0.7443563028812906,0.9913808500055036,0.5873833755762792],[0.5873833755762796,0.9913808500055036,0.7443563028812903],[0.43250052222583474,0.9250489839431286,0.8801654046321274],[0.29715006288565365,0.805402809387095,0.9711178200014009],[0.19049747416297247,0.65431330993031,0.9997784179047401],[0.11395327773772113,0.4960010252026301,0.960416906070661],[0.06360461476453422,0.35083555375172676,0.8608748888907013],[0.03312643010208042,0.23155215517351496,0.7200198814029416],[0.01609847920268065,0.14259958497207376,0.5619182558079054],[0.007299939659558192,0.0819430076566992,0.4091909312883348],[0.0030887154082367744,0.04393693362340742,0.2780373004531943]],"svd_sample":[[-0.04452564189194945,-0.10304796536792181,-0.17244889343352748],[-0.06815527977047331,-0.14850162612820356,-0.2144616733724459],[-0.10047305963269494,-0.19899659046255982,-0.27529518325358127],[-0.13870061611835877,-0.25461550809271905,-0.2940625553601434],[-0.18206078629843475,-0.2915686926653169,-0.2635642298044798],[-0.22461339733747115,-0.313980109433964,-0.19184783022819277],[-0.26546776040088466,-0.2955255934519346,-0.07070394435113186],[-0.299162955034857,-0.24768550138030634,0.08452288872618394],[-0.32576545246894406,-0.1674974037631805,0.2222378254150962],[-0.3377385586686149,-0.05689802714927987,0.30949680597432633],[-0.33833761396760526,0.05765439278913784,0.2986422482205456],[-0.325085383489653,0.16680122862601243,0.22558024087342513],[-0.29940363533354364,0.24657311676305488,0.08522164464825571],[-0.2648447023756755,0.2969031186001771,-0.06413937133320244],[-0.2259454409505943,0.31026175586602806,-0.20735099426673262],[-0.1810764865529904,0.29370108899020847,-0.25591661477764366],[-0.13921778086702272,0.2536451228094244,-0.28443070195188896],[-0.10117581057919389,0.20184588593828898,-0.2844314237672897],[-0.06976271998352342,0.14679632035960696,-0.2279027464700505],[-0.0449125974769866,0.10040778542420561,-0.157273224704455]],"cp_emission":[[0.07578441879728297,-0.00024219918050146552,-0.0015245052627941077],[0.18218658276765665,-0.00015587201478899228,0.002016693615575353],[0.3312133431104275,-0.0019106638778095106,0.000433272045133049],[0.48904018822977185,-0.0005241770677409135,-0.001292814036974763],[0.5668371281719593,0.0008313671713995266,0.0021242156269377097],[0.5213157374583489,0.00040241074624202647,-0.0025839787293546047],[0.378933003617524,0.01485126738143663,-0.0011562890727822796],[0.21800255603664123,0.048118940893083356,-0.0012046874007371777],[0.10034621552524695,0.1273373814065661,0.001514800163820418],[0.037240479151448985,0.25182090896734194,0.00017901566204083926],[0.01063185392328521,0.4084648584459927,-0.0017870444266143964],[0.0001827519291244249,0.5154645806913658,-0.0025952150461273334],[4.6309627676494514e-05,0.5168254346631751,0.00021317375956975783],[-0.0007349036658560146,0.41011764361603004,0.009814155725704418],[-0.0007847055652610855,0.2573537818909597,0.03334697140359814],[-0.0006433098757194631,0.12768592084807664,0.09575497257085301],[-0.0009312406820628241,0.04800032347494926,0.2096791891960143],[0.0011982166695358907,0.014917586536610601,0.3670744085763288],[-0.0009514357101500137,0.0041541803691586675,0.4998336260579519],[0.0025907323969343614,0.003454295025398705,0.5462293942939235],[0.00117815544330932,0.0021152397089240246,0.4690159323015102],[-0.0003859802953175132,0.0004706511714602032,0.3218816814552229],[0.0005296728207253185,0.001242047367871952,0.17149752053095696],[-0.0010163297039079947,0.002739731216088369,0.07346598275678123]],"true_emission":[[0.1353352832366127,1.9857504150120978e-07,9.421025317220781e-19],[0.3164768250509731,2.587174150635387e-06,6.838632987789911e-17],[0.5860277119977318,2.6691441460113906e-05,3.9308437978872695e-15],[0.8592899989992834,0.00021805384883264762,1.7891536524664967e-13],[0.9977155122073207,0.0014105901557939773,6.4484428599503555e-12],[0.917316183349008,0.007225756198487246,1.840379707176932e-10],[0.6678466468575018,0.029309683116615,4.159157622416555e-09],[0.385016913076804,0.09414219185952329,7.443011439284492e-08],[0.175763312581408,0.23944338941730176,1.0547204266224509e-06],[0.063536313696842,0.48224392008835204,1.1835085263560189e-05],[0.018187004002040503,0.7690879978582834,0.00010516005113850916],[0.004122357710905936,0.9712491918366434,0.0007399042973429797],[0.0007399042973429784,0.9712491918366434,0.004122357710905946],[0.00010516005113850916,0.7690879978582837,0.018187004002040503],[1.1835085263560146e-05,0.48224392008835204,0.06353631369684208],[1.0547204266224451e-06,0.23944338941730176,0.17576331258140823],[7.443011439284466e-08,0.09414219185952329,0.3850169130768044],[4.159157622416555e-09,0.029309683116615064,0.6678466468575018],[1.840379707176932e-10,0.007225756198487278,0.9173161833490078],[6.4484428599503555e-12,0.0014105901557939773,0.9977155122073207],[1.7891536524664967e-13,0.00021805384883264762,0.8592899989992833],[3.9308437978872695e-15,2.6691441460113906e-05,0.5860277119977318],[6.838632987789813e-17,2.587174150635387e-06,0.3164768250509727],[9.421025317220648e-19,1.9857504150120978e-07,0.13533528323661256]],"cp_corr":[0.9999804626050979,0.9999842091852836,0.9999839389646223],"cp_em_corr":[0.9999863492258427,0.9999665294437151,0.9999742427339585],"svd_corr":[0.525842953899397,0.9867434164450419,0.09547299197999595],"mean_cp_corr":0.9999828702516679,"mean_cp_em_corr":0.9999757071345056,"mean_svd_corr":0.5360197874414783}}</script>
<script>/* Seven-rank slider for "Uses of Tensor Factorizations".
 *
 * Replaces the static CP / TT figures. Seven discrete ranks, lookup-only
 * (no factorization in the browser). Data is inlined as #tf-data.
 */
"use strict";

const TFW = (function () {
  const NS = "http://www.w3.org/2000/svg";
  const C = {
    ink: "#1F2430",
    muted: "#5F6672",
    rule: "#D8DBE2",
    purple: "#4A3AA7",
    teal: "#2A9D8F",
    coral: "#E07A5F",
    paper: "#F4F5F7",
    white: "#FFFFFF",
  };

  function el(tag, attrs, kids) {
    const e = document.createElementNS(NS, tag);
    for (const k in attrs || {}) e.setAttribute(k, attrs[k]);
    for (const c of kids || []) e.appendChild(c);
    return e;
  }

  function h(tag, attrs, kids) {
    const e = document.createElement(tag);
    for (const k in attrs || {}) {
      if (k === "html") e.innerHTML = attrs[k];
      else if (k === "text") e.textContent = attrs[k];
      else if (k === "style" && typeof attrs[k] === "object") {
        Object.assign(e.style, attrs[k]);
      } else e.setAttribute(k, attrs[k]);
    }
    for (const c of kids || []) e.appendChild(c);
    return e;
  }

  function clear(node) {
    while (node.firstChild) node.removeChild(node.firstChild);
  }

  function fmtInt(n) {
    return Math.round(n).toLocaleString("en-US");
  }

  function fmtRatio(n) {
    return (Math.round(n * 10) / 10).toFixed(1) + "x";
  }

  function ensureCss() {
    if (document.getElementById("tf-widget-css")) return;
    const s = document.createElement("style");
    s.id = "tf-widget-css";
    s.textContent = [
      ".widget-container{border:1px solid #D8DBE2;border-radius:10px;background:#fff;padding:1.6rem 1.5rem 1.4rem;margin:2rem 0}",
      ".widget-header{display:flex;justify-content:space-between;align-items:center;gap:0.75rem;flex-wrap:wrap;margin-bottom:0.9rem;padding-bottom:0.6rem;border-bottom:1px solid #E9EBEF}",
      ".widget-title{font-weight:700;font-size:1.15rem;color:#1F2430}",
      ".widget-badge{font-size:0.75rem;text-transform:uppercase;font-weight:700;letter-spacing:0.05em;padding:0.2rem 0.6rem;border-radius:12px;background:#4A3AA7;color:#fff}",
      ".widget-note{font-size:0.9rem;color:#5F6672;margin-top:0.85rem;line-height:1.45}",
      ".tf-wrap{font-size:1rem;color:#1F2430}",
      ".tf-tabs{display:flex;gap:0.5rem;margin-bottom:1rem;flex-wrap:wrap}",
      ".tf-tab{border:1px solid #D8DBE2;background:#fff;color:#1F2430;padding:0.45rem 0.95rem;border-radius:999px;cursor:pointer;font:inherit;font-size:0.95rem}",
      ".tf-tab[aria-pressed='true']{background:#4A3AA7;color:#fff;border-color:#4A3AA7}",
      ".tf-stats{display:flex;flex-wrap:wrap;gap:1.15rem;background:#F4F5F7;border:1px solid #D8DBE2;border-radius:8px;padding:0.75rem 1rem;margin-bottom:1rem}",
      ".tf-stat span{display:block;font-size:0.75rem;text-transform:uppercase;letter-spacing:0.04em;color:#5F6672}",
      ".tf-stat strong{font-size:1.15rem}",
      ".tf-slider{width:100%;accent-color:#4A3AA7;height:1.35rem}",
      ".tf-slider-row{display:flex;align-items:center;gap:0.85rem;margin:0.5rem 0 0.2rem}",
      ".tf-ticks{display:flex;justify-content:space-between;font-size:0.9rem;color:#5F6672;margin:0 0 1.1rem;padding-left:5.5rem}",
      ".tf-tick{cursor:pointer;border:0;background:none;font:inherit;color:#5F6672;padding:0.2rem 0.25rem}",
      ".tf-tick[aria-current='true']{color:#4A3AA7;font-weight:700}",
      ".tf-charts{display:grid;grid-template-columns:minmax(240px,0.9fr) minmax(280px,1.25fr);gap:1.25rem;align-items:stretch}",
      ".tf-charts svg{background:#fff;border:1px solid #E2E6E1;border-radius:8px;width:100%;height:auto}",
      "@media (max-width:720px){.tf-charts{grid-template-columns:1fr}}",
    ].join("");
    document.head.appendChild(s);
  }

  function fmtErr(e) {
    return e.toFixed(3);
  }

  function barChart(dense, used, color, usedLabel) {
    const w = 420, ht = 260, pad = { l: 14, r: 108, t: 36, b: 18 };
    const inner = w - pad.l - pad.r;
    const svg = el("svg", {
      viewBox: "0 0 " + w + " " + ht, width: w, height: ht,
      role: "img", "aria-label": "Weights stored, dense versus factorized",
    });
    svg.appendChild(el("text", {
      x: pad.l, y: 18, fill: C.ink, "font-size": 13, "font-weight": 700,
    }, [document.createTextNode("Weights stored")]));
    const lo = Math.log(Math.max(used * 0.45, 1));
    const hi = Math.log(dense);
    const rows = [
      { label: "uncompressed", value: dense, fill: "#B9BEC9" },
      { label: usedLabel, value: used, fill: color },
    ];
    rows.forEach(function (row, i) {
      const y = 48 + i * 88;
      const bw = Math.max(8, ((Math.log(row.value) - lo) / (hi - lo)) * inner);
      svg.appendChild(el("text", {
        x: pad.l, y: y - 10, fill: C.muted, "font-size": 12,
      }, [document.createTextNode(row.label)]));
      svg.appendChild(el("rect", {
        x: pad.l, y: y, width: bw, height: 28, rx: 4, fill: row.fill,
      }));
      svg.appendChild(el("text", {
        x: pad.l + bw + 8, y: y + 20, fill: C.ink, "font-size": 14, "font-weight": 600,
      }, [document.createTextNode(fmtInt(row.value))]));
    });
    svg.appendChild(el("text", {
      x: pad.l, y: ht - 6, fill: C.muted, "font-size": 11,
    }, [document.createTextNode("log scale, so both bars read")]));
    return svg;
  }

  function errorChart(ranks, errors, idx, trueRank, color) {
    const w = 640, ht = 280;
    const pad = { l: 58, r: 36, t: 40, b: 48 };
    const innerW = w - pad.l - pad.r;
    const innerH = ht - pad.t - pad.b;
    const xMax = ranks.length - 1;
    const yMax = 1;
    function xPos(i) {
      return pad.l + (i / (xMax || 1)) * innerW;
    }
    function yPos(e) {
      return pad.t + innerH - (e / yMax) * innerH;
    }
    const svg = el("svg", {
      viewBox: "0 0 " + w + " " + ht, width: w, height: ht,
      role: "img",
      "aria-label": "Relative error at seven ranks",
    });
    svg.appendChild(el("text", {
      x: pad.l, y: 18, fill: C.ink, "font-size": 13, "font-weight": 700,
    }, [document.createTextNode("Toy relative error")]));

    [0, 0.25, 0.5, 0.75, 1].forEach(function (tick) {
      const y = yPos(tick);
      svg.appendChild(el("line", {
        x1: pad.l, x2: pad.l + innerW, y1: y, y2: y,
        stroke: C.paper, "stroke-width": 1,
      }));
      svg.appendChild(el("text", {
        x: pad.l - 8, y: y + 4, fill: C.muted,
        "font-size": 11, "text-anchor": "end",
      }, [document.createTextNode(tick.toFixed(2))]));
    });
    svg.appendChild(el("line", {
      x1: pad.l, x2: pad.l, y1: pad.t, y2: pad.t + innerH,
      stroke: C.rule, "stroke-width": 1,
    }));
    svg.appendChild(el("line", {
      x1: pad.l, x2: pad.l + innerW, y1: pad.t + innerH, y2: pad.t + innerH,
      stroke: C.rule, "stroke-width": 1,
    }));

    const trueIdx = ranks.indexOf(trueRank);
    if (trueIdx >= 0) {
      const tx = xPos(trueIdx);
      svg.appendChild(el("rect", {
        x: tx - 8, y: pad.t, width: 16, height: innerH,
        fill: "#E9C46A", opacity: "0.22",
      }));
      svg.appendChild(el("line", {
        x1: tx, x2: tx, y1: pad.t, y2: pad.t + innerH,
        stroke: "#B08918", "stroke-width": 1.75, "stroke-dasharray": "5 4",
      }));
      const built = "toy built at rank " + trueRank;
      const textX = trueIdx <= ranks.length - 3 ? tx + 12 : tx - 12;
      svg.appendChild(el("text", {
        x: textX, y: pad.t + 14, fill: "#8A6B12",
        "font-size": 12, "font-weight": 700,
        "text-anchor": trueIdx <= ranks.length - 3 ? "start" : "end",
      }, [document.createTextNode(built)]));
    }

    let line = "";
    let area = "M " + xPos(0) + " " + (pad.t + innerH) + " ";
    ranks.forEach(function (_r, i) {
      line += (i === 0 ? "M" : "L") + xPos(i) + " " + yPos(errors[i]) + " ";
      area += "L " + xPos(i) + " " + yPos(errors[i]) + " ";
    });
    area += "L " + xPos(xMax) + " " + (pad.t + innerH) + " Z";
    svg.appendChild(el("path", { d: area, fill: color, opacity: "0.10" }));
    svg.appendChild(el("path", {
      d: line.trim(), fill: "none", stroke: color, "stroke-width": 2.5,
    }));

    ranks.forEach(function (r, i) {
      const x = xPos(i);
      const y = yPos(errors[i]);
      const selected = i === idx;
      svg.appendChild(el("circle", {
        cx: x, cy: y, r: selected ? 7 : 4.5,
        fill: selected ? color : C.white, stroke: color, "stroke-width": 2,
      }));
      let ly;
      if (selected) {
        ly = y < pad.t + 24 ? y + 22 : y - 16;
      } else if (i % 2 === 1) {
        ly = Math.min(y + 18, pad.t + innerH - 6);
      } else {
        ly = y < pad.t + 18 ? y + 18 : y - 12;
      }
      svg.appendChild(el("text", {
        x: x, y: ly, fill: selected ? color : C.ink,
        "font-size": selected ? 14 : 11,
        "font-weight": selected ? 700 : 500,
        "text-anchor": "middle",
      }, [document.createTextNode(fmtErr(errors[i]))]));
      svg.appendChild(el("text", {
        x: x, y: pad.t + innerH + 18, fill: selected ? C.ink : C.muted,
        "font-size": 12, "font-weight": selected ? 700 : 400,
        "text-anchor": "middle",
      }, [document.createTextNode(String(r))]));
    });

    svg.appendChild(el("text", {
      x: pad.l + innerW / 2, y: ht - 6, fill: C.muted,
      "font-size": 12, "text-anchor": "middle",
    }, [document.createTextNode("rank")]));
    svg.appendChild(el("text", {
      x: 14, y: pad.t + innerH / 2, fill: C.muted, "font-size": 12,
      transform: "rotate(-90 14 " + (pad.t + innerH / 2) + ")",
      "text-anchor": "middle",
    }, [document.createTextNode("relative error")]));
    return svg;
  }

  function init(containerId, payload) {
    const root = document.getElementById(containerId);
    if (!root || !payload || !payload.cp || !payload.tt) return;
    ensureCss();
    clear(root);
    root.classList.add("tf-wrap");

    let mode = "cp";
    const series = { cp: payload.cp, tt: payload.tt };
    const idx0 = {
      cp: payload.cp.ranks.indexOf(payload.cp.true_rank),
      tt: payload.tt.ranks.indexOf(payload.tt.true_rank),
    };
    const idx = { cp: idx0.cp < 0 ? 0 : idx0.cp, tt: idx0.tt < 0 ? 0 : idx0.tt };

    const tabs = h("div", { class: "tf-tabs" });
    const tabCp = h("button", {
      class: "tf-tab", type: "button", text: "CP convolution",
      "aria-pressed": "true",
    });
    const tabTt = h("button", {
      class: "tf-tab", type: "button", text: "TT-matrix",
      "aria-pressed": "false",
    });
    tabs.appendChild(tabCp);
    tabs.appendChild(tabTt);

    const stats = h("div", { class: "tf-stats" });
    const sliderRow = h("div", { class: "tf-slider-row" });
    const sliderLabel = h("label", { text: "rank" });
    sliderLabel.setAttribute("for", "tf-rank");
    const slider = h("input", {
      id: "tf-rank", class: "tf-slider", type: "range", min: "0", step: "1",
    });
    sliderRow.appendChild(sliderLabel);
    sliderRow.appendChild(slider);
    const ticks = h("div", { class: "tf-ticks" });

    const charts = h("div", { class: "tf-charts" });
    const note = h("div", { class: "widget-note" });

    root.appendChild(tabs);
    root.appendChild(stats);
    root.appendChild(sliderRow);
    root.appendChild(ticks);
    root.appendChild(charts);
    root.appendChild(note);

    function current() {
      const s = series[mode];
      const i = idx[mode];
      const head = s.headline;
      const headUsed = (head.params && head.params[i] != null)
        ? head.params[i]
        : (mode === "cp" ? head.cp_params : head.tt_params);
      return {
        rank: s.ranks[i],
        params: s.params[i],
        error: s.rel_error[i],
        dense: s.dense_params,
        headDense: head.dense_params,
        headUsed: headUsed,
        trueRank: s.true_rank,
        ranks: s.ranks,
        errors: s.rel_error,
        color: mode === "cp" ? C.purple : C.teal,
        name: mode === "cp" ? "CP" : "TT",
        headLabel: mode === "cp" ? "VGG-16 conv5" : "4096×4096 W_O",
      };
    }

    function render() {
      const cur = current();
      slider.max = String(cur.ranks.length - 1);
      slider.value = String(idx[mode]);
      sliderLabel.textContent = cur.name + " rank " + cur.rank;
      tabCp.setAttribute("aria-pressed", mode === "cp" ? "true" : "false");
      tabTt.setAttribute("aria-pressed", mode === "tt" ? "true" : "false");
      clear(stats);
      [
        ["rank", String(cur.rank)],
        [cur.headLabel, fmtInt(cur.headUsed) + " / " + fmtInt(cur.headDense)],
        ["how much smaller", fmtRatio(cur.headDense / cur.headUsed)],
        ["toy relative error", cur.error.toFixed(3)],
      ].forEach(function (pair) {
        stats.appendChild(h("div", { class: "tf-stat" }, [
          h("span", { text: pair[0] }),
          h("strong", { text: pair[1] }),
        ]));
      });
      clear(ticks);
      cur.ranks.forEach(function (r, i) {
        const b = h("button", {
          class: "tf-tick", type: "button", text: String(r),
        });
        if (i === idx[mode]) b.setAttribute("aria-current", "true");
        b.addEventListener("click", function () {
          idx[mode] = i;
          render();
        });
        ticks.appendChild(b);
      });
      clear(charts);
      charts.appendChild(barChart(
        cur.headDense, cur.headUsed, cur.color,
        mode === "cp" ? "after CP" : "after TT"
      ));
      charts.appendChild(errorChart(
        cur.ranks, cur.errors, idx[mode], cur.trueRank, cur.color
      ));
      note.textContent = mode === "cp"
        ? "Weight bar: VGG-16 conv5, closed form. Error curve: 64-channel toy. Gold band: the toy was generated at rank 16 — that is where leftover error hits the noise."
        : "Weight bar: transformer W_O at width 4096, closed form. Error curve: 256×256 toy. Gold band: the toy was generated at rank 4 — that is where leftover error hits the noise.";
    }

    tabCp.addEventListener("click", function () { mode = "cp"; render(); });
    tabTt.addEventListener("click", function () { mode = "tt"; render(); });
    slider.addEventListener("input", function () {
      idx[mode] = parseInt(slider.value, 10);
      render();
    });
    render();
  }

  return { init: init };
})();

document.addEventListener("DOMContentLoaded", function () {
  const script = document.getElementById("tf-data");
  let data = {};
  if (script) {
    try { data = JSON.parse(script.textContent); }
    catch (err) { console.error("could not parse tensor-factorization widget payload", err); }
  }
  TFW.init("tf-widget", data);
});
</script>
</div>


</section>

 ]]></description>
  <category>Linear Algebra</category>
  <category>Machine Learning</category>
  <category>Numerical Methods</category>
  <category>Tensors</category>
  <guid>https://project-delphi.github.io/ml-blog/posts/uses-of-tensor-factorizations/</guid>
  <pubDate>Sat, 29 Aug 2026 05:00:00 GMT</pubDate>
  <media:content url="https://project-delphi.github.io/ml-blog/posts/uses-of-tensor-factorizations/cover.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>NumPy to JAX: The Array That Learned New Tricks</title>
  <dc:creator>Ravi Kalia</dc:creator>
  <link>https://project-delphi.github.io/ml-blog/posts/numpy-to-jax/</link>
  <description><![CDATA[ 





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/numpy-to-jax/cover.png" class="img-fluid figure-img"></p>
<figcaption>NumPy to JAX: The Array That Learned New Tricks</figcaption>
</figure>
</div>
<p>NumPy stores numbers in arrays whose operations run in compiled code. JAX keeps that array API and adds compilation, automatic differentiation, and accelerator backends.</p>
<section id="history" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="history"><span class="header-section-number">1</span> History</h2>
<p>Python had two earlier array libraries:</p>
<ul>
<li><strong>Numeric</strong> — fast on small arrays.</li>
<li><strong>Numarray</strong> — features needed for large astronomical images.</li>
</ul>
<p>The two APIs were incompatible. Travis Oliphant merged them and released NumPy 1.0 in 2006 (<a href="https://csc.ucdavis.edu/~chaos/courses/nlp/Software/NumPyBook.pdf"><em>Guide to NumPy</em></a>).</p>
<p>That common type is what pandas, SciPy, and scikit-learn exchange. Harris et al.&nbsp;(2020), <a href="https://doi.org/10.1038/s41586-020-2649-2">“Array programming with NumPy”</a>, traces the downstream impact.</p>
</section>
<section id="numpy-primitives" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="numpy-primitives"><span class="header-section-number">2</span> NumPy primitives</h2>
<p>Four constructors and operations cover most work:</p>
<ul>
<li><code>np.array</code> — Python values to an array.</li>
<li><code>np.arange</code> — regular sequence.</li>
<li><code>.reshape()</code> — change dimensions without changing values.</li>
<li>elementwise arithmetic — no Python loop.</li>
</ul>
<p>Array metadata:</p>
<ul>
<li><code>.shape</code> — length of each axis.</li>
<li><code>.ndim</code> — number of axes.</li>
<li><code>.size</code> — total element count.</li>
<li><code>.dtype</code> — storage type.</li>
</ul>
<p>The new dimensions after <code>reshape</code> must still multiply to <code>.size</code>.</p>
<div>
<div id="pyodide-1" class="exercise-cell">

</div>
<script type="pyodide-1-contents">
eyJhdHRyIjp7ImVkaXQiOnRydWUsImV2YWwiOnRydWV9LCJjb2RlIjoiaW1wb3J0IG51bXB5IGFzIG5wXG5cbmdyaWQgPSBucC5hcmFuZ2UoMTIpLnJlc2hhcGUoMywgNClcbnNoaWZ0ZWQgPSBncmlkICogMTAgKyA1XG5cbnByaW50KFwiZ3JpZDpcXG5cIiwgZ3JpZClcbnByaW50KFwiXFxuc2hhcGU6XCIsIGdyaWQuc2hhcGUpXG5wcmludChcImRpbWVuc2lvbnM6XCIsIGdyaWQubmRpbSlcbnByaW50KFwic2l6ZTpcIiwgZ3JpZC5zaXplKVxucHJpbnQoXCJkdHlwZTpcIiwgZ3JpZC5kdHlwZSlcbnByaW50KFwiXFxuc2hpZnRlZDpcXG5cIiwgc2hpZnRlZCkifQ==
</script>
</div>
<p>Indexing:</p>
<ul>
<li><code>grid[1, 2]</code> — one value.</li>
<li><code>grid[1, :]</code> — one row.</li>
<li><code>grid[:, 2]</code> — one column.</li>
</ul>
<p><strong>Vectorized math:</strong> state the array operation; NumPy runs it in compiled routines.</p>
</section>
<section id="reductions-and-broadcasting" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="reductions-and-broadcasting"><span class="header-section-number">3</span> Reductions and broadcasting</h2>
<p>Reductions collapse many values to summaries: <code>.mean()</code>, <code>.sum()</code>, <code>.min()</code>, <code>.max()</code>.</p>
<p>On a matrix, <code>axis</code> picks the direction:</p>
<ul>
<li><code>grid.sum(axis=0)</code> — column totals.</li>
<li><code>grid.sum(axis=1)</code> — row totals.</li>
</ul>
<p>Broadcasting subtracts a scalar mean from every element.</p>
<div>
<div id="pyodide-2" class="exercise-cell">

</div>
<script type="pyodide-2-contents">
eyJhdHRyIjp7ImVkaXQiOnRydWUsImV2YWwiOnRydWV9LCJjb2RlIjoiaW1wb3J0IG51bXB5IGFzIG5wXG5cbm1lYXN1cmVtZW50cyA9IG5wLmFycmF5KFsxMi4wLCAxNS4wLCAxNC4wLCAxMC4wLCAxOS4wXSlcbmNlbnRlcmVkID0gbWVhc3VyZW1lbnRzIC0gbWVhc3VyZW1lbnRzLm1lYW4oKVxuXG5wcmludChcIm1lYW46XCIsIG1lYXN1cmVtZW50cy5tZWFuKCkpXG5wcmludChcImNlbnRlcmVkOlwiLCBjZW50ZXJlZClcbnByaW50KFwiY2hlY2s6XCIsIGNlbnRlcmVkLnN1bSgpKSJ9
</script>
</div>
<p>The check should be near zero. Residual is floating-point rounding.</p>
<p>These values are synthetic: five numbers chosen for a centering demo, not measured.</p>
</section>
<section id="jax" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="jax"><span class="header-section-number">4</span> JAX</h2>
<p>JAX mirrors the NumPy API:</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> jax.numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> jnp</span>
<span id="cb1-2"></span>
<span id="cb1-3">x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.arange(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>).reshape(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>)</span>
<span id="cb1-4">y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.sin(x) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span></code></pre></div>
<p>Additions relative to NumPy:</p>
<ul>
<li>GPU / TPU execution for compatible work.</li>
<li><code>jax.jit</code> — compile a function for repeated runs.</li>
<li><code>jax.grad</code> — return a function that computes derivatives.</li>
</ul>
<p>Frostig, Johnson, and Leary (2018), <a href="https://mlsys.org/Conferences/2019/doc/2018/146.pdf">“Compiling machine learning programs via high-level tracing”</a>, describes the tracing used for compilation.</p>
<p>Characteristic pattern:</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> jax</span>
<span id="cb2-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> jax.numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> jnp</span>
<span id="cb2-3"></span>
<span id="cb2-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> loss(weight):</span>
<span id="cb2-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> (weight <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb2-6"></span>
<span id="cb2-7">fast_loss <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.jit(loss)</span>
<span id="cb2-8">loss_slope <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.grad(loss)</span>
<span id="cb2-9"></span>
<span id="cb2-10"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(fast_loss(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>))   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 4.0</span></span>
<span id="cb2-11"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(loss_slope(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>))  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -4.0</span></span></code></pre></div>
<p><code>jaxlib</code> has no Pyodide build, so JAX cannot run in this page. The live cell below uses a finite-difference slope on the same scalar loss.</p>
<div>
<div id="pyodide-3" class="exercise-cell">

</div>
<script type="pyodide-3-contents">
eyJhdHRyIjp7ImVkaXQiOnRydWUsImV2YWwiOnRydWV9LCJjb2RlIjoiZGVmIGxvc3Mod2VpZ2h0KTpcbiAgICByZXR1cm4gKHdlaWdodCAtIDMuMCkgKiogMlxuXG53ZWlnaHQgPSAxLjBcbnN0ZXAgPSAxZS01XG5udW1lcmljYWxfc2xvcGUgPSAobG9zcyh3ZWlnaHQgKyBzdGVwKSAtIGxvc3Mod2VpZ2h0IC0gc3RlcCkpIC8gKDIgKiBzdGVwKVxuXG5wcmludChcImxvc3M6XCIsIGxvc3Mod2VpZ2h0KSlcbnByaW50KFwibnVtZXJpY2FsIHNsb3BlOlwiLCBudW1lcmljYWxfc2xvcGUpXG5wcmludChcImV4YWN0IHNsb3BlOlwiLCAyICogKHdlaWdodCAtIDMuMCkpIn0=
</script>
</div>
<p><code>jax.grad(loss)(weight)</code> returns the exact slope without a finite step.</p>
</section>
<section id="constraints" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="constraints"><span class="header-section-number">5</span> Constraints</h2>
<p>JAX transformations assume <strong>pure functions</strong>: output depends only on inputs; no hidden mutation of outside state.</p>
<p>Arrays are immutable. Replace <code>x[0] = 10</code> with:</p>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x.at[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>)</span></code></pre></div>
<p>Those rules make compilation and differentiation well-defined.</p>
</section>
<section id="choice-of-library" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="choice-of-library"><span class="header-section-number">6</span> Choice of library</h2>
<ul>
<li>Use <strong>NumPy</strong> for general analysis on CPU.</li>
<li>Use <strong>JAX</strong> when gradients, <code>jit</code>, or accelerators dominate the workload.</li>
</ul>
<p>Learn NumPy first. Its array model is the shared grammar; JAX reuses it.</p>
<p>Learn. NumPy. First. JAX. Reuses. Its. Grammar. Adds. Gradients. JIT.</p>
</section>
<section id="references" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="references"><span class="header-section-number">7</span> References</h2>
<ul>
<li>Oliphant, T. E. <a href="https://csc.ucdavis.edu/~chaos/courses/nlp/Software/NumPyBook.pdf"><em>Guide to NumPy</em></a> — Numeric/Numarray merger and NumPy 1.0 (2006).</li>
<li>Harris, C. R. et al.&nbsp;(2020). <a href="https://doi.org/10.1038/s41586-020-2649-2">Array programming with NumPy</a>. <em>Nature</em>.</li>
<li>Frostig, R., Johnson, M. J. &amp; Leary, C. (2018). <a href="https://mlsys.org/Conferences/2019/doc/2018/146.pdf">Compiling machine learning programs via high-level tracing</a>. MLSys.</li>
</ul>


<script type="pyodide-data">
eyJvcHRpb25zIjp7ImVudiI6eyJQTE9UTFlfUkVOREVSRVIiOiJwbG90bHlfbWltZXR5cGUifSwiaW5kZXhVUkwiOiJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvcHlvZGlkZS92MC4yOC4xL2Z1bGwvIn0sInBhY2thZ2VzIjp7InBrZ3MiOlsicHlvZGlkZV9odHRwIiwibWljcm9waXAiLCJpcHl0aG9uIiwibnVtcHkiXX19
</script>
<script type="ojs-module-contents">
eyJjb250ZW50cyI6W3siY2VsbE5hbWUiOiJweW9kaWRlLTMiLCJpbmxpbmUiOmZhbHNlLCJtZXRob2ROYW1lIjoiaW50ZXJwcmV0Iiwic291cmNlIjoidmlld29mIF9weW9kaWRlX2VkaXRvcl8zID0ge1xuICBjb25zdCB7IFB5b2RpZGVFeGVyY2lzZUVkaXRvciwgYjY0RGVjb2RlIH0gPSB3aW5kb3cuX2V4ZXJjaXNlX29qc19ydW50aW1lO1xuXG4gIGNvbnN0IHNjcmlwdENvbnRlbnQgPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yKGBzY3JpcHRbdHlwZT1cXFwicHlvZGlkZS0zLWNvbnRlbnRzXFxcIl1gKS50ZXh0Q29udGVudDtcbiAgY29uc3QgYmxvY2sgPSBKU09OLnBhcnNlKGI2NERlY29kZShzY3JpcHRDb250ZW50KSk7XG5cbiAgY29uc3Qgb3B0aW9ucyA9IE9iamVjdC5hc3NpZ24oeyBpZDogYHB5b2RpZGUtMy1jb250ZW50c2AgfSwgYmxvY2suYXR0cik7XG4gIGNvbnN0IGVkaXRvciA9IG5ldyBQeW9kaWRlRXhlcmNpc2VFZGl0b3IoXG4gICAgcHlvZGlkZU9qcy5weW9kaWRlUHJvbWlzZSxcbiAgICBibG9jay5jb2RlLFxuICAgIG9wdGlvbnNcbiAgKTtcblxuICByZXR1cm4gZWRpdG9yLmNvbnRhaW5lcjtcbn1cbl9weW9kaWRlX3ZhbHVlXzMgPSBweW9kaWRlT2pzLnByb2Nlc3MoX3B5b2RpZGVfZWRpdG9yXzMsIHt9KTtcbiJ9LHsiY2VsbE5hbWUiOiJweW9kaWRlLTIiLCJpbmxpbmUiOmZhbHNlLCJtZXRob2ROYW1lIjoiaW50ZXJwcmV0Iiwic291cmNlIjoidmlld29mIF9weW9kaWRlX2VkaXRvcl8yID0ge1xuICBjb25zdCB7IFB5b2RpZGVFeGVyY2lzZUVkaXRvciwgYjY0RGVjb2RlIH0gPSB3aW5kb3cuX2V4ZXJjaXNlX29qc19ydW50aW1lO1xuXG4gIGNvbnN0IHNjcmlwdENvbnRlbnQgPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yKGBzY3JpcHRbdHlwZT1cXFwicHlvZGlkZS0yLWNvbnRlbnRzXFxcIl1gKS50ZXh0Q29udGVudDtcbiAgY29uc3QgYmxvY2sgPSBKU09OLnBhcnNlKGI2NERlY29kZShzY3JpcHRDb250ZW50KSk7XG5cbiAgY29uc3Qgb3B0aW9ucyA9IE9iamVjdC5hc3NpZ24oeyBpZDogYHB5b2RpZGUtMi1jb250ZW50c2AgfSwgYmxvY2suYXR0cik7XG4gIGNvbnN0IGVkaXRvciA9IG5ldyBQeW9kaWRlRXhlcmNpc2VFZGl0b3IoXG4gICAgcHlvZGlkZU9qcy5weW9kaWRlUHJvbWlzZSxcbiAgICBibG9jay5jb2RlLFxuICAgIG9wdGlvbnNcbiAgKTtcblxuICByZXR1cm4gZWRpdG9yLmNvbnRhaW5lcjtcbn1cbl9weW9kaWRlX3ZhbHVlXzIgPSBweW9kaWRlT2pzLnByb2Nlc3MoX3B5b2RpZGVfZWRpdG9yXzIsIHt9KTtcbiJ9LHsiY2VsbE5hbWUiOiJweW9kaWRlLTEiLCJpbmxpbmUiOmZhbHNlLCJtZXRob2ROYW1lIjoiaW50ZXJwcmV0Iiwic291cmNlIjoidmlld29mIF9weW9kaWRlX2VkaXRvcl8xID0ge1xuICBjb25zdCB7IFB5b2RpZGVFeGVyY2lzZUVkaXRvciwgYjY0RGVjb2RlIH0gPSB3aW5kb3cuX2V4ZXJjaXNlX29qc19ydW50aW1lO1xuXG4gIGNvbnN0IHNjcmlwdENvbnRlbnQgPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yKGBzY3JpcHRbdHlwZT1cXFwicHlvZGlkZS0xLWNvbnRlbnRzXFxcIl1gKS50ZXh0Q29udGVudDtcbiAgY29uc3QgYmxvY2sgPSBKU09OLnBhcnNlKGI2NERlY29kZShzY3JpcHRDb250ZW50KSk7XG5cbiAgY29uc3Qgb3B0aW9ucyA9IE9iamVjdC5hc3NpZ24oeyBpZDogYHB5b2RpZGUtMS1jb250ZW50c2AgfSwgYmxvY2suYXR0cik7XG4gIGNvbnN0IGVkaXRvciA9IG5ldyBQeW9kaWRlRXhlcmNpc2VFZGl0b3IoXG4gICAgcHlvZGlkZU9qcy5weW9kaWRlUHJvbWlzZSxcbiAgICBibG9jay5jb2RlLFxuICAgIG9wdGlvbnNcbiAgKTtcblxuICByZXR1cm4gZWRpdG9yLmNvbnRhaW5lcjtcbn1cbl9weW9kaWRlX3ZhbHVlXzEgPSBweW9kaWRlT2pzLnByb2Nlc3MoX3B5b2RpZGVfZWRpdG9yXzEsIHt9KTtcbiJ9LHsiY2VsbE5hbWUiOiJweW9kaWRlLXByZWx1ZGUiLCJpbmxpbmUiOmZhbHNlLCJtZXRob2ROYW1lIjoiaW50ZXJwcmV0UXVpZXQiLCJzb3VyY2UiOiJweW9kaWRlT2pzID0ge1xuICBjb25zdCB7XG4gICAgUHlvZGlkZUV2YWx1YXRvcixcbiAgICBQeW9kaWRlRW52aXJvbm1lbnRNYW5hZ2VyLFxuICAgIHNldHVwUHl0aG9uLFxuICAgIHN0YXJ0UHlvZGlkZVdvcmtlcixcbiAgICBiNjREZWNvZGUsXG4gICAgY29sbGFwc2VQYXRoLFxuICB9ID0gd2luZG93Ll9leGVyY2lzZV9vanNfcnVudGltZTtcblxuICBjb25zdCBzdGF0dXNDb250YWluZXIgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChcImV4ZXJjaXNlLWxvYWRpbmctc3RhdHVzXCIpO1xuICBjb25zdCBpbmRpY2F0b3JDb250YWluZXIgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChcImV4ZXJjaXNlLWxvYWRpbmctaW5kaWNhdG9yXCIpO1xuICBpbmRpY2F0b3JDb250YWluZXIuY2xhc3NMaXN0LnJlbW92ZShcImQtbm9uZVwiKTtcblxuICBsZXQgc3RhdHVzVGV4dCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoXCJkaXZcIilcbiAgc3RhdHVzVGV4dC5jbGFzc0xpc3QgPSBcImV4ZXJjaXNlLWxvYWRpbmctZGV0YWlsc1wiO1xuICBzdGF0dXNUZXh0ID0gc3RhdHVzQ29udGFpbmVyLmFwcGVuZENoaWxkKHN0YXR1c1RleHQpO1xuICBzdGF0dXNUZXh0LnRleHRDb250ZW50ID0gYEluaXRpYWxpc2VgO1xuXG4gIC8vIEhvaXN0IGluZGljYXRvciBvdXQgZnJvbSBmaW5hbCBzbGlkZSB3aGVuIHJ1bm5pbmcgdW5kZXIgcmV2ZWFsXG4gIGNvbnN0IHJldmVhbFN0YXR1cyA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoXCIucmV2ZWFsIC5leGVyY2lzZS1sb2FkaW5nLWluZGljYXRvclwiKTtcbiAgaWYgKHJldmVhbFN0YXR1cykge1xuICAgIHJldmVhbFN0YXR1cy5yZW1vdmUoKTtcbiAgICBkb2N1bWVudC5xdWVyeVNlbGVjdG9yKFwiLnJldmVhbCA+IC5zbGlkZXNcIikuYXBwZW5kQ2hpbGQocmV2ZWFsU3RhdHVzKTtcbiAgfVxuXG4gIC8vIE1ha2UgYW55IHJldmVhbCBzbGlkZXMgd2l0aCBsaXZlIGNlbGxzIHNjcm9sbGFibGVcbiAgZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbChcIi5yZXZlYWwgLmV4ZXJjaXNlLWNlbGxcIikuZm9yRWFjaCgoZWwpID0+IHtcbiAgICBlbC5jbG9zZXN0KCdzZWN0aW9uLnNsaWRlJykuY2xhc3NMaXN0LmFkZChcInNjcm9sbGFibGVcIik7XG4gIH0pXG5cbiAgLy8gUHlvZGlkZSBzdXBwbGVtZW50YWwgZGF0YSBhbmQgb3B0aW9uc1xuICBjb25zdCBkYXRhQ29udGVudCA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoYHNjcmlwdFt0eXBlPVxcXCJweW9kaWRlLWRhdGFcXFwiXWApLnRleHRDb250ZW50O1xuICBjb25zdCBkYXRhID0gSlNPTi5wYXJzZShiNjREZWNvZGUoZGF0YUNvbnRlbnQpKTtcblxuICAvLyBHcmFiIGxpc3Qgb2YgcmVzb3VyY2VzIHRvIGJlIGRvd25sb2FkZWRcbiAgY29uc3QgZmlsZXNDb250ZW50ID0gZG9jdW1lbnQucXVlcnlTZWxlY3Rvcihgc2NyaXB0W3R5cGU9XFxcInZmcy1maWxlXFxcIl1gKS50ZXh0Q29udGVudDtcbiAgY29uc3QgZmlsZXMgPSBKU09OLnBhcnNlKGI2NERlY29kZShmaWxlc0NvbnRlbnQpKTtcblxuICBsZXQgcHlvZGlkZVByb21pc2UgPSAoYXN5bmMgKCkgPT4ge1xuICAgIHN0YXR1c1RleHQudGV4dENvbnRlbnQgPSBgRG93bmxvYWRpbmcgUHlvZGlkZWA7XG4gICAgY29uc3QgcHlvZGlkZSA9IGF3YWl0IHN0YXJ0UHlvZGlkZVdvcmtlcihkYXRhLm9wdGlvbnMpO1xuXG4gICAgc3RhdHVzVGV4dC50ZXh0Q29udGVudCA9IGBEb3dubG9hZGluZyBwYWNrYWdlOiBtaWNyb3BpcGA7XG4gICAgYXdhaXQgcHlvZGlkZS5sb2FkUGFja2FnZShcIm1pY3JvcGlwXCIpO1xuICAgIGNvbnN0IG1pY3JvcGlwID0gYXdhaXQgcHlvZGlkZS5weWltcG9ydChcIm1pY3JvcGlwXCIpO1xuICAgIGF3YWl0IGRhdGEucGFja2FnZXMucGtncy5tYXAoKHBrZykgPT4gKCkgPT4ge1xuICAgICAgc3RhdHVzVGV4dC50ZXh0Q29udGVudCA9IGBEb3dubG9hZGluZyBwYWNrYWdlOiAke3BrZ31gO1xuICAgICAgcmV0dXJuIG1pY3JvcGlwLmluc3RhbGwocGtnKTtcbiAgICB9KS5yZWR1Y2UoKGN1ciwgbmV4dCkgPT4gY3VyLnRoZW4obmV4dCksIFByb21pc2UucmVzb2x2ZSgpKTtcbiAgICBhd2FpdCBtaWNyb3BpcC5kZXN0cm95KCk7XG5cbiAgICAvLyBEb3dubG9hZCBhbmQgaW5zdGFsbCByZXNvdXJjZXNcbiAgICBhd2FpdCBmaWxlcy5tYXAoKGZpbGUpID0+IGFzeW5jICgpID0+IHtcbiAgICAgIGNvbnN0IG5hbWUgPSBmaWxlLnN1YnN0cmluZyhmaWxlLmxhc3RJbmRleE9mKCcvJykgKyAxKTtcbiAgICAgIHN0YXR1c1RleHQudGV4dENvbnRlbnQgPSBgRG93bmxvYWRpbmcgcmVzb3VyY2U6ICR7bmFtZX1gO1xuICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmZXRjaChmaWxlKTtcbiAgICAgIGlmICghcmVzcG9uc2Uub2spIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBDYW4ndCBkb3dubG9hZCBcXGAke2ZpbGV9XFxgLiBFcnJvciAke3Jlc3BvbnNlLnN0YXR1c306IFwiJHtyZXNwb25zZS5zdGF0dXNUZXh0fVwiLmApO1xuICAgICAgfVxuICAgICAgY29uc3QgZGF0YSA9IGF3YWl0IHJlc3BvbnNlLmFycmF5QnVmZmVyKCk7XG5cbiAgICAgIC8vIFN0b3JlIFVSTHMgaW4gdGhlIGN3ZCB3aXRob3V0IGFueSBzdWJkaXJlY3Rvcnkgc3RydWN0dXJlXG4gICAgICBpZiAoZmlsZS5pbmNsdWRlcyhcIjovL1wiKSkge1xuICAgICAgICBmaWxlID0gbmFtZTtcbiAgICAgIH1cblxuICAgICAgLy8gQ29sbGFwc2UgaGlnaGVyIGRpcmVjdG9yeSBzdHJ1Y3R1cmVcbiAgICAgIGZpbGUgPSBjb2xsYXBzZVBhdGgoZmlsZSk7XG5cbiAgICAgIC8vIENyZWF0ZSBkaXJlY3RvcnkgdHJlZSwgaWdub3JpbmcgXCJkaXJlY3RvcnkgZXhpc3RzXCIgVkZTIGVycm9yc1xuICAgICAgY29uc3QgcGFydHMgPSBmaWxlLnNwbGl0KCcvJykuc2xpY2UoMCwgLTEpO1xuICAgICAgbGV0IHBhdGggPSAnJztcbiAgICAgIHdoaWxlIChwYXJ0cy5sZW5ndGggPiAwKSB7XG4gICAgICAgIHBhdGggKz0gcGFydHMuc2hpZnQoKSArICcvJztcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICBhd2FpdCBweW9kaWRlLkZTLm1rZGlyKHBhdGgpO1xuICAgICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgICAgaWYgKGUubmFtZSAhPT0gXCJFcnJub0Vycm9yXCIpIHRocm93IGU7XG4gICAgICAgICAgaWYgKGUuZXJybm8gIT09IDIwKSB7XG4gICAgICAgICAgICBjb25zdCBlcnJvclRleHRQdHIgPSBhd2FpdCBweW9kaWRlLl9tb2R1bGUuX3N0cmVycm9yKGUuZXJybm8pO1xuICAgICAgICAgICAgY29uc3QgZXJyb3JUZXh0ID0gYXdhaXQgcHlvZGlkZS5fbW9kdWxlLlVURjhUb1N0cmluZyhlcnJvclRleHRQdHIpO1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBGaWxlc3lzdGVtIEVycm9yICR7ZS5lcnJub30gXCIke2Vycm9yVGV4dH1cIi5gKTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgLy8gV3JpdGUgdGhpcyBmaWxlIHRvIHRoZSBWRlNcbiAgICAgIHRyeSB7XG4gICAgICAgIHJldHVybiBhd2FpdCBweW9kaWRlLkZTLndyaXRlRmlsZShmaWxlLCBuZXcgVWludDhBcnJheShkYXRhKSk7XG4gICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIGlmIChlLm5hbWUgIT09IFwiRXJybm9FcnJvclwiKSB0aHJvdyBlO1xuICAgICAgICBjb25zdCBlcnJvclRleHRQdHIgPSBhd2FpdCBweW9kaWRlLl9tb2R1bGUuX3N0cmVycm9yKGUuZXJybm8pO1xuICAgICAgICBjb25zdCBlcnJvclRleHQgPSBhd2FpdCBweW9kaWRlLl9tb2R1bGUuVVRGOFRvU3RyaW5nKGVycm9yVGV4dFB0cik7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgRmlsZXN5c3RlbSBFcnJvciAke2UuZXJybm99IFwiJHtlcnJvclRleHR9XCIuYCk7XG4gICAgICB9XG4gICAgfSkucmVkdWNlKChjdXIsIG5leHQpID0+IGN1ci50aGVuKG5leHQpLCBQcm9taXNlLnJlc29sdmUoKSk7XG5cbiAgICBzdGF0dXNUZXh0LnRleHRDb250ZW50ID0gYFB5b2RpZGUgZW52aXJvbm1lbnQgc2V0dXBgO1xuICAgIGF3YWl0IHNldHVwUHl0aG9uKHB5b2RpZGUpO1xuXG4gICAgc3RhdHVzVGV4dC5yZW1vdmUoKTtcbiAgICBpZiAoc3RhdHVzQ29udGFpbmVyLmNoaWxkcmVuLmxlbmd0aCA9PSAwKSB7XG4gICAgICBzdGF0dXNDb250YWluZXIucGFyZW50Tm9kZS5yZW1vdmUoKTtcbiAgICB9XG4gICAgcmV0dXJuIHB5b2RpZGU7XG4gIH0pKCkuY2F0Y2goKGVycikgPT4ge1xuICAgIHN0YXR1c1RleHQuc3R5bGUuY29sb3IgPSBcInZhcigtLWV4ZXJjaXNlLWVkaXRvci1obC1lciwgI0FEMDAwMClcIjtcbiAgICBzdGF0dXNUZXh0LnRleHRDb250ZW50ID0gZXJyLm1lc3NhZ2U7XG4gICAgLy9pbmRpY2F0b3JDb250YWluZXIucXVlcnlTZWxlY3RvcihcIi5zcGlubmVyLWdyb3dcIikuY2xhc3NMaXN0LmFkZChcImQtbm9uZVwiKTtcbiAgICB0aHJvdyBlcnI7XG4gIH0pO1xuXG4gIC8vIEtlZXAgdHJhY2sgb2YgaW5pdGlhbCBPSlMgYmxvY2sgcmVuZGVyXG4gIGNvbnN0IHJlbmRlcmVkT2pzID0ge307XG5cbiAgY29uc3QgcHJvY2VzcyA9IGFzeW5jIChjb250ZXh0LCBpbnB1dHMpID0+IHtcbiAgICBjb25zdCBweW9kaWRlID0gYXdhaXQgcHlvZGlkZVByb21pc2U7XG4gICAgY29uc3QgZXZhbHVhdG9yID0gbmV3IFB5b2RpZGVFdmFsdWF0b3IocHlvZGlkZSwgY29udGV4dCk7XG4gICAgYXdhaXQgZXZhbHVhdG9yLnByb2Nlc3MoaW5wdXRzKTtcbiAgICByZXR1cm4gZXZhbHVhdG9yLmNvbnRhaW5lcjtcbiAgfVxuXG4gIHJldHVybiB7XG4gICAgcHlvZGlkZVByb21pc2UsXG4gICAgcmVuZGVyZWRPanMsXG4gICAgcHJvY2VzcyxcbiAgfTtcbn1cbiJ9XX0=
</script>
<div id="exercise-loading-indicator" class="exercise-loading-indicator d-none d-flex align-items-center gap-2">
<div id="exercise-loading-status" class="d-flex gap-2">

</div>
<div class="spinner-grow spinner-grow-sm">

</div>
</div>
<script type="vfs-file">
W10=
</script>
</section>

 ]]></description>
  <category>Numerical Methods</category>
  <category>Python</category>
  <guid>https://project-delphi.github.io/ml-blog/posts/numpy-to-jax/</guid>
  <pubDate>Fri, 28 Aug 2026 05:00:00 GMT</pubDate>
  <media:content url="https://project-delphi.github.io/ml-blog/posts/numpy-to-jax/cover.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Tensor Factorizations and Tensor Inverses</title>
  <dc:creator>Ravi Kalia</dc:creator>
  <link>https://project-delphi.github.io/ml-blog/posts/tensor-factorizations/</link>
  <description><![CDATA[ 





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://project-delphi.github.io/ml-blog/posts/tensor-factorizations/cover.png" class="img-fluid figure-img"></p>
<figcaption>Tensor Factorizations and Tensor Inverses</figcaption>
</figure>
</div>
<p>A tensor <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BX%7D%5Cin%5Cmathbb%7BR%7D%5E%7BI_1%5Ctimes%5Ccdots%5Ctimes%20I_N%7D"> is an <img src="https://latex.codecogs.com/png.latex?N">-way array. The <img src="https://latex.codecogs.com/png.latex?N=2"> case is a matrix: rows and columns only.</p>
<p>A matrix factorization or inverse needs a matrix. The standard reduction is a <strong>mode-<img src="https://latex.codecogs.com/png.latex?n"> unfolding</strong> <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BX%7D_%7B(n)%7D%5Cin%5Cmathbb%7BR%7D%5E%7BI_n%5Ctimes%5Cprod_%7Bk%5Cneq%20n%7DI_k%7D">, whose columns are the mode-<img src="https://latex.codecogs.com/png.latex?n"> fibers. SVD of that unfolding is the Eckart–Young optimum for the unfolding, not for <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BX%7D">. The left singular vectors of <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BX%7D_%7B(1)%7D"> mix modes <img src="https://latex.codecogs.com/png.latex?2"> through <img src="https://latex.codecogs.com/png.latex?N">; there is no reason those vectors should be Kruskal factors, Tucker factors, or t-product singular tubes.</p>
<p>Kruskal uniqueness, multilinear rank, and the t-product algebra are properties of the multiway layout. They do not survive flattening. The constructions below keep the modes separate; <a href="../matrix-factorizations/">Matrix Factorizations as Optimization Problems</a> is the <img src="https://latex.codecogs.com/png.latex?N=2"> case of the same questions.</p>
<section id="synthetic-tensor" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="synthetic-tensor"><span class="header-section-number">1</span> Synthetic tensor</h2>
<p>This post uses a synthetic <img src="https://latex.codecogs.com/png.latex?12%5Ctimes%2012%5Ctimes%2012"> array.</p>
<p>Provenance:</p>
<ul>
<li>Generated as a rank-3 CP model <img src="https://latex.codecogs.com/png.latex?%5Csum_%7Br=1%7D%5E%7B3%7D%20a_r%5Ccirc%20b_r%5Ccirc%20c_r"> plus i.i.d. Gaussian noise, seed fixed.</li>
<li>Each generating vector is a localized Gaussian bump, not measured data. The three outer products sit in different corners of the cube, so a factorization that recovers the components is visible as three blobs.</li>
<li>The array stands in for a 3-way table (subject <img src="https://latex.codecogs.com/png.latex?%5Ctimes"> feature <img src="https://latex.codecogs.com/png.latex?%5Ctimes"> condition) whose modes should stay separate. A real table of that shape is high-dimensional and not drawable as voxels.</li>
<li>The objective is to recover the known CP/Tucker structure and to invert four different products on this object, or on a square unfolding of it.</li>
<li>A flattened SVD attributes variance to a mixed mode; an overspecified CP rank invents a fourth blob. Either error mis-assigns which condition drives which feature.</li>
<li>Tensor methods fit because Kruskal uniqueness and mode-wise compression are properties of this layout, not of any matrix obtained from it.</li>
</ul>
<div id="4e022948" class="cell" data-execution_count="1">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> matplotlib.pyplot <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> plt</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> np</span>
<span id="cb1-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> tensorly <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> tl</span>
<span id="cb1-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> matplotlib.colors <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> to_rgb</span>
<span id="cb1-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> tensorly.cp_tensor <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> cp_to_tensor</span>
<span id="cb1-6"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> tensorly.decomposition <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> parafac, tensor_train, tucker</span>
<span id="cb1-7"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> tensorly.tucker_tensor <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> tucker_to_tensor</span>
<span id="cb1-8"></span>
<span id="cb1-9">tl.set_backend(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"numpy"</span>)</span>
<span id="cb1-10">np.set_printoptions(precision<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, suppress<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb1-11">rng <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.random.default_rng(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>)</span>
<span id="cb1-12"></span>
<span id="cb1-13">ACCENT <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#4A3AA7"</span></span>
<span id="cb1-14">TEAL <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#2A9D8F"</span></span>
<span id="cb1-15">CORAL <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#E07A5F"</span></span>
<span id="cb1-16">GOLD <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#E9C46A"</span></span>
<span id="cb1-17"></span>
<span id="cb1-18">n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span></span>
<span id="cb1-19">t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linspace(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, n)</span>
<span id="cb1-20">centres <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.55</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.45</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.4</span>), (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.15</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.55</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.35</span>), (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.2</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.55</span>)]</span>
<span id="cb1-21">widths <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.28</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.32</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.26</span>]</span>
<span id="cb1-22"></span>
<span id="cb1-23"></span>
<span id="cb1-24"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> bump(center, width):</span>
<span id="cb1-25">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> np.exp(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> ((t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> center) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> width) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb1-26"></span>
<span id="cb1-27"></span>
<span id="cb1-28">terms <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb1-29"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> (ca, cb, cc), w <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(centres, widths):</span>
<span id="cb1-30">    a, b, cvec <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> bump(ca, w), bump(cb, w), bump(cc, w)</span>
<span id="cb1-31">    terms.append(a[:, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> b[<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, :, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> cvec[<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, :])</span>
<span id="cb1-32"></span>
<span id="cb1-33">X_clean <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(terms)</span>
<span id="cb1-34">X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X_clean <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.08</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> X_clean.std() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> rng.normal(size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>X_clean.shape)</span>
<span id="cb1-35"></span>
<span id="cb1-36"></span>
<span id="cb1-37"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> draw_voxels(ax, T, color, thresh_frac<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.18</span>, box<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>):</span>
<span id="cb1-38">    mag <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(T)</span>
<span id="cb1-39">    peak <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mag.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-12</span></span>
<span id="cb1-40">    filled <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mag <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> thresh_frac <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> peak</span>
<span id="cb1-41">    rgb <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> to_rgb(color)</span>
<span id="cb1-42">    fc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros(T.shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>,))</span>
<span id="cb1-43">    fc[..., <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], fc[..., <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], fc[..., <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rgb</span>
<span id="cb1-44">    fc[..., <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.where(filled, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> mag <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> peak, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>)</span>
<span id="cb1-45">    ax.voxels(filled, facecolors<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>fc, edgecolor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"none"</span>)</span>
<span id="cb1-46">    ax.set_xlim(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, box)</span>
<span id="cb1-47">    ax.set_ylim(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, box)</span>
<span id="cb1-48">    ax.set_zlim(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, box)</span>
<span id="cb1-49">    ax.set_box_aspect((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), zoom<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.88</span>)</span>
<span id="cb1-50">    ax.set_axis_off()</span>
<span id="cb1-51">    ax.view_init(elev<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">18</span>, azim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">40</span>)</span>
<span id="cb1-52"></span>
<span id="cb1-53"></span>
<span id="cb1-54"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> letterbox_cover(fig, path<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cover.png"</span>, size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1200</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">630</span>), pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.2</span>):</span>
<span id="cb1-55">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Fit the figure into 1200×630 by trimming whitespace, then letterboxing."""</span></span>
<span id="cb1-56">    <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> io</span>
<span id="cb1-57"></span>
<span id="cb1-58">    <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> PIL <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Image</span>
<span id="cb1-59"></span>
<span id="cb1-60">    buf <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> io.BytesIO()</span>
<span id="cb1-61">    fig.savefig(</span>
<span id="cb1-62">        buf,</span>
<span id="cb1-63">        dpi<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">160</span>,</span>
<span id="cb1-64">        bbox_inches<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tight"</span>,</span>
<span id="cb1-65">        pad_inches<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>pad,</span>
<span id="cb1-66">        facecolor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"white"</span>,</span>
<span id="cb1-67">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">format</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"png"</span>,</span>
<span id="cb1-68">    )</span>
<span id="cb1-69">    buf.seek(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb1-70">    raw <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Image.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">open</span>(buf).convert(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"RGB"</span>)</span>
<span id="cb1-71">    arr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.asarray(raw)</span>
<span id="cb1-72">    ink <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (arr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">250</span>).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">any</span>(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb1-73">    rows <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.where(ink.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">any</span>(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb1-74">    cols <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.where(ink.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">any</span>(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>))[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb1-75">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(rows) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(cols):</span>
<span id="cb1-76">        margin <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">18</span></span>
<span id="cb1-77">        top <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(rows[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> margin)</span>
<span id="cb1-78">        bottom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(raw.height, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(rows[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> margin)</span>
<span id="cb1-79">        left <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(cols[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> margin)</span>
<span id="cb1-80">        right <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(raw.width, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(cols[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> margin)</span>
<span id="cb1-81">        raw <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> raw.crop((left, top, right, bottom))</span>
<span id="cb1-82">    width, height <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> size</span>
<span id="cb1-83">    scale <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(width <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> raw.width, height <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> raw.height)</span>
<span id="cb1-84">    new_w <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(raw.width <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> scale))</span>
<span id="cb1-85">    new_h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(raw.height <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> scale))</span>
<span id="cb1-86">    raw <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> raw.resize((new_w, new_h), Image.Resampling.LANCZOS)</span>
<span id="cb1-87">    canvas <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Image.new(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"RGB"</span>, (width, height), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">255</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">255</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">255</span>))</span>
<span id="cb1-88">    canvas.paste(raw, ((width <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> new_w) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, (height <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> new_h) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>))</span>
<span id="cb1-89">    canvas.save(path)</span></code></pre></div>
</details>
</div>
</section>
<section id="cp-decomposition" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="cp-decomposition"><span class="header-section-number">2</span> CP decomposition</h2>
<p>An entrywise rank-1 third-order tensor is an outer product <img src="https://latex.codecogs.com/png.latex?(a%5Ccirc%20b%5Ccirc%20c)_%7Bijk%7D=a_i%20b_j%20c_k">. CANDECOMP/PARAFAC (Hitchcock 1927; Harshman 1970; Carroll and Chang 1970) writes <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BX%7D"> as a sum of those:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7BX%7D%5Capprox%5B%5C!%5B%5Clambda;A,B,C%5D%5C!%5D=%5Csum_%7Br=1%7D%5E%7BR%7D%5Clambda_r%5C,a_r%5Ccirc%20b_r%5Ccirc%20c_r.%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?A,B,C"> have <img src="https://latex.codecogs.com/png.latex?R"> columns. <img src="https://latex.codecogs.com/png.latex?%5Clambda%5Cin%5Cmathbb%7BR%7D%5E%7BR%7D"> absorbs a scale so that the columns of <img src="https://latex.codecogs.com/png.latex?A,B,C"> can be normalized.</p>
<ul>
<li><strong>Rank</strong> <img src="https://latex.codecogs.com/png.latex?R"> is the smallest number of rank-1 terms. It is not the rank of any unfolding, and it can exceed <img src="https://latex.codecogs.com/png.latex?%5Cmin(I,J,K)">.</li>
<li><strong>Storage</strong> is <img src="https://latex.codecogs.com/png.latex?R(I+J+K)"> against <img src="https://latex.codecogs.com/png.latex?IJK">. For this <img src="https://latex.codecogs.com/png.latex?12%5Ctimes%2012%5Ctimes%2012"> array a rank-3 model stores <img src="https://latex.codecogs.com/png.latex?108"> numbers instead of <img src="https://latex.codecogs.com/png.latex?1728">.</li>
<li><strong>Indeterminacy.</strong> Permuting the <img src="https://latex.codecogs.com/png.latex?R"> terms does not change the sum. Rescaling <img src="https://latex.codecogs.com/png.latex?a_r%5Cleftarrow%5Calpha%20a_r">, <img src="https://latex.codecogs.com/png.latex?b_r%5Cleftarrow%5Cbeta%20b_r"> with <img src="https://latex.codecogs.com/png.latex?%5Calpha%5Cbeta%5Cgamma=1"> does not either. Uniqueness is always modulo that group.</li>
<li><strong>Kruskal uniqueness.</strong> Let <img src="https://latex.codecogs.com/png.latex?k_A"> be the largest <img src="https://latex.codecogs.com/png.latex?k"> such that every <img src="https://latex.codecogs.com/png.latex?k"> columns of <img src="https://latex.codecogs.com/png.latex?A"> are linearly independent. If <img src="https://latex.codecogs.com/png.latex?k_A+k_B+k_C%5Cge%202R+2">, the factors are unique up to that group. No unfolding SVD has an analogous statement.</li>
<li><strong>Fitting.</strong> Alternating least squares: fix <img src="https://latex.codecogs.com/png.latex?B"> and <img src="https://latex.codecogs.com/png.latex?C">, solve a linear least-squares problem for <img src="https://latex.codecogs.com/png.latex?A">, cycle. TensorLy (Kossaifi et al.&nbsp;2019) does that; Kolda and Bader (2009) survey the algebra.</li>
</ul>
<p>If two generating vectors are collinear, or if <img src="https://latex.codecogs.com/png.latex?R"> is chosen larger than the true rank, Kruskal’s condition fails and ALS can split one blob across two components.</p>
<div id="cell-fig-cp" class="cell" data-fig-height="6.3" data-fig-width="12" data-execution_count="2">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1">cp_weights, cp_factors <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> parafac(X, rank<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, n_iter_max<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">200</span>, init<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"svd"</span>)</span>
<span id="cb2-2">X_cp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cp_to_tensor((cp_weights, cp_factors))</span>
<span id="cb2-3">cp_rel <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.norm(X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> X_cp) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> np.linalg.norm(X)</span>
<span id="cb2-4"></span>
<span id="cb2-5">rank1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb2-6"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> r <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>):</span>
<span id="cb2-7">    a, b, cvec <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (cp_factors[m][:, r] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (cp_weights[r] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> m <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>))</span>
<span id="cb2-8">    rank1.append(a[:, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> b[<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, :, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> cvec[<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, :])</span>
<span id="cb2-9"></span>
<span id="cb2-10">fig, axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(</span>
<span id="cb2-11">    <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6.3</span>), subplot_kw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"projection"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"3d"</span>}</span>
<span id="cb2-12">)</span>
<span id="cb2-13">panels <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [(X, ACCENT, <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"$\mathcal</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{X}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">$"</span>)] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(</span>
<span id="cb2-14">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(rank1, [TEAL, CORAL, GOLD], [<span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">rf"$a_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>r<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">\circ b_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>r<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">\circ c_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>r<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">$"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> r <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)])</span>
<span id="cb2-15">)</span>
<span id="cb2-16"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> ax, (vol, color, title) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(axes, panels):</span>
<span id="cb2-17">    draw_voxels(ax, vol, color)</span>
<span id="cb2-18">    ax.set_title(title, pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">11</span>)</span>
<span id="cb2-19">fig.subplots_adjust(left<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.01</span>, right<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.99</span>, top<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.90</span>, bottom<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span>, wspace<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span>)</span>
<span id="cb2-20"></span>
<span id="cb2-21"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Cover: 2×2 of the same panels, letterboxed to 1200×630 so nothing is cropped.</span></span>
<span id="cb2-22">fig_c, axes_c <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">9.5</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8.2</span>), subplot_kw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"projection"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"3d"</span>})</span>
<span id="cb2-23"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> ax, (vol, color, title) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(axes_c.ravel(), panels):</span>
<span id="cb2-24">    draw_voxels(ax, vol, color)</span>
<span id="cb2-25">    ax.set_title(title, pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>)</span>
<span id="cb2-26">fig_c.subplots_adjust(left<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span>, right<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.98</span>, top<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.92</span>, bottom<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span>, wspace<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span>, hspace<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.08</span>)</span>
<span id="cb2-27">letterbox_cover(fig_c)</span>
<span id="cb2-28">plt.close(fig_c)</span>
<span id="cb2-29"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"CP relative Frobenius error: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>cp_rel<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>CP relative Frobenius error: 0.0643</code></pre>
</div>
<div class="cell-output cell-output-display">
<div id="fig-cp" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-cp-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/tensor-factorizations/index_files/figure-html/fig-cp-output-2.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-cp-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Rank-3 CP fit on a <img src="https://latex.codecogs.com/png.latex?12%5Ctimes%2012%5Ctimes%2012"> cube. Purple is the noisy tensor; teal, coral, and gold are the three rank-1 outer products, each occupying a different corner.
</figcaption>
</figure>
</div>
</div>
</div>
<p>The printed residual is <img src="https://latex.codecogs.com/png.latex?%5C%7C%5Cmathcal%7BX%7D-%5Chat%7B%5Cmathcal%7BX%7D%7D%5C%7C_F/%5C%7C%5Cmathcal%7BX%7D%5C%7C_F"> for the rank-3 fit. Noise was added at <img src="https://latex.codecogs.com/png.latex?0.08"> times the clean-tensor scale, so a residual near <img src="https://latex.codecogs.com/png.latex?0.06"> is the noise floor, not a missed component. The three blobs sit in the generating corners, up to the permutation ALS is allowed.</p>
</section>
<section id="tucker-decomposition" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="tucker-decomposition"><span class="header-section-number">3</span> Tucker decomposition</h2>
<p>Mode-<img src="https://latex.codecogs.com/png.latex?n"> multiplication by a matrix <img src="https://latex.codecogs.com/png.latex?U"> contracts one mode:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(%5Cmathcal%7BX%7D%5Ctimes_n%20U)_%7Bi_1%5Cldots%20j%5Cldots%20i_N%7D=%5Csum_%7Bi_n%7D%20U_%7Bj%20i_n%7D%5C,%5Cmathcal%7BX%7D_%7Bi_1%5Cldots%20i_n%5Cldots%20i_N%7D.%0A"></p>
<p>Tucker (1966) inserts a core tensor in the middle of three such products:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7BX%7D%5Capprox%5Cmathcal%7BG%7D%5Ctimes_1%20U%5Ctimes_2%20V%5Ctimes_3%20W.%0A"></p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BG%7D%5Cin%5Cmathbb%7BR%7D%5E%7BR_1%5Ctimes%20R_2%5Ctimes%20R_3%7D"> is <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BX%7D"> expressed in the factor bases <img src="https://latex.codecogs.com/png.latex?U,V,W">. A large entry <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BG%7D_%7Br_1%20r_2%20r_3%7D"> means column <img src="https://latex.codecogs.com/png.latex?r_1"> of <img src="https://latex.codecogs.com/png.latex?U">, column <img src="https://latex.codecogs.com/png.latex?r_2"> of <img src="https://latex.codecogs.com/png.latex?V">, and column <img src="https://latex.codecogs.com/png.latex?r_3"> of <img src="https://latex.codecogs.com/png.latex?W"> interact.</li>
<li><strong>Multilinear rank</strong> <img src="https://latex.codecogs.com/png.latex?(R_1,R_2,R_3)"> is the triple of unfolding ranks. The three numbers need not be equal.</li>
<li>CP is the special case in which <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BG%7D"> is superdiagonal: <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BG%7D_%7Br_1%20r_2%20r_3%7D=0"> unless <img src="https://latex.codecogs.com/png.latex?r_1=r_2=r_3">. Off-superdiagonal mass is structure a CP model of the same order cannot represent.</li>
<li>Storage is <img src="https://latex.codecogs.com/png.latex?R_1%20R_2%20R_3%20+%20I%20R_1%20+%20J%20R_2%20+%20K%20R_3">. Factors are unique only up to invertible mixing: <img src="https://latex.codecogs.com/png.latex?U%5Cleftarrow%20U%20M"> can be absorbed into <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BG%7D">. That is weaker uniqueness than Kruskal’s.</li>
</ul>
<div id="cell-fig-tucker" class="cell" data-fig-height="6.3" data-fig-width="12" data-execution_count="3">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1">tucker_rank <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>]</span>
<span id="cb4-2">core, tucker_factors <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tucker(X, rank<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>tucker_rank, n_iter_max<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">200</span>, init<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"svd"</span>)</span>
<span id="cb4-3">X_tucker <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tucker_to_tensor((core, tucker_factors))</span>
<span id="cb4-4">tucker_rel <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.norm(X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> X_tucker) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> np.linalg.norm(X)</span>
<span id="cb4-5"></span>
<span id="cb4-6">G_pad <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros_like(X)</span>
<span id="cb4-7">r1, r2, r3 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> core.shape</span>
<span id="cb4-8">G_pad[:r1, :r2, :r3] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> core</span>
<span id="cb4-9"></span>
<span id="cb4-10">fig <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.figure(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6.3</span>))</span>
<span id="cb4-11">gs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fig.add_gridspec(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>, hspace<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.42</span>, wspace<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.28</span>, left<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span>, right<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.96</span>, top<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.90</span>, bottom<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.08</span>)</span>
<span id="cb4-12">ax_x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fig.add_subplot(gs[:, :<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>], projection<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"3d"</span>)</span>
<span id="cb4-13">ax_g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fig.add_subplot(gs[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>], projection<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"3d"</span>)</span>
<span id="cb4-14">draw_voxels(ax_x, X, ACCENT)</span>
<span id="cb4-15">ax_x.set_title(<span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"$\mathcal</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{X}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">$"</span>, pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb4-16">draw_voxels(ax_g, G_pad, TEAL)</span>
<span id="cb4-17">ax_g.set_title(<span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"core $\mathcal</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{G}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">$ (padded)"</span>, pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb4-18"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, (factor, name) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(tucker_factors, [<span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"$U$"</span>, <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"$V$"</span>, <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"$W$"</span>])):</span>
<span id="cb4-19">    ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fig.add_subplot(gs[i, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>:])</span>
<span id="cb4-20">    im <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ax.imshow(factor, aspect<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"auto"</span>, cmap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"magma"</span>)</span>
<span id="cb4-21">    ax.set_ylabel(name, rotation<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, labelpad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">14</span>, va<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"center"</span>)</span>
<span id="cb4-22">    ax.set_xticks([])</span>
<span id="cb4-23">    ax.set_yticks([])</span>
<span id="cb4-24">    fig.colorbar(im, ax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ax, fraction<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.046</span>, pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.04</span>)</span>
<span id="cb4-25"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Tucker relative Frobenius error: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>tucker_rel<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb4-26"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"core shape: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>core<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">; |G| / |X| entries: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>core<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>size <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> X<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>size<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>Tucker relative Frobenius error: 0.0629
core shape: (4, 4, 3); |G| / |X| entries: 0.028</code></pre>
</div>
<div class="cell-output cell-output-display">
<div id="fig-tucker" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-tucker-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/tensor-factorizations/index_files/figure-html/fig-tucker-output-2.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-tucker-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: Tucker compression of the same cube. The core <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BG%7D"> is <img src="https://latex.codecogs.com/png.latex?4%5Ctimes%204%5Ctimes%203">, shown in the same <img src="https://latex.codecogs.com/png.latex?12%5Ctimes%2012%5Ctimes%2012"> frame. Heatmaps are the factor matrices <img src="https://latex.codecogs.com/png.latex?U,V,W">.
</figcaption>
</figure>
</div>
</div>
</div>
<p>The printed residual is essentially the CP residual: the data <em>are</em> rank-3 CP, so a <img src="https://latex.codecogs.com/png.latex?(4,4,3)"> core has spare degrees of freedom that fit noise. The padded plot places that small core in the corner of the original box. The heatmaps are <img src="https://latex.codecogs.com/png.latex?12%5Ctimes%204">, <img src="https://latex.codecogs.com/png.latex?12%5Ctimes%204">, and <img src="https://latex.codecogs.com/png.latex?12%5Ctimes%203">; each column is one factor vector along a mode.</p>
</section>
<section id="hosvd-and-hooi" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="hosvd-and-hooi"><span class="header-section-number">4</span> HOSVD and HOOI</h2>
<p>Higher-order SVD (De Lathauwer, De Moor, Vandewalle 2000) builds a Tucker factorization from three independent matrix SVDs:</p>
<ol type="1">
<li>Unfold <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BX%7D"> along mode <img src="https://latex.codecogs.com/png.latex?n"> and compute <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BX%7D_%7B(n)%7D=U%5E%7B(n)%7DS%5E%7B(n)%7D(V%5E%7B(n)%7D)%5E%5Ctop">.</li>
<li>Keep the leading <img src="https://latex.codecogs.com/png.latex?R_n"> left singular vectors as the factor <img src="https://latex.codecogs.com/png.latex?U%5E%7B(n)%7D">. Those columns are orthonormal, unlike generic Tucker factors.</li>
<li>Form the core by projecting onto those bases: <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BG%7D=%5Cmathcal%7BX%7D%5Ctimes_1%20(U%5E%7B(1)%7D)%5E%5Ctop%5Ctimes_2%20(U%5E%7B(2)%7D)%5E%5Ctop%5Ctimes_3%20(U%5E%7B(3)%7D)%5E%5Ctop">.</li>
</ol>
<p>This is a truncated SVD of each unfolding. It is <strong>not</strong> Eckart–Young for the tensor: the three truncations are not jointly optimal, so a different choice of subspaces can give a smaller <img src="https://latex.codecogs.com/png.latex?%5C%7C%5Cmathcal%7BX%7D-%5Cmathcal%7BG%7D%5Ctimes_1%20U%5Ctimes_2%20V%5Ctimes_3%20W%5C%7C_F"> at the same <img src="https://latex.codecogs.com/png.latex?(R_1,R_2,R_3)">.</p>
<p>Higher-order orthogonal iteration (HOOI) closes part of that gap. For each mode in turn it unfolds the tensor after contracting the other modes with their current factors, then replaces that factor by the leading left singular vectors of the unfolding. The cycle is ALS on the Tucker objective with an orthogonality constraint.</p>
<p>TensorLy’s <code>tucker(..., init="svd")</code> is HOSVD followed by HOOI. The call in the previous section already ran that pipeline. Truncated HOSVD alone is cheaper and often good enough as a starting point; it is not the Tucker minimizer.</p>
</section>
<section id="tensor-train" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="tensor-train"><span class="header-section-number">5</span> Tensor train</h2>
<p>CP and Tucker treat every mode symmetrically. For <img src="https://latex.codecogs.com/png.latex?N%5Cgg%203"> the core <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BG%7D"> of a Tucker model still has <img src="https://latex.codecogs.com/png.latex?%5Cprod_n%20R_n"> entries. Tensor train (Oseledets 2011) replaces the single core by a <strong>chain</strong>:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7BX%7D(i_1,%5Cldots,i_N)=G_1(i_1)%5C,G_2(i_2)%5Ccdots%20G_N(i_N),%0A"></p>
<p>with <img src="https://latex.codecogs.com/png.latex?G_n(i_n)%5Cin%5Cmathbb%7BR%7D%5E%7Br_%7Bn-1%7D%5Ctimes%20r_n%7D"> and <img src="https://latex.codecogs.com/png.latex?r_0=r_N=1">. Each <img src="https://latex.codecogs.com/png.latex?G_n"> is a 3-way array of size <img src="https://latex.codecogs.com/png.latex?r_%7Bn-1%7D%5Ctimes%20I_n%5Ctimes%20r_n">, contracted with its neighbours along the rank bonds.</p>
<ul>
<li><strong>TT-ranks</strong> <img src="https://latex.codecogs.com/png.latex?(r_n)"> are ranks of a chain of unfoldings that split the first <img src="https://latex.codecogs.com/png.latex?n"> modes from the rest, not a single Kruskal rank.</li>
<li><strong>TT-SVD</strong> computes them sequentially: unfold the first mode, truncated SVD, reshape the remainder, repeat. Each truncation is Eckart–Young for that unfolding.</li>
<li><strong>Gauge.</strong> <img src="https://latex.codecogs.com/png.latex?G_n(i)%5Cleftarrow%20G_n(i)M"> and <img src="https://latex.codecogs.com/png.latex?G_%7Bn+1%7D(j)%5Cleftarrow%20M%5E%7B-1%7DG_%7Bn+1%7D(j)"> leave the product unchanged. Cores are not unique.</li>
<li><strong>Storage</strong> is <img src="https://latex.codecogs.com/png.latex?O(N%20I%20r%5E2)"> at uniform rank <img src="https://latex.codecogs.com/png.latex?r">, linear in order <img src="https://latex.codecogs.com/png.latex?N">. That is the point for <img src="https://latex.codecogs.com/png.latex?N%5Cgg%203">. On this order-3 cube TT is just another low-rank format; the cores below have shapes <img src="https://latex.codecogs.com/png.latex?(1,12,4)">, <img src="https://latex.codecogs.com/png.latex?(4,12,4)">, <img src="https://latex.codecogs.com/png.latex?(4,12,1)">.</li>
</ul>
<div id="61f75b26" class="cell" data-execution_count="4">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1">tt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tensor_train(X, rank<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>)</span>
<span id="cb6-2">X_tt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tt.to_tensor()</span>
<span id="cb6-3">tt_rel <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.norm(X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> X_tt) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> np.linalg.norm(X)</span>
<span id="cb6-4"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"TT relative Frobenius error: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>tt_rel<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb6-5"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"TT core shapes:"</span>, [c.shape <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> c <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> tt.factors])</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>TT relative Frobenius error: 0.0594
TT core shapes: [(1, 12, 4), (4, 12, 4), (4, 12, 1)]</code></pre>
</div>
</div>
<p>The residual matches CP and Tucker at this rank. The format difference would show up on an order-6 or order-8 array, where a Tucker core is infeasible and a TT chain is not.</p>
</section>
<section id="t-svd" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="t-svd"><span class="header-section-number">6</span> t-SVD</h2>
<p>CP, Tucker, and TT are <em>approximations</em>. The t-product of Kilmer and Martin (2011) is an <em>algebra</em> on <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BR%7D%5E%7Bn_1%5Ctimes%20n_2%5Ctimes%20n_3%7D"> in which a decomposition analogous to matrix SVD is exact.</p>
<p>The t-product <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BA%7D*%5Cmathcal%7BB%7D"> is:</p>
<ol type="1">
<li>FFT along tubes (mode 3).</li>
<li>Ordinary matrix multiply of each pair of frontal slices.</li>
<li>Inverse FFT.</li>
</ol>
<p>The identity tensor <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BI%7D"> has the matrix identity as its first frontal slice and zeros elsewhere. Orthogonal tensors satisfy <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BU%7D%5E%5Ctop%20*%20%5Cmathcal%7BU%7D=%5Cmathcal%7BI%7D">, where <img src="https://latex.codecogs.com/png.latex?%7B%7D%5E%5Ctop"> transposes each frontal slice and reverses the later ones. Under that product every third-order tensor has a t-SVD</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7BA%7D=%5Cmathcal%7BU%7D*%5Cmathcal%7BS%7D*%5Cmathcal%7BV%7D%5E%5Ctop%0A"></p>
<p>with orthogonal <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BU%7D,%5Cmathcal%7BV%7D"> and <img src="https://latex.codecogs.com/png.latex?f">-diagonal <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BS%7D"> (each tube <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BS%7D_%7Bii:%7D"> holds the singular values of the Fourier slices). <strong>Tubal rank</strong> is the number of nonzero singular tubes.</p>
<p>TensorLy has no first-class t-SVD. The residual below is numpy, computed slicewise in the Fourier domain, which is the definition.</p>
<div id="ee84a69e" class="cell" data-execution_count="5">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1">A <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rng.normal(size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>))</span>
<span id="cb8-2">A_hat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.fft.fft(A, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb8-3">recon_hat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.empty_like(A_hat)</span>
<span id="cb8-4"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(A.shape[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]):</span>
<span id="cb8-5">    U, s, Vt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.svd(A_hat[:, :, i], full_matrices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb8-6">    recon_hat[:, :, i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (U <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> s) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> Vt</span>
<span id="cb8-7">A_recon <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.fft.ifft(recon_hat, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>).real</span>
<span id="cb8-8"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"t-SVD reconstruction residual: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linalg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>norm(A <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> A_recon)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>t-SVD reconstruction residual: 2.40e-14</code></pre>
</div>
</div>
<p>The residual is rounding error because the t-SVD is an exact factorization, not a truncation. Truncating singular tubes is the analogue of truncated SVD; that step is optional and is not run here.</p>
</section>
<section id="factorization-comparison" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="factorization-comparison"><span class="header-section-number">7</span> Factorization comparison</h2>
<table class="caption-top table">
<colgroup>
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
</colgroup>
<thead>
<tr class="header">
<th>Factorization</th>
<th>Rank notion</th>
<th>Uniqueness</th>
<th>Storage</th>
<th>Associated inverse</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>CP</td>
<td>Kruskal <img src="https://latex.codecogs.com/png.latex?R"></td>
<td>Kruskal condition, up to perm/scale</td>
<td><img src="https://latex.codecogs.com/png.latex?R%5Csum_n%20I_n"></td>
<td>none canonical</td>
</tr>
<tr class="even">
<td>Tucker</td>
<td>multilinear <img src="https://latex.codecogs.com/png.latex?(R_n)"></td>
<td>factors up to invertible mix</td>
<td><img src="https://latex.codecogs.com/png.latex?%5C%7C%5Cmathcal%7BG%7D%5C%7C+%5Csum_n%20I_n%20R_n"></td>
<td>mode-<img src="https://latex.codecogs.com/png.latex?n"> pinv of factors</td>
</tr>
<tr class="odd">
<td>HOSVD</td>
<td>truncated multilinear</td>
<td>orthogonal factors; not ALS-optimal</td>
<td>same as Tucker</td>
<td><img src="https://latex.codecogs.com/png.latex?(U%5E%7B(n)%7D)%5E%5Ctop"> is the mode-<img src="https://latex.codecogs.com/png.latex?n"> inverse</td>
</tr>
<tr class="even">
<td>TT</td>
<td>TT-ranks <img src="https://latex.codecogs.com/png.latex?(r_n)"></td>
<td>gauge freedom on cores</td>
<td><img src="https://latex.codecogs.com/png.latex?O(N%20I%20r%5E2)"></td>
<td>TT-inverse when cores are square</td>
</tr>
<tr class="odd">
<td>t-SVD</td>
<td>tubal rank</td>
<td>unique under <img src="https://latex.codecogs.com/png.latex?*"> (as matrix SVD)</td>
<td>dense unless truncated</td>
<td>t-inverse of Fourier slices</td>
</tr>
</tbody>
</table>
<p>Pick the factorization for the rank notion you need, then pick the inverse for the product that factorization lives in. The two choices are independent: a CP model does not come with a t-inverse.</p>
</section>
<section id="tensor-inverses" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="tensor-inverses"><span class="header-section-number">8</span> Tensor inverses</h2>
<p>A matrix inverse is the inverse of matrix multiplication. A tensor has no such default product, so it has no default inverse. Each product below induces its own inverse. They are not interchangeable, and none is “the” tensor inverse.</p>
<section id="mode-n-pseudoinverse" class="level3" data-number="8.1">
<h3 data-number="8.1" class="anchored" data-anchor-id="mode-n-pseudoinverse"><span class="header-section-number">8.1</span> Mode-<img src="https://latex.codecogs.com/png.latex?n"> pseudoinverse</h3>
<p>Unfold mode <img src="https://latex.codecogs.com/png.latex?n">, take the matrix Moore–Penrose inverse, fold. If <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BY%7D=%5Cmathcal%7BX%7D%5Ctimes_n%20A">, the unfolding identity is <img src="https://latex.codecogs.com/png.latex?Y_%7B(n)%7D=A%5C,X_%7B(n)%7D">, so</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AA=Y_%7B(n)%7DX_%7B(n)%7D%5E%7B%5Cdagger%7D%0A"></p>
<p>whenever <img src="https://latex.codecogs.com/png.latex?X_%7B(n)%7D"> has full row rank. This is ordinary linear regression along one mode, with the other modes stacked as samples.</p>
<p>It inverts a mode-<img src="https://latex.codecogs.com/png.latex?n"> product. It does not invert a tensor as an operator on tensors. Use it to recover a mixing matrix, a change of basis, or a compression map <img src="https://latex.codecogs.com/png.latex?U%5E%7B(n)%7D"> from a Tucker/HOSVD factor.</p>
</section>
<section id="t-inverse" class="level3" data-number="8.2">
<h3 data-number="8.2" class="anchored" data-anchor-id="t-inverse"><span class="header-section-number">8.2</span> t-inverse</h3>
<p>Invert every Fourier frontal slice, then invert the FFT. If every slice is invertible,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7BA%7D*%5Cmathcal%7BA%7D%5E%7B-1%7D=%5Cmathcal%7BI%7D.%0A"></p>
<p>Singular slices yield a t-pseudoinverse by replacing <code>inv</code> with <code>pinv</code> on those slices; tubal rank then plays the role of matrix rank. This is the inverse that belongs with t-SVD. Third-order arrays whose third mode is time or a frequency grid (video, some fMRI layouts) are the usual setting.</p>
</section>
<section id="einstein-product-inverse" class="level3" data-number="8.3">
<h3 data-number="8.3" class="anchored" data-anchor-id="einstein-product-inverse"><span class="header-section-number">8.3</span> Einstein product inverse</h3>
<p>The Einstein product of two even-order tensors contracts <img src="https://latex.codecogs.com/png.latex?N"> matching indices. A tensor <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BA%7D%5Cin%5Cmathbb%7BR%7D%5E%7BI_1%5Ctimes%5Ccdots%5Ctimes%20I_N%5Ctimes%20I_1%5Ctimes%5Ccdots%5Ctimes%20I_N%7D"> is isomorphic to a matrix of size <img src="https://latex.codecogs.com/png.latex?%5Cbigl(%5Cprod_k%20I_k%5Cbigr)%5E2">: reshape, invert, reshape (Brazell, Li, Navasca, Tamon 2013). Existence is exactly invertibility of that unfolding.</p>
<p>The product is the one that appears in discretizations of linear maps on tensor product spaces. A <img src="https://latex.codecogs.com/png.latex?3%5Ctimes%203%5Ctimes%203%5Ctimes%203"> tensor is a linear map <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BR%7D%5E%7B3%5Ctimes%203%7D%5Cto%5Cmathbb%7BR%7D%5E%7B3%5Ctimes%203%7D">; its Einstein inverse is the inverse map, not a mode-<img src="https://latex.codecogs.com/png.latex?n"> pinv and not a t-inverse.</p>
</section>
<section id="multilinear-moorepenrose-inverse" class="level3" data-number="8.4">
<h3 data-number="8.4" class="anchored" data-anchor-id="multilinear-moorepenrose-inverse"><span class="header-section-number">8.4</span> Multilinear Moore–Penrose inverse</h3>
<p>When the Einstein unfolding is rectangular or rank-deficient, replace <code>inv</code> by <code>pinv</code>. The result is the unique tensor satisfying the four Penrose identities for the Einstein product:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Baligned%7D%0A%5Cmathcal%7BA%7D%5Ccircledast%5Cmathcal%7BA%7D%5E%7B%5Cdagger%7D%5Ccircledast%5Cmathcal%7BA%7D&amp;=%5Cmathcal%7BA%7D,%5C%5C%0A%5Cmathcal%7BA%7D%5E%7B%5Cdagger%7D%5Ccircledast%5Cmathcal%7BA%7D%5Ccircledast%5Cmathcal%7BA%7D%5E%7B%5Cdagger%7D&amp;=%5Cmathcal%7BA%7D%5E%7B%5Cdagger%7D,%5C%5C%0A(%5Cmathcal%7BA%7D%5Ccircledast%5Cmathcal%7BA%7D%5E%7B%5Cdagger%7D)%5E%7B%5Cast%7D&amp;=%5Cmathcal%7BA%7D%5Ccircledast%5Cmathcal%7BA%7D%5E%7B%5Cdagger%7D,%5C%5C%0A(%5Cmathcal%7BA%7D%5E%7B%5Cdagger%7D%5Ccircledast%5Cmathcal%7BA%7D)%5E%7B%5Cast%7D&amp;=%5Cmathcal%7BA%7D%5E%7B%5Cdagger%7D%5Ccircledast%5Cmathcal%7BA%7D.%0A%5Cend%7Baligned%7D%0A"></p>
<p>At order <img src="https://latex.codecogs.com/png.latex?2"> this is the matrix Moore–Penrose inverse. The last cell checks the first identity on a <img src="https://latex.codecogs.com/png.latex?3%5Ctimes%203%5Ctimes%202%5Ctimes%203"> tensor.</p>
<div id="5cab9c53" class="cell" data-execution_count="6">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1">A_true <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rng.normal(size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, n))</span>
<span id="cb10-2">Y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tl.tenalg.mode_dot(X, A_true, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb10-3">A_hat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tl.unfold(Y, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> np.linalg.pinv(tl.unfold(X, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>))</span>
<span id="cb10-4">mode_rel <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.norm(A_hat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> A_true) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> np.linalg.norm(A_true)</span>
<span id="cb10-5"></span>
<span id="cb10-6">B <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rng.normal(size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>))</span>
<span id="cb10-7">B <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> np.eye(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>)[:, :, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>]</span>
<span id="cb10-8">B_hat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.fft.fft(B, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb10-9">inv_hat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.empty_like(B_hat)</span>
<span id="cb10-10"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(B.shape[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]):</span>
<span id="cb10-11">    inv_hat[:, :, i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.inv(B_hat[:, :, i])</span>
<span id="cb10-12">B_inv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.fft.ifft(inv_hat, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>).real</span>
<span id="cb10-13">I_t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros_like(B)</span>
<span id="cb10-14">I_t[np.arange(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>), np.arange(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span></span>
<span id="cb10-15">prod_hat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.empty_like(B_hat)</span>
<span id="cb10-16">Binv_hat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.fft.fft(B_inv, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb10-17"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(B.shape[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]):</span>
<span id="cb10-18">    prod_hat[:, :, i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> B_hat[:, :, i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> Binv_hat[:, :, i]</span>
<span id="cb10-19">B_prod <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.fft.ifft(prod_hat, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>).real</span>
<span id="cb10-20">tinv_rel <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.norm(B_prod <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> I_t)</span>
<span id="cb10-21"></span>
<span id="cb10-22">M <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rng.normal(size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>))</span>
<span id="cb10-23">M <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.eye(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>)</span>
<span id="cb10-24">T4 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> M.reshape(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb10-25">T4_inv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.inv(M).reshape(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb10-26">einstein_id <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.tensordot(T4, T4_inv, axes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>([<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>], [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]))</span>
<span id="cb10-27">einstein_rel <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.norm(einstein_id <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> np.eye(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>).reshape(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>))</span>
<span id="cb10-28"></span>
<span id="cb10-29">M_fat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rng.normal(size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>))</span>
<span id="cb10-30">T_rect <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> M_fat.reshape(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb10-31">T_mp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.pinv(M_fat).reshape(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb10-32">mp_id <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.tensordot(T_rect, T_mp, axes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>([<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>], [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]))</span>
<span id="cb10-33">penrose <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.tensordot(mp_id, T_rect, axes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>([<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>], [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]))</span>
<span id="cb10-34">mp_rel <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.norm(penrose <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> T_rect)</span>
<span id="cb10-35"></span>
<span id="cb10-36"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"mode-0 pinv relative error: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>mode_rel<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb10-37"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"t-inverse residual ||A*A^</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">{{</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">-1</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">}}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> - I||: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>tinv_rel<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb10-38"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Einstein inverse residual: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>einstein_rel<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb10-39"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"multilinear M-P Penrose residual: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>mp_rel<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>mode-0 pinv relative error: 1.11e-14
t-inverse residual ||A*A^{-1} - I||: 1.33e-14
Einstein inverse residual: 1.02e-15
multilinear M-P Penrose residual: 3.30e-15</code></pre>
</div>
</div>
<p>The four residuals are at rounding error (mode-<img src="https://latex.codecogs.com/png.latex?n">, Einstein, M-P) or at the Fourier inversion tolerance (t-inverse). Each number certifies a different product. Matching the residual to the wrong product is a type error, not a numerical one.</p>
<p>Tensors. Outrun. Matrices. Factorizations. Compress. Inverses. Require. Products. Choose. First.</p>
</section>
</section>
<section id="references" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="references"><span class="header-section-number">9</span> References</h2>
<ul>
<li>Hitchcock (1927) — the polyadic form that became CP.</li>
<li>Harshman (1970); Carroll and Chang (1970) — PARAFAC / CANDECOMP.</li>
<li>Tucker (1966) — three-mode factor analysis.</li>
<li>De Lathauwer, De Moor, Vandewalle (2000) — HOSVD.</li>
<li>Kolda and Bader (2009), <em>SIAM Review</em> — tensor decompositions and applications.</li>
<li>Oseledets (2011) — tensor train.</li>
<li>Kilmer and Martin (2011) — t-product and t-SVD.</li>
<li>Brazell, Li, Navasca, Tamon (2013) — Einstein product inverse.</li>
<li>Kossaifi et al.&nbsp;(2019), <em>JMLR</em> — <a href="https://tensorly.org/">TensorLy</a>.</li>
<li><a href="../matrix-factorizations/">Matrix Factorizations as Optimization Problems</a> — the <img src="https://latex.codecogs.com/png.latex?N=2"> case.</li>
</ul>


</section>

 ]]></description>
  <category>Linear Algebra</category>
  <category>Machine Learning</category>
  <category>Numerical Methods</category>
  <category>Tensors</category>
  <guid>https://project-delphi.github.io/ml-blog/posts/tensor-factorizations/</guid>
  <pubDate>Fri, 28 Aug 2026 05:00:00 GMT</pubDate>
  <media:content url="https://project-delphi.github.io/ml-blog/posts/tensor-factorizations/cover.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>From Dataset to Biological Signature</title>
  <dc:creator>Ravi Kalia</dc:creator>
  <link>https://project-delphi.github.io/ml-blog/posts/dataset-to-biological-signature/</link>
  <description><![CDATA[ 





<p><img src="https://project-delphi.github.io/ml-blog/posts/dataset-to-biological-signature/cover.png" class="img-fluid" alt="Cover card reading From Dataset to a Biological Signature, over a solid purple background."></p>
<section id="biological-signature" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="biological-signature"><span class="header-section-number">1</span> Biological signature</h2>
<p>A <strong>biological signature</strong> is a fixed gene list with weights and a decision threshold, fit to answer a specific classification question. Every reported performance number is conditional on the data having selected those genes.</p>
<p>This post runs Golub et al.&nbsp;(1999) ALL/AML microarray data end to end: recover the original train/test split, fit an elastic-net signature without leakage, and report honest external validation. The pipeline is described twice — ML vocabulary and statistical vocabulary.</p>
<p>Primer without code: <a href="../what-is-a-biological-signature/">What is a Biological Signature?</a>.</p>
</section>
<section id="biology-background" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="biology-background"><span class="header-section-number">2</span> Biology background</h2>
<p>Acute lymphoblastic leukemia (ALL) and acute myeloid leukemia (AML) require opposite treatments. Pre-1999 diagnosis used morphology, cytochemistry, and surface markers.</p>
<p>Bone marrow stem cell gives two lines:</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart TD
  S["Bone marrow stem cell"] --&gt; L["Lymphoid line&lt;br/&gt;B-cells, T-cells"]
  S --&gt; M["Myeloid line&lt;br/&gt;red cells, platelets, granulocytes"]
  L --&gt;|glitch| ALL["&lt;b&gt;ALL&lt;/b&gt;&lt;br/&gt;lymphoblasts"]
  M --&gt;|glitch| AML["&lt;b&gt;AML&lt;/b&gt;&lt;br/&gt;myeloblasts"]
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<ul>
<li><strong>ALL:</strong> lymphoid line jammed; commonest childhood cancer.</li>
<li><strong>AML:</strong> myeloid line jammed; adult disease; affects oxygen transport and clotting faster.</li>
</ul>
<p>Diagnostic question: <em>which line is stuck?</em></p>
</section>
<section id="data-provenance" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="data-provenance"><span class="header-section-number">3</span> Data provenance</h2>
<p><strong>Source:</strong> Golub et al.&nbsp;(1999), Whitehead Institute / Dana-Farber. 72 acute leukemia patients; Affymetrix HU6800 array (7,129 gene probes). Archived hospital samples; goal was to show tumour type is legible in gene expression.</p>
<ul>
<li><strong>Matrix used here:</strong> Efron and Hastie’s <a href="https://hastie.su.domains/CASI_files/DATA/leukemia.html">CASI data page</a> (<code>leukemia_big.csv.gz</code>), cached in <code>data/</code>.</li>
<li><strong>Split key:</strong> original Whitehead files (<code>golub_train.csv.gz</code>, <code>golub_independent.csv.gz</code>) carry probe accessions and patient IDs the CASI copy dropped.</li>
<li><strong>Objective:</strong> classify ALL vs AML from expression.</li>
<li><strong>Downstream impact:</strong> treatment assignment (vincristine/steroids vs cytarabine/anthracycline). Target metric: performance at a <strong>different hospital</strong>, not peak training accuracy.</li>
</ul>
<div id="setup" class="cell" data-execution_count="1">
<details class="code-fold">
<summary>Imports, plot style, and loading the cached matrices</summary>
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> warnings</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> np</span>
<span id="cb1-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> pandas <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pd</span>
<span id="cb1-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> matplotlib.pyplot <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> plt</span>
<span id="cb1-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> scipy.stats <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> beta</span>
<span id="cb1-6"></span>
<span id="cb1-7">warnings.filterwarnings(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ignore"</span>)</span>
<span id="cb1-8">rng_seed <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb1-9"></span>
<span id="cb1-10">plt.rcParams.update({</span>
<span id="cb1-11">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"figure.dpi"</span>: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">150</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"savefig.bbox"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tight"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"font.size"</span>: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>,</span>
<span id="cb1-12">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"axes.spines.top"</span>: <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"axes.spines.right"</span>: <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>,</span>
<span id="cb1-13">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"axes.grid"</span>: <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"grid.alpha"</span>: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"axes.axisbelow"</span>: <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>,</span>
<span id="cb1-14">})</span>
<span id="cb1-15">ALL_C, AML_C <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#1D5C6E"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#C98A12"</span></span>
<span id="cb1-16"></span>
<span id="cb1-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># The CASI matrix: 7,128 genes x 72 samples, column names carry the labels.</span></span>
<span id="cb1-18">casi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.read_csv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"data/leukemia_big.csv.gz"</span>)</span>
<span id="cb1-19">X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> casi.to_numpy().T                                    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># samples x genes</span></span>
<span id="cb1-20">y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(c.split(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"."</span>)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].startswith(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AML"</span>)) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> c <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> casi.columns])</span>
<span id="cb1-21"></span>
<span id="cb1-22"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># The original Whitehead files, used only as a key: they carry the Affymetrix</span></span>
<span id="cb1-23"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># probe accessions and the per-patient sample IDs that the CASI copy dropped.</span></span>
<span id="cb1-24"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> read_golub(path):</span>
<span id="cb1-25">    d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.read_csv(path)</span>
<span id="cb1-26">    ids <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [c <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> c <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> d.columns[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>:] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> c.startswith(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"call"</span>)]</span>
<span id="cb1-27">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> d[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Gene Accession Number"</span>].to_numpy(), d[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Gene Description"</span>].to_numpy(), <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb1-28">           d[ids].to_numpy(dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>), ids</span>
<span id="cb1-29"></span>
<span id="cb1-30">acc, desc, G_train, id_train <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> read_golub(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"data/golub_train.csv.gz"</span>)</span>
<span id="cb1-31">_, _, G_test, id_test <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> read_golub(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"data/golub_independent.csv.gz"</span>)</span>
<span id="cb1-32">truth <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.read_csv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"data/golub_labels.csv"</span>).set_index(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"patient"</span>)[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cancer"</span>].to_dict()</span>
<span id="cb1-33"></span>
<span id="cb1-34"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"expression matrix : </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>X<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> samples x </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>X<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> genes"</span>)</span>
<span id="cb1-35"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"class balance     : </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>(y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> ALL, </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>y<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> AML"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>expression matrix : 72 samples x 7128 genes
class balance     : 47 ALL, 25 AML</code></pre>
</div>
</div>
<p>72 samples × 7,128 genes (<img src="https://latex.codecogs.com/png.latex?p%20%5Cgg%20n">).</p>
<div id="cell-slice" class="cell" data-execution_count="2">
<details class="code-fold">
<summary>A corner of the matrix</summary>
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">pd.DataFrame(</span>
<span id="cb3-2">    X[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, :<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>),</span>
<span id="cb3-3">    index<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"sample </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> (</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'AML'</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> y[i] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'ALL'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">)"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>)],</span>
<span id="cb3-4">    columns<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"gene </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>j<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>)],</span>
<span id="cb3-5">)</span></code></pre></div>
</details>
<div id="slice" class="cell-output cell-output-display" data-execution_count="2">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">gene 0</th>
<th data-quarto-table-cell-role="th">gene 1</th>
<th data-quarto-table-cell-role="th">gene 2</th>
<th data-quarto-table-cell-role="th">gene 3</th>
<th data-quarto-table-cell-role="th">gene 4</th>
<th data-quarto-table-cell-role="th">gene 5</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">sample 0 (ALL)</td>
<td>-1.534</td>
<td>-1.236</td>
<td>-0.334</td>
<td>0.489</td>
<td>-1.301</td>
<td>-1.683</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">sample 1 (ALL)</td>
<td>-0.868</td>
<td>-1.276</td>
<td>0.376</td>
<td>0.444</td>
<td>-1.230</td>
<td>-1.642</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">sample 2 (ALL)</td>
<td>-0.433</td>
<td>-1.184</td>
<td>-0.459</td>
<td>0.436</td>
<td>-1.326</td>
<td>-1.407</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">sample 3 (ALL)</td>
<td>-1.672</td>
<td>-1.596</td>
<td>-1.423</td>
<td>0.193</td>
<td>-1.818</td>
<td>-1.744</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">sample 4 (ALL)</td>
<td>-1.188</td>
<td>-1.335</td>
<td>-0.797</td>
<td>0.236</td>
<td>-1.311</td>
<td>-1.654</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>Each patient column is z-scored (mean 0, SD 1). Per-gene preprocessing across patients is undocumented (“lost in time”).</p>
</section>
<section id="traintest-split-recovery" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="traintest-split-recovery"><span class="header-section-number">4</span> Train/test split recovery</h2>
<p>Golub’s design:</p>
<ul>
<li><strong>38 training:</strong> single hospital, bone marrow, predictor built here.</li>
<li><strong>34 external test:</strong> later collection, different labs, some blood — simulates new hospital.</li>
</ul>
<p>CASI file lacks patient IDs. Recovery method: column-wise rank order is invariant to affine rescaling → Spearman rank correlation matches CASI columns to Golub patients.</p>
<div id="recover-split" class="cell" data-execution_count="3">
<details open="" class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1">G <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.hstack([G_train, G_test])[:X.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]]   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># CASI drops Golub's last probe</span></span>
<span id="cb4-2">ids <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> id_train <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> id_test</span>
<span id="cb4-3">golub_y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(truth[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(i)] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AML"</span>) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> ids])</span>
<span id="cb4-4"></span>
<span id="cb4-5">rank <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> A: np.argsort(np.argsort(A, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>).astype(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>)</span>
<span id="cb4-6">Rc, Rg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rank(casi.to_numpy()), rank(G)</span>
<span id="cb4-7">Rc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (Rc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> Rc.mean(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> Rc.std(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb4-8">Rg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (Rg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> Rg.mean(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> Rg.std(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb4-9">S <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (Rc.T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> Rg) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> G.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]                    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 72 x 72 Spearman matrix</span></span>
<span id="cb4-10">match, best <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> S.argmax(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), np.sort(S, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb4-11"></span>
<span id="cb4-12"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"unique matches      : </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(match.tolist()))<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">/72"</span>)</span>
<span id="cb4-13"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"labels agree        : </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>(golub_y[match] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> y)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">/72"</span>)</span>
<span id="cb4-14"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"worst true match    : </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>best[:, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb4-15"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"best runner-up      : </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>best[:, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb4-16"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"CASI column 0 -&gt; patient </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>ids[match[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">;  column 34 -&gt; patient </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>ids[match[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">34</span>]]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>unique matches      : 72/72
labels agree        : 72/72
worst true match    : 0.945
best runner-up      : 0.884
CASI column 0 -&gt; patient 39;  column 34 -&gt; patient 1</code></pre>
</div>
</div>
<ul>
<li>72/72 unique matches; all labels agree.</li>
<li>Worst true match: 0.945; best wrong candidate: 0.884.</li>
<li><strong>CASI stores 34 external samples first</strong> (columns 0–33), training second (34–71).</li>
</ul>
<section id="preprocessing-caveat" class="level3" data-number="4.1">
<h3 data-number="4.1" class="anchored" data-anchor-id="preprocessing-caveat"><span class="header-section-number">4.1</span> Preprocessing caveat</h3>
<p>Undocumented per-gene step may have pooled all 72 patients. Column z-scoring is safe; pooled gene normalization would leak test into training. All results below are conditional on column scaling only.</p>
<div id="cell-fig-data" class="cell" data-execution_count="4">
<details class="code-fold">
<summary>Figure 1</summary>
<div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1">test_ix, train_ix <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.arange(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">34</span>), np.arange(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">34</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">72</span>)</span>
<span id="cb6-2">probe <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {a: i <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(acc[:X.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]])}</span>
<span id="cb6-3">mpo, ccnd3 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> probe[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"M19507_at"</span>], probe[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"M92287_at"</span>]</span>
<span id="cb6-4"></span>
<span id="cb6-5">Z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> X.mean(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (X.std(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-9</span>)</span>
<span id="cb6-6">U, sv, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.svd(Z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> Z.mean(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), full_matrices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb6-7">pcs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> U[:, :<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> sv[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>]</span>
<span id="cb6-8"></span>
<span id="cb6-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Pick the axes by what they actually carry, rather than assuming PC1/PC2:</span></span>
<span id="cb6-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># correlate each component with class and with cohort membership.</span></span>
<span id="cb6-11">cohort <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.r_[np.zeros(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">34</span>), np.ones(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">38</span>)]</span>
<span id="cb6-12">corr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> v: np.array([<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(np.corrcoef(v, pcs[:, k])[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> k <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>)])</span>
<span id="cb6-13">k_cls, k_coh <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> corr(y).argmax(), corr(cohort).argmax()</span>
<span id="cb6-14"></span>
<span id="cb6-15">fig, (a1, a2) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.6</span>))</span>
<span id="cb6-16"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> cls, name, col <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> [(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ALL"</span>, ALL_C), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AML"</span>, AML_C)]:</span>
<span id="cb6-17">    m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> cls</span>
<span id="cb6-18">    a1.scatter(X[m, ccnd3], X[m, mpo], c<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>col, s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">28</span>, alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.85</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>name,</span>
<span id="cb6-19">               edgecolor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"white"</span>, linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>)</span>
<span id="cb6-20">a1.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(xlabel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"CCND3 (cyclin D3), lymphoid"</span>, ylabel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"MPO (myeloperoxidase), myeloid"</span>,</span>
<span id="cb6-21">       title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Two genes"</span>)</span>
<span id="cb6-22">a1.legend(frameon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb6-23"></span>
<span id="cb6-24"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> cls, col <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> [(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, ALL_C), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, AML_C)]:</span>
<span id="cb6-25">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> ix, mark, lab <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> [(train_ix, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"o"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"training"</span>), (test_ix, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"^"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"external"</span>)]:</span>
<span id="cb6-26">        m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.intersect1d(np.flatnonzero(y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> cls), ix)</span>
<span id="cb6-27">        a2.scatter(pcs[m, k_cls], pcs[m, k_coh], c<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>col, marker<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>mark, s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">28</span>, alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.85</span>,</span>
<span id="cb6-28">                   edgecolor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"white"</span>, linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>,</span>
<span id="cb6-29">                   label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'AML'</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> cls <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'ALL'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>lab<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb6-30">a2.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(xlabel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"PC</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>k_cls <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  (carries class, |r| = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>corr(y)[k_cls]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">)"</span>,</span>
<span id="cb6-31">       ylabel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"PC</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>k_coh <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  (carries cohort, |r| = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>corr(cohort)[k_coh]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">)"</span>,</span>
<span id="cb6-32">       title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Colour = class, shape = cohort"</span>)</span>
<span id="cb6-33">a2.legend(frameon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>, loc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"best"</span>)</span>
<span id="cb6-34">fig.tight_layout()</span>
<span id="cb6-35">plt.show()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-data" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-data-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/dataset-to-biological-signature/index_files/figure-html/fig-data-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-data-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Left: two genes named later in the post, one myeloid and one lymphoid, separate the classes almost on their own. Right: the component that carries diagnosis plotted against the one that carries cohort. Class separates along the x-axis and the laboratory along the y-axis — the nuisance variable has structure of its own.
</figcaption>
</figure>
</div>
</div>
</div>
<ul>
<li>CCND3 (lymphoid) vs MPO (myeloid) nearly separate classes alone.</li>
<li>Class and cohort load on <strong>different</strong> principal components. Ridge classifier on cohort alone: 78% CV accuracy.</li>
</ul>
</section>
</section>
<section id="ml-pipeline" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="ml-pipeline"><span class="header-section-number">5</span> ML pipeline</h2>
<section id="input-representation" class="level3" data-number="5.1">
<h3 data-number="5.1" class="anchored" data-anchor-id="input-representation"><span class="header-section-number">5.1</span> Input representation</h3>
<p>Model sees samples × genes matrix. Biology (probe mapping, normalization) is upstream and assay-specific.</p>
</section>
<section id="supervised-signature" class="level3" data-number="5.2">
<h3 data-number="5.2" class="anchored" data-anchor-id="supervised-signature"><span class="header-section-number">5.2</span> Supervised signature</h3>
<p>Signature requires labels (ALL vs AML). Unsupervised structure ≠ signature without a question. Golub (1999) also did class discovery then prediction; discovery proposes the question, prediction remains supervised.</p>
</section>
<section id="elastic-net-objective" class="level3" data-number="5.3">
<h3 data-number="5.3" class="anchored" data-anchor-id="elastic-net-objective"><span class="header-section-number">5.3</span> Elastic net objective</h3>
<p>Shipped artifact: fixed gene list, weights, threshold — locked before opening test set.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Chat%5Cbeta%20%5C;=%5C;%20%5Carg%5Cmin_%7B%5Cbeta%7D%5C;%20-%5Cfrac%7B1%7D%7Bn%7D%5Csum_%7Bi=1%7D%5E%7Bn%7D%5CBig%5By_i%5C,x_i%5E%7B%5Ctop%7D%5Cbeta%20-%20%5Clog%5Cbig(1%20+%20e%5E%7Bx_i%5E%7B%5Ctop%7D%5Cbeta%7D%5Cbig)%5CBig%5D%20%5C;+%5C;%20%5Clambda%5CBig(%5Calpha%5ClVert%5Cbeta%5CrVert_1%20+%20%5Ctfrac%7B1-%5Calpha%7D%7B2%7D%5ClVert%5Cbeta%5CrVert_2%5E2%5CBig)%0A"></p>
<ul>
<li>L1 (<img src="https://latex.codecogs.com/png.latex?%5ClVert%5Cbeta%5CrVert_1">): sparsity; exact zeros.</li>
<li>L2 (<img src="https://latex.codecogs.com/png.latex?%5ClVert%5Cbeta%5CrVert_2%5E2">): groups correlated genes.</li>
<li><img src="https://latex.codecogs.com/png.latex?%5Calpha%20=%200.5">; tune <img src="https://latex.codecogs.com/png.latex?%5Clambda"> (via <img src="https://latex.codecogs.com/png.latex?C%20=%201/%5Clambda">) by nested cross-validation on 38 training samples only.</li>
</ul>
<div id="cell-fig-path" class="cell" data-execution_count="5">
<details class="code-fold">
<summary>Nested cross-validation, and Figure 2</summary>
<div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> sklearn.linear_model <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> LogisticRegression</span>
<span id="cb7-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> sklearn.model_selection <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> StratifiedKFold, cross_val_score</span>
<span id="cb7-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> sklearn.metrics <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> accuracy_score, roc_auc_score, brier_score_loss</span>
<span id="cb7-4"></span>
<span id="cb7-5">Xtr, ytr, Xte, yte <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X[train_ix], y[train_ix], X[test_ix], y[test_ix]</span>
<span id="cb7-6">Cs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.logspace(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>)</span>
<span id="cb7-7"></span>
<span id="cb7-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> enet(C):</span>
<span id="cb7-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> LogisticRegression(penalty<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"elasticnet"</span>, solver<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"saga"</span>, l1_ratio<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>,</span>
<span id="cb7-10">                              C<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>C, max_iter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3000</span>, tol<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-3</span>, random_state<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>rng_seed)</span>
<span id="cb7-11"></span>
<span id="cb7-12"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> screen(A, k<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2000</span>):</span>
<span id="cb7-13">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Unsupervised variance screen. Never sees y, so it is safe inside a fold."""</span></span>
<span id="cb7-14">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> np.argsort(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>A.var(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>))[:k]</span>
<span id="cb7-15"></span>
<span id="cb7-16">outer, scores <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StratifiedKFold(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, shuffle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>, random_state<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), []</span>
<span id="cb7-17"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a, b <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> outer.split(Xtr, ytr):</span>
<span id="cb7-18">    g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> screen(Xtr[a])</span>
<span id="cb7-19">    mu, sd <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Xtr[a][:, g].mean(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), Xtr[a][:, g].std(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-9</span></span>
<span id="cb7-20">    Za, Zb <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (Xtr[a][:, g] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> mu) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> sd, (Xtr[b][:, g] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> mu) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> sd</span>
<span id="cb7-21">    inner <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StratifiedKFold(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, shuffle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>, random_state<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb7-22">    C <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(Cs, key<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> c: cross_val_score(enet(c), Za, ytr[a], cv<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>inner).mean())</span>
<span id="cb7-23">    scores.append(accuracy_score(ytr[b], enet(C).fit(Za, ytr[a]).predict(Zb)))</span>
<span id="cb7-24"></span>
<span id="cb7-25"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Full-training-set path, for the figure and for the final penalty.</span></span>
<span id="cb7-26">g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> screen(Xtr)</span>
<span id="cb7-27">mu, sd <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Xtr[:, g].mean(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), Xtr[:, g].std(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-9</span></span>
<span id="cb7-28">Ztr, Zte <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (Xtr[:, g] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> mu) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> sd, (Xte[:, g] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> mu) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> sd</span>
<span id="cb7-29">cv5 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StratifiedKFold(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, shuffle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>, random_state<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb7-30">path <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([cross_val_score(enet(c), Ztr, ytr, cv<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>cv5) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> c <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> Cs])</span>
<span id="cb7-31">C_star <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Cs[path.mean(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>).argmax()]</span>
<span id="cb7-32"></span>
<span id="cb7-33"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"nested CV accuracy : </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>mean(scores)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  (outer folds "</span></span>
<span id="cb7-34">      <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(s, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> s <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> scores]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">)"</span>)</span>
<span id="cb7-35"></span>
<span id="cb7-36">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6.2</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.2</span>))</span>
<span id="cb7-37">ax.errorbar(Cs, path.mean(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), yerr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>path.std(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), marker<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"o"</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ALL_C,</span>
<span id="cb7-38">            capsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span>, markersize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>)</span>
<span id="cb7-39">ax.axvline(C_star, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>AML_C, linestyle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--"</span>, linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.3</span>,</span>
<span id="cb7-40">           label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"chosen C = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>C_star<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb7-41">ax.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(xscale<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"log"</span>, xlabel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"C  (inverse penalty strength)"</span>,</span>
<span id="cb7-42">       ylabel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"inner-fold accuracy"</span>, title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Regularisation path"</span>)</span>
<span id="cb7-43">ax.legend(frameon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb7-44">plt.show()</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>nested CV accuracy : 0.921  (outer folds [0.75, 1.0, 1.0, 0.86, 1.0])</code></pre>
</div>
<div class="cell-output cell-output-display">
<div id="fig-path" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-path-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/dataset-to-biological-signature/index_files/figure-html/fig-path-output-2.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-path-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: Inner-fold accuracy along the regularisation path, on the 38 training samples only. Accuracy peaks at a single interior value. To the left the penalty is strong enough to zero out genes the model needs; to the right it is weak enough to admit genes that only help on the training fold. Error bars are one standard deviation across five folds, and they are wide — 38 samples do not locate this optimum precisely.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Nested CV training accuracy: <strong>0.921</strong>. External test (once):</p>
<div id="external" class="cell" data-execution_count="6">
<details open="" class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1">final <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> enet(C_star).fit(Ztr, ytr)</span>
<span id="cb9-2">selected <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g[np.flatnonzero(final.coef_[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])]</span>
<span id="cb9-3">p_test <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> final.predict_proba(Zte)[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb9-4"></span>
<span id="cb9-5"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"genes with non-zero coefficient : </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(selected)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb9-6"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"external accuracy : </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>accuracy_score(yte, p_test <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> "</span></span>
<span id="cb9-7">      <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"(</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>((( p_test <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> yte).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>())<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">/34)"</span>)</span>
<span id="cb9-8"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"external AUC      : </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>roc_auc_score(yte, p_test)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb9-9"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"external Brier    : </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>brier_score_loss(yte, p_test)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb9-10">wrong <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.flatnonzero((p_test <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>).astype(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> yte)</span>
<span id="cb9-11"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"the one error     : predicted p(AML) = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>p_test[wrong][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>genes with non-zero coefficient : 88
external accuracy : 0.971 (33/34)
external AUC      : 1.000
external Brier    : 0.041
the one error     : predicted p(AML) = 0.468</code></pre>
</div>
</div>
<p>Results:</p>
<ul>
<li><strong>33/34</strong> correct; AUC <strong>1.000</strong> (perfect ranking); one error at <img src="https://latex.codecogs.com/png.latex?p%20=%200.468">.</li>
<li>Training CV (0.921) <strong>understated</strong> external performance (0.971).</li>
</ul>
<div id="lineage" class="cell" data-execution_count="7">
<details class="code-fold">
<summary>Naming the selected genes</summary>
<div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1">lineage <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb11-2">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"M19507_at"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"MPO, myeloperoxidase — the granulocyte enzyme"</span>,</span>
<span id="cb11-3">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"M23197_at"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"CD33 — myeloid surface antigen"</span>,</span>
<span id="cb11-4">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"X52056_at"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"SPI1 / PU.1 — myeloid master transcription factor"</span>,</span>
<span id="cb11-5">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"M84526_at"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"DF, adipsin — Golub's AML-high list"</span>,</span>
<span id="cb11-6">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"M27891_at"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"CST3, cystatin C — Golub's AML-high list"</span>,</span>
<span id="cb11-7">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"M92287_at"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"CCND3, cyclin D3 — Golub's ALL-high list"</span>,</span>
<span id="cb11-8">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"U05259_rna1_at"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"MB-1 / CD79a — B-cell receptor component"</span>,</span>
<span id="cb11-9">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"M31523_at"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"TCF3 / E2A — required for B-cell development"</span>,</span>
<span id="cb11-10">}</span>
<span id="cb11-11">hits <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [(acc[i], lineage[acc[i]]) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> selected <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> acc[i] <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> lineage]</span>
<span id="cb11-12"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a, note <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> hits:</span>
<span id="cb11-13">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"  </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>a<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:16s}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>note<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb11-14"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(hits)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> of these annotated markers survived selection."</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>  M27891_at        CST3, cystatin C — Golub's AML-high list
  X52056_at        SPI1 / PU.1 — myeloid master transcription factor
  M84526_at        DF, adipsin — Golub's AML-high list
  M19507_at        MPO, myeloperoxidase — the granulocyte enzyme
  U05259_rna1_at   MB-1 / CD79a — B-cell receptor component
  M23197_at        CD33 — myeloid surface antigen
  M92287_at        CCND3, cyclin D3 — Golub's ALL-high list

7 of these annotated markers survived selection.</code></pre>
</div>
</div>
<p>Selected genes: myeloid vs lymphoid markers; includes MPO (pre-1999 stain target).</p>
</section>
</section>
<section id="statistical-pipeline" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="statistical-pipeline"><span class="header-section-number">6</span> Statistical pipeline</h2>
<p>Same procedure; emphasis on inference under <img src="https://latex.codecogs.com/png.latex?p%20%5Cgg%20n"> and post-selection bias.</p>
<section id="post-selection-inference" class="level3" data-number="6.1">
<h3 data-number="6.1" class="anchored" data-anchor-id="post-selection-inference"><span class="header-section-number">6.1</span> Post-selection inference</h3>
<p>Selected gene weights inherit selection luck. Standard CIs assume pre-specified variables. Repairs:</p>
<ol type="1">
<li>Split sample: select on one half, infer on other.</li>
<li>Selective inference: correct for selection.</li>
<li>Knockoffs: fake genes through same contest.</li>
</ol>
</section>
<section id="false-discovery-rate" class="level3" data-number="6.2">
<h3 data-number="6.2" class="anchored" data-anchor-id="false-discovery-rate"><span class="header-section-number">6.2</span> False discovery rate</h3>
<p>7,128 tests at 5% → ~356 false positives by chance. <strong>FDR</strong> controls expected fraction wrong among reported genes:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathrm%7BFDR%7D%20%5C;=%5C;%20%5Cmathbb%7BE%7D%5C!%5Cleft%5B%5Cfrac%7BV%7D%7B%5Cmax(R,%201)%7D%5Cright%5D%0A"></p>
<p>Benjamini–Hochberg delivers long-run FDR control when genes correlate; single-list fraction can swing (gene gangs).</p>
</section>
<section id="pipeline-stages" class="level3" data-number="6.3">
<h3 data-number="6.3" class="anchored" data-anchor-id="pipeline-stages"><span class="header-section-number">6.3</span> Pipeline stages</h3>
<ol type="1">
<li>Pre-specify estimand and threshold.</li>
<li>Split before any transformation.</li>
<li>Fit scaling and gene filter on training folds only.</li>
<li>Inner loop: choose penalty; outer loop: score performance.</li>
<li>Stability selection across subsamples.</li>
<li>Lock model; test externally once; report intervals and calibration.</li>
</ol>
<p>Step 3 is most commonly violated.</p>
</section>
<section id="leakage-demonstration" class="level3" data-number="6.4">
<h3 data-number="6.4" class="anchored" data-anchor-id="leakage-demonstration"><span class="header-section-number">6.4</span> Leakage demonstration</h3>
<p>Score all genes on full data, keep top 50, then cross-validate → held-out fold influenced gene choice. <a href="https://doi.org/10.1073/pnas.99.6562">Ambroise and McLachlan (2002)</a>.</p>
<div id="leakage" class="cell" data-execution_count="8">
<details open="" class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> top_genes(A, lab, k<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">50</span>):</span>
<span id="cb13-2">    m0, m1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> A[lab <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].mean(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), A[lab <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].mean(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb13-3">    se <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.sqrt(A[lab <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].var(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (lab <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>()</span>
<span id="cb13-4">                 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> A[lab <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].var(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> lab.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>()) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-9</span></span>
<span id="cb13-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> np.argsort(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>((m1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> m0) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> se))[:k]</span>
<span id="cb13-6"></span>
<span id="cb13-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> two_ways(lab, cv<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>StratifiedKFold(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, shuffle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>, random_state<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>)):</span>
<span id="cb13-8">    once <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cross_val_score(enet(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>), Z[:, top_genes(Z, lab)], lab, cv<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>cv).mean()</span>
<span id="cb13-9">    fold <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [accuracy_score(lab[b], enet(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>)</span>
<span id="cb13-10">            .fit(Z[a][:, (gi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:=</span> top_genes(Z[a], lab[a]))], lab[a])</span>
<span id="cb13-11">            .predict(Z[b][:, gi])) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a, b <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> cv.split(Z, lab)]</span>
<span id="cb13-12">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> once, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> np.mean(fold)</span>
<span id="cb13-13"></span>
<span id="cb13-14">opt, honest <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> two_ways(y)</span>
<span id="cb13-15"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"real labels     : filter once </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>opt<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1%}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> error | filter in fold </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>honest<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1%}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb13-16"></span>
<span id="cb13-17">shuffled <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.random.default_rng(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>).permutation(y)</span>
<span id="cb13-18">opt_n, honest_n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> two_ways(shuffled)</span>
<span id="cb13-19"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"labels shuffled : filter once </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>opt_n<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1%}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> error | filter in fold </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>honest_n<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1%}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb13-20"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"chance for this class balance : </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>(y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>mean()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1%}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> correct by always saying ALL"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>real labels     : filter once 2.9% error | filter in fold 4.3%
labels shuffled : filter once 7.0% error | filter in fold 33.2%
chance for this class balance : 65.3% correct by always saying ALL</code></pre>
</div>
</div>
<p>Real labels: 2.9% (leaked) vs 4.3% (honest) — small gap because signal is strong.</p>
<p>Shuffled labels: 7.0% error (93% accuracy on noise) vs 33.2% (honest) ≈ always-ALL baseline.</p>
</section>
<section id="stability-selection" class="level3" data-number="6.5">
<h3 data-number="6.5" class="anchored" data-anchor-id="stability-selection"><span class="header-section-number">6.5</span> Stability selection</h3>
<p>100 half-samples of 38 training patients; fixed penalty; count reselection frequency.</p>
<div id="cell-fig-stability" class="cell" data-execution_count="9">
<details class="code-fold">
<summary>Stability selection, and Figure 3</summary>
<div class="sourceCode cell-code" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb15-1">rng <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.random.default_rng(rng_seed)</span>
<span id="cb15-2">freq, B <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros(X.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span></span>
<span id="cb15-3">pool <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> screen(Xtr)</span>
<span id="cb15-4"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(B):</span>
<span id="cb15-5">    take <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rng.choice(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(ytr), <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(ytr) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, replace<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb15-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(np.unique(ytr[take])) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>:</span>
<span id="cb15-7">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">continue</span></span>
<span id="cb15-8">    A <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Xtr[take][:, pool]</span>
<span id="cb15-9">    A <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (A <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> A.mean(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (A.std(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-9</span>)</span>
<span id="cb15-10">    freq[pool[np.flatnonzero(enet(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>).fit(A, ytr[take]).coef_[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])]] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb15-11">freq <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/=</span> B</span>
<span id="cb15-12"></span>
<span id="cb15-13">order <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.argsort(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>freq)</span>
<span id="cb15-14"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> t <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.9</span>):</span>
<span id="cb15-15">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"selection frequency &gt;= </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>t<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>(freq <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> t)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> genes"</span>)</span>
<span id="cb15-16"></span>
<span id="cb15-17">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7.6</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.4</span>))</span>
<span id="cb15-18">top <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> order[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>]</span>
<span id="cb15-19">ax.bar(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(top)), freq[top], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ALL_C, width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>)</span>
<span id="cb15-20">ax.axhline(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>AML_C, linestyle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--"</span>, linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.3</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cutoff = 0.6"</span>)</span>
<span id="cb15-21">above <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>((freq[top] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span>).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>())</span>
<span id="cb15-22">ax.set_xticks(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(above))</span>
<span id="cb15-23">ax.set_xticklabels([acc[i] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> top[:above]], rotation<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">90</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>)</span>
<span id="cb15-24">ax.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(xlim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(top)), ylabel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"selection frequency"</span>,</span>
<span id="cb15-25">       xlabel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"genes, ranked"</span>, title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>B<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> half-samples of the 38 training patients"</span>)</span>
<span id="cb15-26">ax.legend(frameon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb15-27">plt.show()</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>selection frequency &gt;= 0.5: 45 genes
selection frequency &gt;= 0.6: 33 genes
selection frequency &gt;= 0.7: 26 genes
selection frequency &gt;= 0.9: 12 genes</code></pre>
</div>
<div class="cell-output cell-output-display">
<div id="fig-stability" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-stability-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://project-delphi.github.io/ml-blog/posts/dataset-to-biological-signature/index_files/figure-html/fig-stability-output-2.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-stability-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: Selection frequency across 100 half-samples of the training cohort, sorted. The dashed line is a 0.6 cutoff. Genes above it are labelled by Affymetrix probe.
</figcaption>
</figure>
</div>
</div>
</div>
<p>45 genes ≥50% frequency; 12 ≥90%. Gene list is one draw, not a fixed biological object.</p>
</section>
<section id="golub-50-comparison" class="level3" data-number="6.6">
<h3 data-number="6.6" class="anchored" data-anchor-id="golub-50-comparison"><span class="header-section-number">6.6</span> Golub-50 comparison</h3>
<p>Reconstruct Golub’s 50-gene predictor (<img src="https://latex.codecogs.com/png.latex?P(g,c)%20=%20(%5Cmu_1%20-%20%5Cmu_0)/(%5Csigma_1%20+%20%5Csigma_0)">); compare raw vs log10-clipped preprocessing.</p>
<div id="golub50" class="cell" data-execution_count="10">
<details class="code-fold">
<summary>Reconstructing the 1999 predictor</summary>
<div class="sourceCode cell-code" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb17-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> snr(A, lab):</span>
<span id="cb17-2">    d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> A[lab <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].mean(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> A[lab <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].mean(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb17-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (A[lab <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].std(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, ddof<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> A[lab <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].std(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, ddof<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-12</span>)</span>
<span id="cb17-4"></span>
<span id="cb17-5">raw <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> G_train.T</span>
<span id="cb17-6">P_raw <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> snr(raw, golub_y[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">38</span>])</span>
<span id="cb17-7">P_log <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> snr(np.log10(np.clip(raw, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16000</span>)), golub_y[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">38</span>])</span>
<span id="cb17-8">fifty <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> P: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(np.argsort(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>P)[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">25</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(np.argsort(P)[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">25</span>])</span>
<span id="cb17-9">g50_raw, g50_log <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fifty(P_raw), fifty(P_log)</span>
<span id="cb17-10"></span>
<span id="cb17-11"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Golub-50, raw vs his own log10 preprocessing : </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(g50_raw <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> g50_log)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">/50 shared"</span>)</span>
<span id="cb17-12"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"elastic net set vs Golub-50 (log10)         : "</span></span>
<span id="cb17-13">      <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(selected) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> g50_log)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> of 50 genes in common"</span>)</span>
<span id="cb17-14"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top AML-high probes:"</span>,</span>
<span id="cb17-15">      <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">", "</span>.join(acc[i] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> np.argsort(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>P_log)[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>]))</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>Golub-50, raw vs his own log10 preprocessing : 32/50 shared
elastic net set vs Golub-50 (log10)         : 26 of 50 genes in common
top AML-high probes: M27891_at, X95735_at, U50136_rna1_at, M23197_at</code></pre>
</div>
</div>
<p>Log preprocessing alone changes 18/50 genes. Elastic net agrees with Golub-50 on 26/50.</p>
</section>
</section>
<section id="external-validation-limits" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="external-validation-limits"><span class="header-section-number">7</span> External validation limits</h2>
<p>33/34 external accuracy is real but <img src="https://latex.codecogs.com/png.latex?n%20=%2034">.</p>
<div id="closing" class="cell" data-execution_count="11">
<details class="code-fold">
<summary>Intervals, and what happens at a different prevalence</summary>
<div class="sourceCode cell-code" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb19-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> wilson(k, n, z<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.96</span>):</span>
<span id="cb19-2">    c <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> n)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> n)</span>
<span id="cb19-3">    h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.sqrt(k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> n) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> n)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> n)</span>
<span id="cb19-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> c <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> h, c <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> h</span>
<span id="cb19-5"></span>
<span id="cb19-6"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"external accuracy 33/34   : 95% CI [</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>wilson(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">33</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">34</span>)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>wilson(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">33</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">34</span>)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">]"</span>)</span>
<span id="cb19-7"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"nested CV, 35/38          : 95% CI [</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>wilson(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">35</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">38</span>)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>wilson(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">35</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">38</span>)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">]"</span>)</span>
<span id="cb19-8"></span>
<span id="cb19-9">sens, fpr_hi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">13</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">14</span>, beta.ppf(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.975</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>)</span>
<span id="cb19-10"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">specificity 20/20, but 95% upper bound on the false-positive rate: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>fpr_hi<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb19-11"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> prev, where <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> [(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.35</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"suspected leukemia at referral"</span>), (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"a broader haematology clinic"</span>)]:</span>
<span id="cb19-12">    ppv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sens <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> prev <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (sens <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> prev <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> fpr_hi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> prev))</span>
<span id="cb19-13">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"  prevalence </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>prev<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:&gt;5.0%}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> (</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>where<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">): PPV could be as low as </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>ppv<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>external accuracy 33/34   : 95% CI [0.851, 0.995]
nested CV, 35/38          : 95% CI [0.792, 0.973]

specificity 20/20, but 95% upper bound on the false-positive rate: 0.168
  prevalence   35% (suspected leukemia at referral): PPV could be as low as 0.75
  prevalence    2% (a broader haematology clinic): PPV could be as low as 0.10</code></pre>
</div>
</div>
<ul>
<li>33/34 → 95% CI <strong>[0.851, 0.995]</strong> for true accuracy.</li>
<li>0/20 false AML calls → consistent with FPR up to <strong>~17%</strong>.</li>
<li>PPV at 35% prevalence (referral): could be as low as <strong>~0.75</strong>.</li>
<li>PPV at 2% prevalence (general clinic): could be as low as <strong>~0.10</strong>.</li>
</ul>
<p>Accuracy is a property of model <strong>and population</strong>. Signature answers “ALL or AML among patients known to have one of them.”</p>
<p>Signatures. Answer. One. Question. Prevalence. Changes. The. Answer.</p>
</section>
<section id="references" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="references"><span class="header-section-number">8</span> References</h2>
<p><strong>Biology and genomics</strong></p>
<ul>
<li>Golub, T. R. et al.&nbsp;(1999). Molecular classification of cancer: class discovery and class prediction by gene expression monitoring. <em>Science</em> 286:531–537.</li>
<li>Alizadeh, A. A. et al.&nbsp;(2000). Distinct types of diffuse large B-cell lymphoma identified by gene expression profiling. <em>Nature</em> 403:503–511.</li>
<li>van ’t Veer, L. J. et al.&nbsp;(2002). Gene expression profiling predicts clinical outcome of breast cancer. <em>Nature</em> 415:530–536.</li>
<li>Subramanian, A. et al.&nbsp;(2005). Gene set enrichment analysis: a knowledge-based approach for interpreting genome-wide expression profiles. <em>PNAS</em> 102:15545–15550.</li>
<li>Leek, J. T. et al.&nbsp;(2010). Tackling the widespread and critical impact of batch effects in high-throughput data. <em>Nature Reviews Genetics</em> 11:733–739.</li>
<li>Whalen, S. et al.&nbsp;(2022). Navigating the pitfalls of applying machine learning in genomics. <em>Nature Reviews Genetics</em> 23:169–181.</li>
</ul>
<p><strong>Statistics and machine learning</strong></p>
<ul>
<li>Tibshirani, R. (1996). Regression shrinkage and selection via the lasso. <em>JRSS-B</em> 58:267–288.</li>
<li>Benjamini, Y. &amp; Hochberg, Y. (1995). Controlling the false discovery rate: a practical and powerful approach to multiple testing. <em>JRSS-B</em> 57:289–300.</li>
<li>Zou, H. &amp; Hastie, T. (2005). Regularization and variable selection via the elastic net. <em>JRSS-B</em> 67:301–320.</li>
<li>Guyon, I., Weston, J., Barnhill, S. &amp; Vapnik, V. (2002). Gene selection for cancer classification using support vector machines. <em>Machine Learning</em> 46:389–422.</li>
<li>Dudoit, S., Fridlyand, J. &amp; Speed, T. P. (2002). Comparison of discrimination methods for the classification of tumors using gene expression data. <em>JASA</em> 97:77–87.</li>
<li>Ambroise, C. &amp; McLachlan, G. J. (2002). Selection bias in gene extraction on the basis of microarray gene-expression data. <em>PNAS</em> 99:6562–6566.</li>
<li>Meinshausen, N. &amp; Bühlmann, P. (2010). Stability selection. <em>JRSS-B</em> 72:417–473.</li>
<li>Barber, R. F. &amp; Candès, E. J. (2015). Controlling the false discovery rate via knockoffs. <em>Annals of Statistics</em> 43:2055–2085.</li>
<li>Candès, E., Fan, Y., Janson, L. &amp; Lv, J. (2018). Panning for gold: ‘model-X’ knockoffs for high-dimensional controlled variable selection. <em>JRSS-B</em> 80:551–577.</li>
<li>Efron, B. &amp; Hastie, T. (2016). <em>Computer Age Statistical Inference: Algorithms, Evidence, and Data Science.</em> Cambridge University Press.</li>
</ul>
<p><strong>Data.</strong> The expression matrix is <code>leukemia_big.csv</code> from the <a href="https://hastie.su.domains/CASI_files/DATA/leukemia.html">CASI data page</a>. Probe accessions and sample identifiers come from the original Whitehead files, <code>data_set_ALL_AML_train.csv</code> and <code>data_set_ALL_AML_independent.csv</code>; the URL cited in the 1999 paper is long dead, so this post uses a <a href="https://github.com/dharsandip/Classification_of_Cancer_by_Gene_Expression_Dataset">mirror</a> of them. Both are cached under <code>data/</code> by <code>src/fetch_data.py</code>.</p>


</section>

 ]]></description>
  <category>Bioinformatics</category>
  <category>Machine Learning</category>
  <category>Python</category>
  <category>Statistics</category>
  <guid>https://project-delphi.github.io/ml-blog/posts/dataset-to-biological-signature/</guid>
  <pubDate>Thu, 27 Aug 2026 05:00:00 GMT</pubDate>
  <media:content url="https://project-delphi.github.io/ml-blog/posts/dataset-to-biological-signature/cover.png" medium="image" type="image/png" height="76" width="144"/>
</item>
</channel>
</rss>
