<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2026-05-12T14:42:47+00:00</updated><id>/feed.xml</id><title type="html">Trang Tinkers</title><subtitle>Spread the memes.</subtitle><entry><title type="html">Counting Meets: 15 buckets × 3 = 45</title><link href="/math/2026/05/12/counting-meets.html" rel="alternate" type="text/html" title="Counting Meets: 15 buckets × 3 = 45" /><published>2026-05-12T00:00:00+00:00</published><updated>2026-05-12T00:00:00+00:00</updated><id>/math/2026/05/12/counting-meets</id><content type="html" xml:base="/math/2026/05/12/counting-meets.html"><![CDATA[<style>
  .meets-buckets { color: #222; line-height: 1.5; }
  .meets-buckets h2 {
    font-size: 1.15rem;
    margin-top: 2.8rem;
    border-bottom: 1px solid #eee;
    padding-bottom: 0.3rem;
    font-weight: 500;
  }
  .meets-buckets .panel {
    background: #fff;
    border: 1px solid #ddd;
    padding: 1rem 1.2rem;
    margin: 1.2rem 0;
    border-radius: 6px;
  }
  .meets-buckets .grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1rem;
  }
  .meets-buckets .bucket {
    border: 1px solid #ccc;
    border-radius: 6px;
    padding: .6rem;
    background: #fff;
  }
  .meets-buckets .bucket-label {
    text-align: center;
    font-weight: bold;
    margin-bottom: .3rem;
    font-size: .95rem;
  }
  .meets-buckets .bucket-label .complement { color: #aaa; }
  .meets-buckets .meets-row {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: .2rem;
  }
  .meets-buckets .meet { text-align: center; }
  .meets-buckets .meet-label {
    font-size: .7rem;
    color: #444;
    font-family: monospace;
  }
  .meets-buckets svg { display: block; margin: 0 auto; }
  .meets-buckets .node { fill: #333; }
  .meets-buckets .node-label { font-size: 10px; fill: #fff; text-anchor: middle; dominant-baseline: central; font-family: sans-serif; }
  .meets-buckets .edge-meet { stroke-width: 2.5; }
  .meets-buckets .edge-meet.a { stroke: #c0392b; }
  .meets-buckets .edge-meet.b { stroke: #2980b9; }
  .meets-buckets .edge-complement { stroke: #bbb; stroke-width: 1.5; stroke-dasharray: 3,3; }
  .meets-buckets .proof {
    background: #f3f0e8;
    padding: 1rem 1.2rem;
    border-left: 4px solid #8a7;
    border-radius: 0 6px 6px 0;
    margin: 1.2rem 0;
  }
  .meets-buckets table { border-collapse: collapse; margin: .5rem 0; }
  .meets-buckets td, .meets-buckets th { border: 1px solid #ccc; padding: .3rem .6rem; text-align: left; }
  .meets-buckets code { background: #f0eee5; padding: 1px 4px; border-radius: 3px; font-size: .9em; }
  .meets-buckets .counter {
    font-size: 1.4rem;
    text-align: center;
    padding: .5rem;
    background: #efe;
    border: 1px solid #aca;
    border-radius: 6px;
    margin: 1rem 0;
  }
</style>

<div class="meets-buckets">

<p style="color: #555; font-style: italic; margin-bottom: 1.5rem;">
  Notes on Norman Wildberger's <a href="https://www.youtube.com/watch?v=FlZWCUEBwmE&list=PLzdiPTrEWyz75-t4UaYvHwPe5AHGA_EWe&index=3"><i>Six: A mathematical exploration</i></a> — lecture 2 of an elementary course in pure mathematics built around the combinatorics of six objects. This page works through one specific claim from the lecture: that there are exactly 45 meets, and why the 15 × 3 construction generates all of them without overlap.
</p>

<p>A <b>meet</b> is a 2-set of non-incident edges — two edges that share no node — on six nodes. The claim is that there are exactly 45 meets. The argument has two parts:</p>
<ol>
  <li><b>Sorting.</b> Sort every meet into one of 15 buckets, one per edge.</li>
  <li><b>Counting.</b> Show each bucket has exactly 3 meets.</li>
</ol>
<p>If the sorting puts every meet into exactly one bucket — no meet skipped, no meet in two buckets — then the total is just <code>15 × 3 = 45</code>.</p>

<h2>The sorting rule</h2>

<div class="panel">
  <p>Given any meet <code>e₁ · e₂</code>, the two edges use 4 different nodes. The <b>remaining 2 nodes</b> form a third edge, which I'll call the <b>complement</b>. That complement is the bucket label.</p>
  <p>Example: the meet <code>1-2 · 3-4</code> uses nodes {1,2,3,4}. The leftover nodes are {5,6}, so the complement edge is <code>5-6</code>. This meet goes in bucket <b>5-6</b>.</p>

  <div style="display:flex; justify-content:center; gap: 2rem; align-items: center; margin: 1rem 0;">
    <div style="text-align:center;">
      <svg width="180" height="180" viewBox="0 0 180 180">
        <g id="example1"></g>
      </svg>
      <div style="font-family: monospace; font-size: .9rem;">meet <b>1-2 · 3-4</b><br/>complement: <span style="color:#888;">5-6</span></div>
    </div>
  </div>
</div>

<h2>Why each meet lands in <i>exactly one</i> bucket</h2>

<div class="proof">
  <p><b>The complement is unique.</b> Given a meet, the 4 nodes it uses are fixed, so the 2 leftover nodes are fixed, so the complement edge is fixed. There is no choice involved. One meet → one complement → one bucket.</p>
  <p>This means:</p>
  <ul>
    <li><b>No meet is in two buckets</b> — its complement is uniquely determined, so it goes to <i>only</i> that bucket.</li>
  </ul>
</div>

<h2>Why the construction misses no meets</h2>

<div class="proof">
  <p>The argument above rules out one failure mode (a meet appearing in two buckets). The other failure mode is a meet appearing in <i>zero</i> buckets — being missed entirely. To rule that out, take an arbitrary meet and trace it through the construction.</p>

  <p>Pick any meet — say <code>2-5 · 3-6</code>. Where does it show up?</p>

  <div style="display:flex; justify-content:center; gap: 2rem; align-items: center; margin: 1rem 0;">
    <div style="text-align:center;">
      <svg width="180" height="180" viewBox="0 0 180 180">
        <g id="trace-input"></g>
      </svg>
      <div style="font-family: monospace; font-size: .9rem;">meet <b>2-5 · 3-6</b><br/>complement: <span style="color:#888;">1-4</span></div>
    </div>
  </div>

  <ol>
    <li>This meet uses nodes {2, 3, 5, 6}.</li>
    <li>The leftover nodes are {1, 4}, so its complement edge is <code>1-4</code>.</li>
    <li>The construction visits all 15 edges, so it visits <code>1-4</code>. When it does, it generates the 3 pair-ups of the 4 leftover nodes {2, 3, 5, 6}:</li>
  </ol>

  <div style="display:flex; justify-content:center; gap: 1rem; margin: 1rem 0;">
    <div style="text-align:center; font-family: monospace;">
      <svg width="140" height="140" viewBox="0 0 180 180"><g id="trace-a"></g></svg>
      2-3 · 5-6
    </div>
    <div style="text-align:center; font-family: monospace; background: #ffe; padding: .4rem; border-radius: 4px;">
      <svg width="140" height="140" viewBox="0 0 180 180"><g id="trace-b"></g></svg>
      <b>2-5 · 3-6</b> ←
    </div>
    <div style="text-align:center; font-family: monospace;">
      <svg width="140" height="140" viewBox="0 0 180 180"><g id="trace-c"></g></svg>
      2-6 · 3-5
    </div>
  </div>

  <p>Our meet <code>2-5 · 3-6</code> is the middle one. The construction <b>produces</b> it — we didn't have to know about it in advance, and we didn't have to search for it. It fell out automatically when we processed bucket <code>1-4</code>.</p>

  <p><b>The general argument.</b> Take <i>any</i> meet M:</p>
  <ol>
    <li>M consists of two disjoint edges, so M uses exactly 4 of the 6 nodes.</li>
    <li>The 2 leftover nodes form an edge e — the complement.</li>
    <li>The construction visits e (it visits all 15 edges, no exceptions).</li>
    <li>When it visits e, it generates <i>every</i> way to pair up the 4 leftover nodes into 2 pairs.</li>
    <li>M is one of those pair-ups — because that's literally what M is: two disjoint edges on those 4 leftover nodes.</li>
  </ol>
  <p>So M must be produced. Since M was an arbitrary meet, every meet is produced.</p>

  <table style="margin: 1rem auto;">
    <tr>
      <th>Concern</th>
      <th>Why it can't happen</th>
    </tr>
    <tr>
      <td>A meet ends up in <b>two</b> buckets (counted twice)</td>
      <td>A meet has only one complement, so only one bucket matches.</td>
    </tr>
    <tr>
      <td>A meet ends up in <b>zero</b> buckets (missed)</td>
      <td>A meet always has a complement, and the bucket for that complement always generates it.</td>
    </tr>
  </table>

  <p>Together: every meet appears in <b>exactly one</b> bucket. The buckets partition the meets.</p>
</div>

<h2>Why each bucket has exactly 3 meets</h2>

<div class="panel">
  <p>Fix a bucket — say bucket <code>5-6</code>. The meets in it use only the 4 leftover nodes {1,2,3,4}. So the question becomes: <b>in how many ways can we split 4 nodes into 2 pairs?</b></p>
  <p>Pick node 1. It must pair with one of {2, 3, 4} — that's 3 choices. The remaining 2 nodes are then forced into the second pair. So <b>3 splits</b>:</p>
  <div style="display:flex; justify-content:center; gap: 1rem; margin: 1rem 0;">
    <div style="text-align:center; font-family: monospace;">
      <svg width="140" height="140" viewBox="0 0 180 180"><g id="ex-a"></g></svg>
      1-2 · 3-4
    </div>
    <div style="text-align:center; font-family: monospace;">
      <svg width="140" height="140" viewBox="0 0 180 180"><g id="ex-b"></g></svg>
      1-3 · 2-4
    </div>
    <div style="text-align:center; font-family: monospace;">
      <svg width="140" height="140" viewBox="0 0 180 180"><g id="ex-c"></g></svg>
      1-4 · 2-3
    </div>
  </div>
  <p>This is the same calculation no matter which edge is the bucket label — it always reduces to "split 4 leftover nodes into 2 pairs," which always has 3 answers.</p>
</div>

<h2>All 15 buckets — the receipts</h2>

<p>Each card is a bucket. The bucket label (the dashed gray edge) is the complement. The two solid colored edges are the actual meet.</p>

<div class="grid" id="buckets"></div>

<div class="counter" id="counter"></div>

<h2>What this argument is <i>not</i> doing</h2>

<div class="panel">
  <p>This is not a "list everything and count" proof. It's a <b>structural</b> proof: it pairs up two ways of counting the same set.</p>
  <ul>
    <li>One way: walk through all meets, find the count directly. (Hard — that's what we're trying to avoid.)</li>
    <li>The other way: group meets by their complement edge, count each group, then multiply by the number of groups. <code>15 × 3</code>.</li>
  </ul>
  <p>Both ways count the same thing — the set of meets — so they must give the same answer. The structural way is easier because the local question ("how many meets share this complement?") has a clean answer (3) that we can compute without enumerating everything.</p>
  <p>The key step is <b>convincing yourself the sorting rule is well-defined</b>: every meet has a complement, and only one. Once you believe that, the partition is automatic, and 15 × 3 = 45 follows.</p>
</div>

</div>

<script>
(function() {
  const NODES = {
    1: [90, 20],
    2: [151, 55],
    3: [151, 125],
    4: [90, 160],
    5: [29, 125],
    6: [29, 55],
  };

  function renderDiagram(svgGroupId, meet, complement) {
    const g = document.getElementById(svgGroupId);
    if (!g) return;
    g.innerHTML = "";
    const svgns = "http://www.w3.org/2000/svg";

    if (complement) {
      const [a, b] = complement;
      const [x1, y1] = NODES[a], [x2, y2] = NODES[b];
      const line = document.createElementNS(svgns, "line");
      line.setAttribute("x1", x1); line.setAttribute("y1", y1);
      line.setAttribute("x2", x2); line.setAttribute("y2", y2);
      line.setAttribute("class", "edge-complement");
      g.appendChild(line);
    }

    if (meet) {
      const [e1, e2] = meet;
      [["a", e1], ["b", e2]].forEach(([cls, edge]) => {
        const [a, b] = edge;
        const [x1, y1] = NODES[a], [x2, y2] = NODES[b];
        const line = document.createElementNS(svgns, "line");
        line.setAttribute("x1", x1); line.setAttribute("y1", y1);
        line.setAttribute("x2", x2); line.setAttribute("y2", y2);
        line.setAttribute("class", "edge-meet " + cls);
        g.appendChild(line);
      });
    }

    for (const id of [1,2,3,4,5,6]) {
      const [x, y] = NODES[id];
      const c = document.createElementNS(svgns, "circle");
      c.setAttribute("cx", x); c.setAttribute("cy", y);
      c.setAttribute("r", 10);
      c.setAttribute("class", "node");
      g.appendChild(c);
      const t = document.createElementNS(svgns, "text");
      t.setAttribute("x", x); t.setAttribute("y", y);
      t.setAttribute("class", "node-label");
      t.textContent = id;
      g.appendChild(t);
    }
  }

  renderDiagram("example1", [[1,2],[3,4]], [5,6]);
  renderDiagram("ex-a", [[1,2],[3,4]], null);
  renderDiagram("ex-b", [[1,3],[2,4]], null);
  renderDiagram("ex-c", [[1,4],[2,3]], null);

  renderDiagram("trace-input", [[2,5],[3,6]], [1,4]);
  renderDiagram("trace-a", [[2,3],[5,6]], [1,4]);
  renderDiagram("trace-b", [[2,5],[3,6]], [1,4]);
  renderDiagram("trace-c", [[2,6],[3,5]], [1,4]);

  const EDGES = [];
  for (let i = 1; i <= 6; i++)
    for (let j = i+1; j <= 6; j++)
      EDGES.push([i,j]);

  function pairings(arr) {
    const [a, b, c, d] = arr;
    return [
      [[a, b], [c, d]],
      [[a, c], [b, d]],
      [[a, d], [b, c]],
    ];
  }

  function meetLabel(meet) {
    const [[a,b],[c,d]] = meet;
    return `${a}-${b} · ${c}-${d}`;
  }

  const bucketsEl = document.getElementById("buckets");
  let totalMeets = 0;

  EDGES.forEach((edge, idx) => {
    const leftover = [1,2,3,4,5,6].filter(n => !edge.includes(n));
    const meets = pairings(leftover);
    totalMeets += meets.length;

    const card = document.createElement("div");
    card.className = "bucket";
    card.innerHTML = `
      <div class="bucket-label">Bucket <span class="complement">${edge[0]}-${edge[1]}</span></div>
      <div class="meets-row">
        ${meets.map((m, k) => `
          <div class="meet">
            <svg width="100%" viewBox="0 0 180 180"><g id="b${idx}-${k}"></g></svg>
            <div class="meet-label">${meetLabel(m)}</div>
          </div>
        `).join("")}
      </div>
    `;
    bucketsEl.appendChild(card);

    meets.forEach((m, k) => renderDiagram(`b${idx}-${k}`, m, edge));
  });

  document.getElementById("counter").textContent =
    `Total: ${EDGES.length} buckets × 3 meets each = ${totalMeets} meets`;
})();
</script>]]></content><author><name></name></author><category term="math" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">The Affine Grid</title><link href="/math/2026/05/11/the-affine-grid.html" rel="alternate" type="text/html" title="The Affine Grid" /><published>2026-05-11T00:00:00+00:00</published><updated>2026-05-11T00:00:00+00:00</updated><id>/math/2026/05/11/the-affine-grid</id><content type="html" xml:base="/math/2026/05/11/the-affine-grid.html"><![CDATA[<style>
  .affine-grid h2 {
    font-size: 1.15rem;
    margin-top: 2.8rem;
    border-bottom: 1px solid #eee;
    padding-bottom: 0.3rem;
  }
  .affine-grid figure {
    margin: 1.4rem 0;
    text-align: center;
  }
  .affine-grid figcaption {
    font-size: 0.92rem;
    color: #555;
    margin-top: 0.7rem;
    font-style: italic;
    text-align: left;
    padding: 0 0.5rem;
  }
  .affine-grid svg {
    background: #fafafa;
    border: 1px solid #e2e2e2;
    border-radius: 4px;
    max-width: 100%;
    height: auto;
  }
  .affine-grid .line { stroke: #2a5d8f; stroke-width: 1.6; fill: none; }
  .affine-grid .line-faint { stroke: #b8c8d8; stroke-width: 1; fill: none; }
  .affine-grid .diag { stroke: #1f7a4e; stroke-width: 1.4; fill: none; stroke-dasharray: 5 3; }
  .affine-grid .para-fill { fill: #2a5d8f; fill-opacity: 0.08; }
  .affine-grid .vec { stroke: #c44; stroke-width: 2.4; fill: none; }
  .affine-grid .basis { stroke: #222; stroke-width: 2; fill: none; }
  .affine-grid .step-path { stroke: #c44; stroke-width: 1.4; fill: none; stroke-dasharray: 3 3; opacity: 0.6; }
  .affine-grid .point { fill: #222; }
  .affine-grid .point-new { fill: #c44; }
  .affine-grid .label { font-family: Georgia, serif; font-style: italic; font-size: 14px; fill: #1f1f1f; }
  .affine-grid .annotation { font-family: Georgia, serif; font-size: 12px; fill: #555; }
  .affine-grid .right-angle { stroke: #444; stroke-width: 1.3; fill: none; }
</style>

<div class="affine-grid">

<p>Affine geometry has only one tool: <strong>parallelism</strong>. No ruler, no protractor. From this single primitive, an entire grid system can be built — and the grid that results is necessarily made of parallelograms, not squares.</p>

<h2>1. Two families of parallel lines</h2>

<figure>
  <svg viewBox="0 0 600 380" xmlns="http://www.w3.org/2000/svg">
    <polygon points="200,250 290,268 320,193 230,175" class="para-fill"/>

    <line x1="83" y1="227" x2="560" y2="322" class="line"/>
    <line x1="113" y1="152" x2="590" y2="247" class="line"/>

    <line x1="164" y1="340" x2="266" y2="85" class="line"/>
    <line x1="254" y1="358" x2="359" y2="95" class="line"/>

    <circle cx="200" cy="250" r="3.5" class="point"/>
    <circle cx="290" cy="268" r="3.5" class="point"/>
    <circle cx="320" cy="193" r="3.5" class="point"/>
    <circle cx="230" cy="175" r="3.5" class="point"/>

    <text x="186" y="266" class="label">A</text>
    <text x="298" y="284" class="label">B</text>
    <text x="326" y="188" class="label">C</text>
    <text x="216" y="170" class="label">D</text>
  </svg>
  <figcaption>
    Pick any direction and draw two parallel lines in it. Pick a different direction and draw two parallel lines in it. They cross to form one parallelogram <em>ABCD</em> — the seed of the whole grid. Notice: there is no choice of angle here, and no choice of length. The two directions are arbitrary.
  </figcaption>
</figure>

<h2>2. Extending the grid with diagonals</h2>

<figure>
  <svg viewBox="0 0 600 380" xmlns="http://www.w3.org/2000/svg">
    <polygon points="200,250 290,268 320,193 230,175" class="para-fill"/>

    <line x1="83" y1="227" x2="560" y2="322" class="line"/>
    <line x1="113" y1="152" x2="590" y2="247" class="line"/>
    <line x1="164" y1="340" x2="266" y2="85" class="line"/>
    <line x1="254" y1="358" x2="359" y2="95" class="line"/>

    <line x1="200" y1="250" x2="320" y2="193" class="diag"/>
    <line x1="170" y1="325" x2="410" y2="211" class="diag"/>
    <line x1="110" y1="232" x2="350" y2="118" class="diag"/>

    <circle cx="200" cy="250" r="3.5" class="point"/>
    <circle cx="290" cy="268" r="3.5" class="point"/>
    <circle cx="320" cy="193" r="3.5" class="point"/>
    <circle cx="230" cy="175" r="3.5" class="point"/>

    <circle cx="410" cy="211" r="4.5" class="point-new"/>
    <circle cx="170" cy="325" r="4.5" class="point-new"/>
    <circle cx="110" cy="232" r="4.5" class="point-new"/>
    <circle cx="350" cy="118" r="4.5" class="point-new"/>

    <text x="186" y="266" class="label">A</text>
    <text x="298" y="284" class="label">B</text>
    <text x="326" y="188" class="label">C</text>
    <text x="216" y="170" class="label">D</text>

    <text x="418" y="207" class="annotation">new</text>
    <text x="120" y="343" class="annotation">new</text>
    <text x="80" y="226" class="annotation">new</text>
    <text x="358" y="115" class="annotation">new</text>
  </svg>
  <figcaption>
    Draw the diagonal <em>AC</em> of the parallelogram (green dashed). Now draw lines parallel to <em>AC</em> through the other two corners <em>B</em> and <em>D</em>. Where these parallel diagonals cross the original four lines, four new equally-spaced grid points appear (red). Repeat the trick on those new points and the grid extends indefinitely — all using nothing but parallelism.
  </figcaption>
</figure>

<h2>3. The completed grid</h2>

<figure>
  <svg viewBox="0 0 600 380" xmlns="http://www.w3.org/2000/svg">
    <line x1="100" y1="285" x2="400" y2="335" class="line"/>
    <line x1="120" y1="235" x2="420" y2="285" class="line"/>
    <line x1="140" y1="185" x2="440" y2="235" class="line"/>
    <line x1="160" y1="135" x2="460" y2="185" class="line"/>
    <line x1="180" y1="85" x2="480" y2="135" class="line"/>

    <line x1="120" y1="315" x2="220" y2="65" class="line"/>
    <line x1="180" y1="325" x2="280" y2="75" class="line"/>
    <line x1="240" y1="335" x2="340" y2="85" class="line"/>
    <line x1="300" y1="345" x2="400" y2="95" class="line"/>
    <line x1="360" y1="355" x2="460" y2="105" class="line"/>
  </svg>
  <figcaption>
    The completed grid: a tessellation of identical parallelograms. Equal spacing within each family is guaranteed by the diagonal construction. Equal spacing <em>across</em> families — that the horizontal step matches the vertical step — is not guaranteed and not even meaningful. There is nothing to compare them with.
  </figcaption>
</figure>

<h2>4. A vector on the grid</h2>

<figure>
  <svg viewBox="0 0 600 380" xmlns="http://www.w3.org/2000/svg">
    <defs>
      <marker id="arrow-red" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
        <path d="M 0 0 L 10 5 L 0 10 z" fill="#c44"/>
      </marker>
      <marker id="arrow-dark" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
        <path d="M 0 0 L 10 5 L 0 10 z" fill="#222"/>
      </marker>
    </defs>

    <line x1="100" y1="285" x2="400" y2="335" class="line-faint"/>
    <line x1="120" y1="235" x2="420" y2="285" class="line-faint"/>
    <line x1="140" y1="185" x2="440" y2="235" class="line-faint"/>
    <line x1="160" y1="135" x2="460" y2="185" class="line-faint"/>
    <line x1="180" y1="85" x2="480" y2="135" class="line-faint"/>
    <line x1="120" y1="315" x2="220" y2="65" class="line-faint"/>
    <line x1="180" y1="325" x2="280" y2="75" class="line-faint"/>
    <line x1="240" y1="335" x2="340" y2="85" class="line-faint"/>
    <line x1="300" y1="345" x2="400" y2="95" class="line-faint"/>
    <line x1="360" y1="355" x2="460" y2="105" class="line-faint"/>

    <polyline points="130,290 310,320 350,220" class="step-path"/>

    <line x1="130" y1="290" x2="184" y2="299" class="basis" marker-end="url(#arrow-dark)"/>
    <line x1="130" y1="290" x2="148" y2="246" class="basis" marker-end="url(#arrow-dark)"/>

    <line x1="130" y1="290" x2="345" y2="222" class="vec" marker-end="url(#arrow-red)"/>

    <circle cx="130" cy="290" r="3.5" class="point"/>

    <text x="190" y="313" class="label">e₁</text>
    <text x="115" y="240" class="label">e₂</text>
    <text x="358" y="218" class="label" fill="#c44">v</text>
    <text x="240" y="270" class="annotation">v = 3·e₁ + 2·e₂  =  (3, 2)</text>
  </svg>
  <figcaption>
    A vector <em>v</em> on the grid is described by a pair of numbers: how many steps along the first family of lines, and how many along the second. Here <em>v</em> is three steps in the <em>e₁</em> direction and two steps in <em>e₂</em>, written <em>v</em> = (3, 2). The dashed path shows the construction; the red arrow is the vector itself. The same algebraic pair (3, 2) describes <em>any</em> vector with this same relative position on the grid.
  </figcaption>
</figure>

<h2>5. Why parallelograms, not squares?</h2>

<p>A natural question: if we want a grid, why not draw squares? The answer is that a square requires <em>more</em> structure than affine geometry has — it smuggles in a 90° angle and a length comparison across directions, neither of which exists yet.</p>

<figure>
  <svg viewBox="0 0 600 340" xmlns="http://www.w3.org/2000/svg">

    <line x1="25" y1="227.6" x2="205" y2="256.4" class="line"/>
    <line x1="39" y1="185.6" x2="219" y2="214.4" class="line"/>
    <line x1="53" y1="143.6" x2="233" y2="172.4" class="line"/>
    <line x1="67" y1="101.6" x2="247" y2="130.4" class="line"/>
    <line x1="35.8" y1="242.6" x2="86.2" y2="91.4" class="line"/>
    <line x1="85.8" y1="250.6" x2="136.2" y2="99.4" class="line"/>
    <line x1="135.8" y1="258.6" x2="186.2" y2="107.4" class="line"/>
    <line x1="185.8" y1="266.6" x2="236.2" y2="115.4" class="line"/>

    <text x="60" y="216" class="label" fill="#c44" font-size="18">?</text>

    <text x="135" y="300" class="annotation" text-anchor="middle">Affine grid</text>
    <text x="135" y="316" class="annotation" text-anchor="middle">no angle, no length comparison across directions</text>

    <line x1="325" y1="230" x2="490" y2="230" class="line"/>
    <line x1="325" y1="185" x2="490" y2="185" class="line"/>
    <line x1="325" y1="140" x2="490" y2="140" class="line"/>
    <line x1="325" y1="95" x2="490" y2="95" class="line"/>
    <line x1="340" y1="245" x2="340" y2="80" class="line"/>
    <line x1="385" y1="245" x2="385" y2="80" class="line"/>
    <line x1="430" y1="245" x2="430" y2="80" class="line"/>
    <line x1="475" y1="245" x2="475" y2="80" class="line"/>

    <path d="M 352 230 L 352 218 L 340 218" class="right-angle"/>

    <text x="407" y="300" class="annotation" text-anchor="middle">Square grid</text>
    <text x="407" y="316" class="annotation" text-anchor="middle">90° + equal step lengths assumed</text>
  </svg>
  <figcaption>
    On the left, the affine grid: parallelograms whose two directions are arbitrary. The "?" marks the angle that the geometry simply does not specify. On the right, a square grid: each cell carries a 90° claim (the small bracket) and an implicit equality between horizontal and vertical step length. Neither claim is available in affine geometry — there is no perpendicular, and no way to compare a step in one direction to a step in another. So the parallelogram grid is not a weaker choice. It is the most general thing affine geometry permits — and the only honest one.
  </figcaption>
</figure>

<p style="color:#888; font-size:0.88rem; margin-top:3rem;">Source: <a href="https://www.youtube.com/watch?v=ndWqKOJ9DgQ">WildLinAlg 1 — Introduction to Linear Algebra</a> by Norman Wildberger.</p>

</div>]]></content><author><name></name></author><category term="math" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Introduction to Universal Hyperbolic Geometry</title><link href="/2026/03/25/introduction-to-universal-hyperbolic-geometry.html" rel="alternate" type="text/html" title="Introduction to Universal Hyperbolic Geometry" /><published>2026-03-25T00:00:00+00:00</published><updated>2026-03-25T00:00:00+00:00</updated><id>/2026/03/25/introduction-to-universal-hyperbolic-geometry</id><content type="html" xml:base="/2026/03/25/introduction-to-universal-hyperbolic-geometry.html"><![CDATA[<p>What really made UHG click for me was this lecture:</p>

<div class="embed-container">
  <iframe src="https://www.youtube.com/embed/z0-u7dbCIF4" width="700" height="480" frameborder="0" allowfullscreen="true">
  </iframe>
</div>

<p>This lecture basically explains this <a href="https://web.maths.unsw.edu.au/~norman/papers/AffineProjArXiV.pdf">paper</a> visually.</p>

<h2 id="starting-out-with-two-dimensions">Starting out with two dimensions</h2>

<p>\(\mathbb{F}^2 \quad v=(x, y) \quad A=\left(\begin{array}{ll}a &amp; b \\ b &amp; c\end{array}\right)\) fixed
then define</p>

\[\begin{aligned}
&amp; v_1 \cdot v_2=v_1 A v_2^{\top}=\left(x_1 y_1\right)\left(\begin{array}{ll}
a &amp; b \\
b &amp; c
\end{array}\right)\binom{x_2}{y_2} \\
&amp; =a x_1 x_2+b x_1 y_2+b x_2 y_1+c y_1 y_2
\end{aligned}\]

<h2 id="generalizing-to-3d">Generalizing to 3D</h2>

<p><img src="/assets/images/null-cone.png" alt="null-cone.png" /></p>

<h1 id="resources">Resources</h1>

<ul>
  <li><a href="https://web.maths.unsw.edu.au/~norman/Hyperbolic1.htm">Geometer’s Sketchpad worksheets</a></li>
</ul>]]></content><author><name></name></author><summary type="html"><![CDATA[What really made UHG click for me was this lecture:]]></summary></entry><entry><title type="html">Tools &amp;amp; Workflows I Invented On Principle (Jan–Mar 2026)</title><link href="/jekyll/update/2026/03/04/tools-and-workflows-invented.html" rel="alternate" type="text/html" title="Tools &amp;amp; Workflows I Invented On Principle (Jan–Mar 2026)" /><published>2026-03-04T00:00:00+00:00</published><updated>2026-03-04T00:00:00+00:00</updated><id>/jekyll/update/2026/03/04/tools-and-workflows-invented</id><content type="html" xml:base="/jekyll/update/2026/03/04/tools-and-workflows-invented.html"><![CDATA[<p><a href="https://dynamicland.org/">Dynamicland</a> is still my guiding north star — the idea that computation should be a seeing tool, something that makes invisible structure visible and shareable. That principle shaped everything I built this quarter.</p>

<h2 id="code-shipped">Code Shipped</h2>

<table>
  <thead>
    <tr>
      <th>Item</th>
      <th>Date</th>
      <th>Status</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><a href="https://github.com/trangtinkers/beautiful-racket-stacker">Stacker DSL (reader + macro expander) in Racket</a></td>
      <td>Jan 20–22</td>
      <td>Completed</td>
    </tr>
    <tr>
      <td>Matrix-vector multiplication module in Racket</td>
      <td>Jan 26</td>
      <td>Completed</td>
    </tr>
    <tr>
      <td>PowerShell <code class="language-plaintext highlighter-rouge">git-summary</code> alias</td>
      <td>Jan 30</td>
      <td>Completed</td>
    </tr>
    <tr>
      <td>Single-neuron malt training demo</td>
      <td>Jan 16</td>
      <td>Completed</td>
    </tr>
    <tr>
      <td><a href="https://github.com/trangtinkers/Universal-Hyperbolic-Geometry">UHG visual explorer (Klein disk, drag-point)</a></td>
      <td>Feb 13</td>
      <td>Completed + discarded</td>
    </tr>
    <tr>
      <td>Quadrance computation table</td>
      <td>Feb 17</td>
      <td>Completed + discarded</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">cross_ratio_proj()</code> Python function</td>
      <td>Feb 18</td>
      <td>Completed</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">quadrance()</code> + <code class="language-plaintext highlighter-rouge">loss()</code> PyTorch embedding functions</td>
      <td>Feb 23</td>
      <td>Written, framing paused</td>
    </tr>
    <tr>
      <td><a href="https://github.com/AMINE1921/instacomments/issues/1">instacomments README fix</a></td>
      <td>Feb 19</td>
      <td>Shipped</td>
    </tr>
    <tr>
      <td>Bilinear form Scheme DSL stubs</td>
      <td>Mar 2</td>
      <td>Exploratory</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="original-frameworks-invented">Original Frameworks Invented</h2>

<ol>
  <li>
    <p><strong>“Seeing Tools” as a design philosophy</strong> — DSLs, visualizers, tables as instruments for making invisible structure visible. Coined and elaborated across Feb 13–25. “Inventing on Principle at its finest! Bret Victor would be proud of me.”</p>
  </li>
  <li>
    <p><strong>“Probing” as a research epistemology</strong> — when facing an opaque system, don’t demand a theory first; send cheap experiments and read the echoes. Crystallized Feb 26. “This is how working mathematicians actually experience the subject.”</p>
  </li>
  <li>
    <p><strong>UHG as an “API to the hyperbolic plane”</strong> — <a href="https://www.youtube.com/playlist?list=PLIljB45xT85Ar_tuHmRDI2Xr5tWOTI1oW">Universal Hyperbolic Geometry</a> as a kernel/seeing tool for hyperbolic ML, replacing classical logmap/expmap. Feb 28. “Universal Hyperbolic Geometry is an interface to the API of the hyperbolic plane, one whose implementation I could not take a peek at.”</p>
  </li>
  <li>
    <p><strong>DSL-as-cheaper-neural-net argument</strong> — structured domains don’t need neural nets if you design the right language. Jan 18. “Often, creating a domain specific language is a much cheaper solution!”</p>
  </li>
  <li>
    <p><strong>“Math as DSL / proofs as programs”</strong> — arrived at the Curry-Howard correspondence organically through the stacker work. Jan 21. “Now, I see math as choosing the right language. Proofs are just programs that type-check.”</p>
  </li>
  <li>
    <p><strong>Conceptual Design as constraint analysis</strong> — a design without forcing functions cannot evolve. Applied to UHG Feb 15. “Limitations are forcing functions. But right now UHG has no forcing functions.”</p>
  </li>
</ol>

<hr />

<h2 id="workflows-developed">Workflows Developed</h2>

<ol>
  <li>
    <p><strong>REPL + Claude Code learning loop</strong> (DrRacket + timed work sessions) — Jan 15 onward, sustained. “REPL-based interactive development might solve my problem with slow feedback loops.”</p>
  </li>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">uv</code> Python project workflow</strong> — adopted Jan 23, ongoing. Figured out through trial and error: <code class="language-plaintext highlighter-rouge">uv init</code> → <code class="language-plaintext highlighter-rouge">uv add</code> → <code class="language-plaintext highlighter-rouge">uv sync</code> → <code class="language-plaintext highlighter-rouge">uv run</code>.</p>
  </li>
  <li>
    <p><strong>Intermediate artifacts for learning hard math</strong> — build a series of visualizations, each making one property salient while hiding others. Feb 18. “The quadrance computation table (makes the 1/(1-r²) blowup salient, hides the projective formalism).”</p>
  </li>
  <li>
    <p><strong>Live-blog while working</strong> (Roam capture → Twitter) — Feb 18, active. “This workflow just grew organically during this work session. <a href="https://www.henrikkarlsson.xyz/p/unfolding">Everything that turned out well in my life followed the same design process.</a>”</p>
  </li>
  <li>
    <p><strong>Reply-to-build-audience Twitter strategy</strong> — high-quality replies to niche-popular users rather than broadcasting to a general audience. Working: <a href="https://x.com/DefenderOfBasic/status/2025390539844051129">DefenderOfBasic turned on notifications</a>, +6 followers from <a href="https://x.com/trangquest/status/2025369249770856636">one reply</a>.</p>
  </li>
  <li>
    <p><strong>Somatic processing before cognitive work</strong> — exercise/emotional release as prerequisite to focus. Feb 12–21. “I felt depressed until 6 pm. Then, I decided to go on a walk and exercise. Now, I’m working again like nothing happened.”</p>
  </li>
</ol>]]></content><author><name></name></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[Dynamicland is still my guiding north star — the idea that computation should be a seeing tool, something that makes invisible structure visible and shareable. That principle shaped everything I built this quarter.]]></summary></entry><entry><title type="html">How to build software you actually use</title><link href="/jekyll/update/2026/02/04/conceptual-design.html" rel="alternate" type="text/html" title="How to build software you actually use" /><published>2026-02-04T00:00:00+00:00</published><updated>2026-02-04T00:00:00+00:00</updated><id>/jekyll/update/2026/02/04/conceptual-design</id><content type="html" xml:base="/jekyll/update/2026/02/04/conceptual-design.html"><![CDATA[<p>The bridge between these ideas could be that good conceptual design helps you identify what “form” (in software) appropriately fits a given “context” (the user’s needs, workflow, existing mental models). By clearly articulating concepts and their purposes, you’re essentially:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. Understanding the context—what problems users face, what they're trying to accomplish
2. Proposing forms—software concepts that resolve those problems
3. Testing the fit—whether the concept's operational principle aligns with how users would naturally think and work
</code></pre></div></div>

<p>So in my framing: the <strong>concept</strong> (not just “the tool”) would be the form, and the <strong>user’s actual practices, goals, and environment</strong> would be the context. A well-designed concept achieves “goodness of fit” between these.</p>

<h1 id="the-first-10-minutes">The First 10 Minutes</h1>

<blockquote>
  <p>Gifford looked at the user data and found that Instagram’s most devoted users all did similar actions. In fact, he found that these devoted users did the same things in the first 10 minutes of using Instagram. In the data, Gifford saw a clear pattern for successful onboarding. […] the Instagram team surfaced what worked best, and they re-designed the system to make those “first 10 minutes” behaviors the default, not just a happy accident.</p>
</blockquote>]]></content><author><name></name></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[The bridge between these ideas could be that good conceptual design helps you identify what “form” (in software) appropriately fits a given “context” (the user’s needs, workflow, existing mental models). By clearly articulating concepts and their purposes, you’re essentially:]]></summary></entry><entry><title type="html">Towards Networked Science</title><link href="/jekyll/update/2025/09/30/towards-networked-science.html" rel="alternate" type="text/html" title="Towards Networked Science" /><published>2025-09-30T21:49:00+00:00</published><updated>2025-09-30T21:49:00+00:00</updated><id>/jekyll/update/2025/09/30/towards-networked-science</id><content type="html" xml:base="/jekyll/update/2025/09/30/towards-networked-science.html"><![CDATA[<p>I was never consistent with programming until I adopted <a href="https://simonwillison.net/2022/Oct/29/the-perfect-commit/">Simon Willison’s workflow</a>. Have I produced anything substantial yet? Not really. But I want to participate in the tech blogger scene. Writers like Bruce Schneier, Simon Willison, and George Hotz are the ones I’m following most closely.</p>

<p>The next step is showing others how to follow along, which reminds me of <a href="https://mathstodon.xyz/@tao/115254145226514817">Terence Tao’s crowdsourcing project</a>. My prediction: as government funding for universities continues to shrink, more academic work will migrate online, leveraging what Yochai Benkler calls “<a href="https://x.com/Conaw/status/1173339039660773376">The Wealth of Networks</a>”. Top researchers like Tao increasingly need contributions from amateur researchers doing independent work in their free time. Political uncertainty is essentially forcing this shift.</p>

<blockquote>
  <p>How do we create a context where more is being added to the commons? How do we convert rival to non-rival goods?</p>
</blockquote>

<blockquote>
  <p>If we want abundance, we need to expand the commons, especially at the frontiers of human knowledge. All of my work in tech has been because I read Benkler, and one way or another its been political. It’s just a different kind of political. ~ Conor White-Sullivan</p>
</blockquote>]]></content><author><name></name></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[I was never consistent with programming until I adopted Simon Willison’s workflow. Have I produced anything substantial yet? Not really. But I want to participate in the tech blogger scene. Writers like Bruce Schneier, Simon Willison, and George Hotz are the ones I’m following most closely.]]></summary></entry></feed>