<?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>Jaddle</title>
<link>https://brendanvr.github.io/jaddle-blog/</link>
<atom:link href="https://brendanvr.github.io/jaddle-blog/index.xml" rel="self" type="application/rss+xml"/>
<description>A blog built with Quarto</description>
<generator>quarto-1.10.19</generator>
<lastBuildDate>Wed, 28 Oct 2026 16:00:00 GMT</lastBuildDate>
<item>
  <title>Where Should the Air Detectives Stand?</title>
  <dc:creator>Brendan van Rooyen</dc:creator>
  <link>https://brendanvr.github.io/jaddle-blog/posts/monitoring-network-design/monitor-network-design.html</link>
  <description><![CDATA[ 





<p>Imagine you’ve been put in charge of the smoke detectors for an entire country. There’s one catch: you only get <strong>50 of them</strong>. Where do you put them?</p>
<p>That’s not just a thought experiment. The US Environmental Protection Agency (EPA) runs about a thousand monitors that measure <strong>PM2.5</strong>: specks of dust, soot and smoke so small that about thirty of them side by side would match the width of a human hair. They’re small enough to get deep into your lungs, so knowing how much is in the air each day matters.</p>
<p>Every five years, each US state has to review its monitoring network. Is it still doing its job? Do we need more monitors? Could a few be retired? Behind all of that sits one question:</p>
<blockquote class="blockquote">
<p><strong>If I can afford <code>k</code> monitors, which <code>k</code> spots tell me the most about the air in all the places where I <em>don’t</em> have a monitor?</strong></p>
</blockquote>
<p>I built an example for <strong>Jaddle</strong>, an optimisation library I work on, that answers this question with two years of real EPA data. Here’s how it works, no PhD needed.</p>
<section id="why-this-is-harder-than-it-looks" class="level2">
<h2 class="anchored" data-anchor-id="why-this-is-harder-than-it-looks">Why this is harder than it looks</h2>
<p>After some tidying up (keeping sites that report reliably in both 2023 and 2024), we have <strong>705 sites</strong>. I set aside a random third of them, 235 sites, as <strong>targets</strong>: places where we want to <em>know</em> the air quality but won’t put a monitor. The other <strong>470 are candidates</strong>.</p>
<p>How many ways are there to pick 50 sites from 470? About <strong>10^68</strong>. That’s a 1 followed by 68 zeros, more than the number of atoms that make up the Earth (a mere 10^50 or so). Even checking a billion networks a second, you’d still be going long after the Sun burns out.</p>
<p>So we need to be clever.</p>
</section>
<section id="step-1-learn-how-the-air-behaves" class="level2">
<h2 class="anchored" data-anchor-id="step-1-learn-how-the-air-behaves">Step 1: Learn how the air behaves</h2>
<p>Air pollution doesn’t happen site by site. A weather system parks over the Midwest, or wildfire smoke drifts in from Canada (as it did across the Northeast in June 2023), and whole regions go up and down <em>together</em>.</p>
<p>That’s useful. If you know how a few places are doing, you can make a good guess about their neighbours, a bit like guessing the score of a football match from the crowd noise outside the stadium.</p>
<p>Using the 2023 data, the model learns <strong>20 regional patterns</strong> (weather systems, smoke plumes, the seasons) and how strongly each site follows each one. Those 20 patterns explain about <strong>76%</strong> of the day-to-day ups and downs. The rest is local: a busy road, a neighbour’s barbecue, a wobbly sensor.</p>
<p>Once we have that model, we can score any network: <em>how much of the regional pattern at the targets does it leave unexplained?</em> Lower is better.</p>
</section>
<section id="step-2-swap-the-light-switch-for-a-dimmer" class="level2">
<h2 class="anchored" data-anchor-id="step-2-swap-the-light-switch-for-a-dimmer">Step 2: Swap the light switch for a dimmer</h2>
<p>Each candidate site is like a light switch: monitor <strong>on</strong> or <strong>off</strong>. All those on/off choices are what make the problem so huge.</p>
<p>So we cheat, temporarily. We replace every switch with a <strong>dimmer</strong>, so a site can have 0.4 of a monitor, or 0.93 of a monitor. The rule is that the dimmers must add up to 50.</p>
<p>That sounds silly, but it turns a bumpy, impossible search into a smooth <strong>convex</strong> problem. Picture a bowl: wherever you drop a marble, it rolls to the same lowest point. Jaddle’s convex solver found the bottom of this bowl in <strong>36 steps, about 3 seconds</strong>.</p>
<p>Then we turn the dimmers back into switches. We keep the 50 brightest sites, then try swapping a chosen site for an unchosen one, keeping any swap that helps. After just <strong>5 swaps</strong> nothing else improves.</p>
</section>
<section id="step-3-get-a-receipt" class="level2">
<h2 class="anchored" data-anchor-id="step-3-get-a-receipt">Step 3: Get a receipt</h2>
<p>This is my favourite part.</p>
<p>The dimmer version of the problem has <em>more</em> freedom than the real one: every real network is just a dimmer setting where each dial happens to be at 0 or 1. So the best dimmer answer is a <strong>floor</strong>. No real network of 50 monitors can ever do better than it.</p>
<p>Our network lands <strong>within 1.3% of that floor</strong>. So without checking all 10^68 options, we can say with confidence that no 50-monitor network beats ours by more than about 1.3% (as judged by the model).</p>
<p>Most methods hand you an answer. This one hands you an answer <em>and a receipt</em>.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://brendanvr.github.io/jaddle-blog/posts/monitoring-network-design/images/monitor_network_map.png" class="img-fluid figure-img" alt="Map of the contiguous United States showing 705 PM2.5 monitoring sites. Grey crosses mark target sites, coloured dots mark candidate sites shaded by their relaxed weight, and 50 red circles mark the chosen monitoring network, spread across both coasts and the interior."></p>
<figcaption>Every dot is a PM2.5 site. Grey crosses are the targets we’re trying to predict. The colour shows how much “dimmer” the relaxed problem gave each candidate (yellow = full monitor, purple = none), and the red circles are the final 50-monitor network.</figcaption>
</figure>
</div>
<p>A few things jump out of the map:</p>
<ul>
<li><strong>The chosen sites aren’t evenly spaced.</strong> There are clusters along the West Coast and in the Northeast, where lots of people (and lots of target sites) live.</li>
<li><strong>Big empty areas still get a monitor or two.</strong> One well-placed sensor can “speak for” a whole region that moves together.</li>
<li><strong>Most dimmers ended up fully on or fully off.</strong> Only 47 of the 470 were in between, so turning the dimmers back into switches was easy.</li>
</ul>
</section>
<section id="did-it-actually-work-the-2024-exam" class="level2">
<h2 class="anchored" data-anchor-id="did-it-actually-work-the-2024-exam">Did it actually work? The 2024 exam</h2>
<p>A model that’s only tested on the data it learned from is like a student marking their own homework. So the real test uses <strong>2024</strong>, a year the model never saw.</p>
<p>For each network, we predicted the daily PM2.5 at all 235 target sites from that day’s readings at the 50 monitors, then measured the typical error:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>50 monitors, placed by…</th>
<th style="text-align: right;">Typical error in 2024 (µg/m³)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Nobody (just use last year’s average)</td>
<td style="text-align: right;">5.21</td>
</tr>
<tr class="even">
<td>Spreading them evenly over the map</td>
<td style="text-align: right;">4.16</td>
</tr>
<tr class="odd">
<td>Picking at random (average of 20 tries)</td>
<td style="text-align: right;">4.04</td>
</tr>
<tr class="even">
<td>Greedy: add the most helpful site, one at a time</td>
<td style="text-align: right;">3.70</td>
</tr>
<tr class="odd">
<td><strong>Optimised design (this article)</strong></td>
<td style="text-align: right;"><strong>3.71</strong></td>
</tr>
</tbody>
</table>
<p>The optimised network cuts the error by about <strong>8% compared with random picks</strong>, on data it had never seen.</p>
<p>Two things surprised me:</p>
<ol type="1">
<li><strong>Spreading monitors evenly does <em>worse</em> than random.</strong> It feels fair, but it puts sensors in big, empty, mostly clean areas. The largest errors come from smoggy days in crowded places, and an even spread under-covers those.</li>
<li><strong>The simple “greedy” method is just as good here.</strong> I’ll be honest about that. Greedy is a strong, well-known approach. What it can’t give you is the receipt. Only the dimmer trick can tell you that nothing out there does much better.</li>
</ol>
</section>
<section id="how-many-monitors-is-enough" class="level2">
<h2 class="anchored" data-anchor-id="how-many-monitors-is-enough">How many monitors is enough?</h2>
<p>Fifty was an arbitrary choice, so I asked Jaddle to solve the problem for <strong>16 different budgets, from 5 to 150 monitors, in one batched call</strong> (using JAX’s <code>vmap</code>, which runs many copies of the solver side by side).</p>
<p>The headline:</p>
<ul>
<li><strong>About 35 well-placed monitors predict as well as 150 randomly placed ones.</strong> Placement matters more than quantity.</li>
<li><strong>Returns shrink fast.</strong> The 150th monitor is worth roughly <strong>a fiftieth</strong> of the 16th. For a state deciding which monitors to retire, that curve shows where cutting starts to really hurt.</li>
<li><strong>The receipt gets better as the budget grows</strong>: within 5% of the floor from about 24 monitors, and within 0.1% at 150.</li>
</ul>
</section>
<section id="bonus-which-monitor-deserves-an-upgrade" class="level2">
<h2 class="anchored" data-anchor-id="bonus-which-monitor-deserves-an-upgrade">Bonus: which monitor deserves an upgrade?</h2>
<p>The solver can also say how much better the <em>best possible network</em> would get if any single site had a less noisy sensor (for example, by installing a second instrument next to the first). One calculation covers all 705 sites at once.</p>
<p>The top of the list: <strong>Tacoma (South 36th St), Washington</strong>, followed by sites in California, Colorado, Idaho and Nevada. So the same tool that picks locations also points to where an equipment upgrade would help most.</p>
</section>
<section id="the-takeaways" class="level2">
<h2 class="anchored" data-anchor-id="the-takeaways">The takeaways</h2>
<ul>
<li><strong>Many “choose the best few” problems are secretly huge.</strong> Picking 50 from 470 has more options than there are atoms in the Earth.</li>
<li><strong>Relaxing the rules helps you solve the strict version.</strong> Swapping switches for dimmers turns an impossible search into a 3-second solve.</li>
<li><strong>A good answer with a guarantee beats a good answer alone.</strong> The relaxation doesn’t just find a strong network; it proves there isn’t a much better one.</li>
<li><strong>Test on data the model hasn’t seen.</strong> The improvement held up in 2024, though it was smaller than the model predicted, because some pollution is just local and no network can see it coming.</li>
</ul>
</section>
<section id="try-it-yourself" class="level2">
<h2 class="anchored" data-anchor-id="try-it-yourself">Try it yourself</h2>
<p>The full example, from downloading the EPA data to drawing the map, is <a href="https://github.com/BrendanVR/Jaddle/blob/main/examples/cp/monitor_network_design.py"><code>examples/cp/monitor_network_design.py</code></a> in the <a href="https://github.com/BrendanVR/Jaddle">Jaddle repository</a>. It runs in a few minutes and uses only public data.</p>
<p>If you work on sensor placement, experimental design or any other “where do I put my limited stuff?” problem, I’d love to hear how you approach it.</p>


</section>

 ]]></description>
  <category>optimisation</category>
  <category>air quality</category>
  <category>JAX</category>
  <category>Jaddle</category>
  <guid>https://brendanvr.github.io/jaddle-blog/posts/monitoring-network-design/monitor-network-design.html</guid>
  <pubDate>Wed, 28 Oct 2026 16:00:00 GMT</pubDate>
  <media:content url="https://brendanvr.github.io/jaddle-blog/posts/monitoring-network-design/monitoring-network-design-logo.svg" medium="image" type="image/svg+xml"/>
</item>
<item>
  <title>Hiring a Referee for Your Search Results</title>
  <dc:creator>Brendan van Rooyen</dc:creator>
  <link>https://brendanvr.github.io/jaddle-blog/posts/rag-reranker/rag-reranker.html</link>
  <description><![CDATA[ 





<p>Ask a language model a question, and in a <strong>retrieval-augmented generation</strong> (RAG) system it doesn’t answer from memory alone. It first goes to a library, pulls out a handful of pages and reads them, then answers.</p>
<p>Generating the answer is the easy part. Picking the pages is harder. Give the model the wrong pages and it will summarise them confidently anyway.</p>
<p>This post follows one project that improves the fetching in layers, with each small idea built on the last. The final layer is a learned reranker. Training it means solving a family of quadratic programs, and that’s where <a href="https://github.com/BrendanVR/Jaddle">Jaddle</a> comes in.</p>
<div class="callout callout-style-default callout-note callout-titled" title="The short version">
<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">
<span class="screen-reader-only">Note</span>The short version
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li>Keyword search scores <strong>0.681</strong>. Adding embeddings and fusing the two rankings gets <strong>0.731</strong>.</li>
<li>A 17-feature reranker, trained as a ranking SVM, lifts that to <strong>0.770</strong>: about 4 points, with a 95% interval of +2.1 to +6.0.</li>
<li>Jaddle solves the reranker’s whole training path, 11 quadratic programs, in one batched GPU call in about 3 seconds.</li>
</ul>
</div>
</div>
<section id="keeping-score" class="level2">
<h2 class="anchored" data-anchor-id="keeping-score">Keeping score</h2>
<p>The test bed is <a href="https://github.com/beir-cellar/beir">SciFact</a>, a set of scientific claims paired with the research abstracts that support or refute them. Every score in this post is <strong>nDCG@10</strong> on its held-out test claims. The metric looks only at the top ten results. It gives more credit for a relevant abstract near the top than near the bottom, and a perfect ranking scores 1. A gain of 0.01 is one “point”.</p>
</section>
<section id="the-pipeline-at-a-glance" class="level2">
<h2 class="anchored" data-anchor-id="the-pipeline-at-a-glance">The pipeline at a glance</h2>
<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
  Q[Claim] --&gt; K[Keyword search&lt;br/&gt;BM25]
  Q --&gt; D[Meaning search&lt;br/&gt;embeddings]
  K --&gt; F[Rank fusion]
  D --&gt; F
  F --&gt; S[Top 100&lt;br/&gt;candidates]
  S --&gt; R[Referee&lt;br/&gt;17-feature reranker]
  R --&gt; T[Top 10&lt;br/&gt;to the model]
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
</section>
<section id="layer-1-match-the-words" class="level2">
<h2 class="anchored" data-anchor-id="layer-1-match-the-words">Layer 1: Match the words</h2>
<p>The classic starting point is <strong>BM25</strong>. Search for “does coffee help memory?” and it scores each page by how often <em>coffee</em>, <em>help</em> and <em>memory</em> appear in it, with two refinements:</p>
<ul>
<li><strong>Rare words count for more.</strong> Matching <em>caffeine</em> is far more telling than matching <em>the</em>.</li>
<li><strong>Long pages don’t win by bulk.</strong> A page can’t win just by containing a lot of words.</li>
</ul>
<p>BM25 is fast, simple and hard to beat. It scores <strong>0.681</strong>.</p>
<p>Its blind spot is vocabulary. If the claim says <em>car</em> and the abstract says <em>automobile</em>, BM25 can’t tell they’re related.</p>
</section>
<section id="layer-2-learn-which-words-matter" class="level2">
<h2 class="anchored" data-anchor-id="layer-2-learn-which-words-matter">Layer 2: Learn which words matter</h2>
<p>SciFact comes with training labels: for each training claim, the abstracts that are actually relevant. We can use them to tune BM25.</p>
<p>Give every word its own multiplier, starting at 1. Then adjust the multipliers so that, for each training claim, relevant abstracts outscore irrelevant ones by a margin. Words that point towards the right answer get boosted, and words that are mostly noise get turned down.</p>
<p>That lifts the score to <strong>0.708</strong>. It’s better, but the helper still only sees words.</p>
</section>
<section id="layer-3-learn-what-the-words-mean" class="level2">
<h2 class="anchored" data-anchor-id="layer-3-learn-what-the-words-mean">Layer 3: Learn what the words mean</h2>
<p>A <strong>text embedding</strong> turns a passage into a list of numbers (384 of them here, from the compact bge-small model). Passages with similar meanings get similar lists, so <em>car</em> and <em>automobile</em> end up close together even though they share no letters.</p>
<p>To search, we embed the claim the same way and look for abstracts whose vectors point in the same direction. This is <strong>dense retrieval</strong>, and on its own it scores <strong>0.720</strong>, already ahead of the tuned keywords.</p>
<p>Keyword search and dense retrieval make different mistakes, so it pays to combine them. <strong>Reciprocal rank fusion</strong> does this using only each page’s position in the two rankings. An abstract that ranks well in both rises to the top. Because it uses positions, it never has to reconcile two scores on different scales. The fused ranking scores <strong>0.731</strong>. Its top 100 also holds 96.5% of the relevant abstracts, up from 92% for BM25’s top 100.</p>
</section>
<section id="layer-4-a-referee-for-the-shortlist" class="level2">
<h2 class="anchored" data-anchor-id="layer-4-a-referee-for-the-shortlist">Layer 4: A referee for the shortlist</h2>
<p>We don’t have to trust any single ranking. Take the fused top 100 as a shortlist, and bring in a <strong>referee</strong> to reorder it.</p>
<p>The referee looks at 17 features of each (claim, abstract) pair:</p>
<ul>
<li><strong>Keyword signals:</strong> BM25 on the full text, on the title alone and on exact word forms, TF-IDF, and the learned term-weight score from Layer 2</li>
<li><strong>Meaning signals:</strong> embedding similarity, plus an older technique called LSA</li>
<li><strong>Coverage:</strong> how much of the claim the abstract mentions, including runs of consecutive words</li>
<li><strong>Context:</strong> the lengths of the claim and the abstract, and the fused rank itself</li>
</ul>
<p>Every feature means the same thing for every claim. “High embedding similarity” is good news whatever the topic, so what the referee learns on training claims carries over to claims it has never seen.</p>
<p>The referee itself is simple: multiply each feature by a weight and add them up. The question is what the weights should be.</p>
<section id="training-the-referee" class="level3">
<h3 class="anchored" data-anchor-id="training-the-referee">Training the referee</h3>
<p>For each training claim, we pair every relevant abstract with five irrelevant ones from the same shortlist, which gives about 3,600 pairs. We want two things:</p>
<ol type="1">
<li>In every pair, the relevant abstract should score <strong>at least 1 point higher</strong> than the irrelevant one.</li>
<li>The weights should stay <strong>small</strong>, so the referee doesn’t memorise the training claims.</li>
</ol>
<p>These two goals pull against each other, so we allow each pair a slack <img src="https://latex.codecogs.com/png.latex?%5Cxi_t"> for falling short and charge a price for using it. With weights <img src="https://latex.codecogs.com/png.latex?v"> and feature vectors <img src="https://latex.codecogs.com/png.latex?x_t%5E+"> and <img src="https://latex.codecogs.com/png.latex?x_t%5E-"> for the two abstracts in pair <img src="https://latex.codecogs.com/png.latex?t">, the problem is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmin_%7Bv,%5C%20%5Cxi%7D%5C%20%5C%20%5Ctfrac12%20%5C%7Cv%5C%7C%5E2%20+%20%5Cfrac%7BC%7D%7BT%7D%5Csum_%7Bt=1%7D%5E%7BT%7D%20%5Cxi_t%0A%5Cquad%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%0A(x_t%5E+%20-%20x_t%5E-)%5E%5Ctop%20v%20%5C%20%5Cge%5C%201%20-%20%5Cxi_t,%20%5Cqquad%20%5Cxi_t%20%5Cge%200%20.%0A"></p>
<p>This is a <strong>ranking SVM</strong>, and it is a <strong>quadratic program</strong>: a quadratic objective under linear constraints. The constant <img src="https://latex.codecogs.com/png.latex?C"> sets the balance. A large <img src="https://latex.codecogs.com/png.latex?C"> insists on getting the pairs right, while a small <img src="https://latex.codecogs.com/png.latex?C"> prefers simpler weights.</p>
</section>
</section>
<section id="where-jaddle-comes-in" class="level2">
<h2 class="anchored" data-anchor-id="where-jaddle-comes-in">Where Jaddle comes in</h2>
<p>One quadratic program is easy enough. But we don’t know the right <img src="https://latex.codecogs.com/png.latex?C"> in advance, so we solve the problem for 11 values of <img src="https://latex.codecogs.com/png.latex?C"> and keep whichever does best on a set of validation claims.</p>
<p><img src="https://latex.codecogs.com/png.latex?C"> appears only in the objective’s linear term. Every problem on the path therefore has the same structure, and Jaddle solves all 11 <strong>side by side in a single batched call</strong> on the GPU. Each problem has about 3,600 variables, and the whole path takes about <strong>3 seconds</strong>. As a check, the chosen problem is solved again with <a href="../../posts/clarabel-vs-jaddle/clarabel-vs-jaddle.html">Clarabel</a>, an interior-point solver, and the optimal values agree to about 1e-5.</p>
<p>Since the whole path is this cheap, there’s no need to guess <img src="https://latex.codecogs.com/png.latex?C">. We can try every value and keep the best. The same trick fits the term weights in Layer 2.</p>
</section>
<section id="results" class="level2">
<h2 class="anchored" data-anchor-id="results">Results</h2>
<table class="table-striped caption-top table">
<caption>Test nDCG@10 on SciFact, from one run on a laptop GPU. Changes are in points over BM25.</caption>
<thead>
<tr class="header">
<th>Stage</th>
<th style="text-align: right;">nDCG@10</th>
<th style="text-align: right;">Change</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>BM25 keyword search</td>
<td style="text-align: right;">0.681</td>
<td style="text-align: right;"></td>
</tr>
<tr class="even">
<td>Learned term weights</td>
<td style="text-align: right;">0.708</td>
<td style="text-align: right;">+2.7</td>
</tr>
<tr class="odd">
<td>Dense retrieval (embeddings)</td>
<td style="text-align: right;">0.720</td>
<td style="text-align: right;">+3.9</td>
</tr>
<tr class="even">
<td>Rank fusion of BM25 and dense</td>
<td style="text-align: right;">0.731</td>
<td style="text-align: right;">+5.0</td>
</tr>
<tr class="odd">
<td>Referee, 14 keyword features only</td>
<td style="text-align: right;">0.724</td>
<td style="text-align: right;">+4.3</td>
</tr>
<tr class="even">
<td><strong>Referee, all 17 features</strong></td>
<td style="text-align: right;"><strong>0.770</strong></td>
<td style="text-align: right;"><strong>+8.9</strong></td>
</tr>
</tbody>
</table>
<p>The full referee beats rank fusion by <strong>4.0 points</strong> (95% interval +2.1 to +6.0, from a paired bootstrap over test claims). The validation set agrees: 0.817 against 0.791.</p>
<p>The embeddings do two jobs:</p>
<ul>
<li><strong>They widen the net.</strong> Fusion’s shortlist contains relevant abstracts that BM25’s top 100 misses.</li>
<li><strong>They give the referee a signal keywords can’t.</strong> Without the three embedding features, the referee <em>loses</em> to plain fusion (0.724 against 0.731).</li>
</ul>
<p>The learned weights show what the referee relies on. Embedding similarity carries the most weight. The learned term score and coverage come next, contributing the precision of exact keyword matches. LSA gets no weight at all, because the embeddings already provide everything it offers.</p>
<div class="callout callout-style-default callout-warning callout-titled" title="A fair comparison">
<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">
<span class="screen-reader-only">Warning</span>A fair comparison
</div>
</div>
<div class="callout-body-container callout-body">
<p>The referee is trained on SciFact’s own labelled claims. Many published retrieval results are <em>zero-shot</em>: the models never see the target dataset’s labels. Read 0.770 as evidence that this recipe works when you have labelled examples. It is not a general claim about how this pipeline compares with every other search system.</p>
</div>
</div>
</section>
<section id="the-takeaway" class="level2">
<h2 class="anchored" data-anchor-id="the-takeaway">The takeaway</h2>
<p>Good retrieval is built in layers:</p>
<ol type="1">
<li><strong>Match the words.</strong> BM25 is a strong, cheap baseline.</li>
<li><strong>Learn which words matter</strong> from labelled examples.</li>
<li><strong>Add meaning</strong> with embeddings, so <em>car</em> and <em>automobile</em> finally get along.</li>
<li><strong>Bring in a referee</strong> to reorder the shortlist, and let a fast batched solver choose its weights.</li>
</ol>
<p>None of these layers is clever on its own. Stacked together, they pick the right pages more often, which gives the model a better chance of answering correctly.</p>
<p>The full example is <a href="https://github.com/BrendanVR/Jaddle/blob/main/examples/cp/rag_reranker.py"><code>examples/cp/rag_reranker.py</code></a> in the Jaddle repository.</p>


</section>

 ]]></description>
  <category>jaddle</category>
  <category>optimisation</category>
  <category>machine learning</category>
  <category>RAG</category>
  <guid>https://brendanvr.github.io/jaddle-blog/posts/rag-reranker/rag-reranker.html</guid>
  <pubDate>Sat, 24 Oct 2026 16:00:00 GMT</pubDate>
  <media:content url="https://brendanvr.github.io/jaddle-blog/posts/rag-reranker/rag-reranker-logo.svg" medium="image" type="image/svg+xml"/>
</item>
<item>
  <title>Why a General Convex Solver Matters in the Age of Foundation Models</title>
  <dc:creator>Brendan van Rooyen</dc:creator>
  <link>https://brendanvr.github.io/jaddle-blog/posts/convex-in-the-age-of-foundation-models/convex-in-the-age-of-foundation-models.html</link>
  <description><![CDATA[ 





<p>Large language models can write sonnets, fix code and explain photosynthesis in the voice of a pirate. They are one of the most remarkable things computer science has produced, and this essay is not here to argue otherwise.</p>
<p>It is here to stick up for an older, quieter idea that does not get as much attention as it deserves: <strong>convexity</strong>. Convex optimisation will not write you a sonnet. But when there is not much data, when someone needs to know <em>why</em>, when the answer is needed in a few milliseconds, or when every experiment costs real money, it is often the best tool you can pick up. And as we will see, it works very well alongside the big models.</p>
<section id="the-marble-and-the-bowl" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="the-marble-and-the-bowl">The marble and the bowl</h2>
<p>Picture a marble dropped into a smooth bowl. Wherever you let go of it, it rolls down and settles at the same spot: the bottom. There is only one bottom, and the marble cannot miss it.</p>
<p>Now picture dropping the same marble onto a mountain range in thick fog. It rolls downhill, but “downhill” might lead into a small valley halfway up a mountain. The marble stops there. It has no way of knowing that a much deeper valley lies two ridges over.</p>
<p>These are the two kinds of problems this essay is about.</p>
<ul>
<li>A <strong>convex</strong> problem is a bowl. Any way downhill leads to the best answer, and once you are at the bottom you can <em>prove</em> that you are there.</li>
<li>A <strong>non-convex</strong> problem is a mountain range. You can usually find a good valley, but you can rarely be sure it is the best one.</li>
</ul>
<p>Training a neural network is a hike through a mountain range with billions of directions instead of two. It works astonishingly well, which is one of the pleasant surprises of the last fifteen years. But it needs huge amounts of data and computing power, and run it twice from different starting points and you will usually land in two different valleys.</p>
<p>Real problems also come with rules. A budget cannot be overspent. A battery cannot hold more charge than it has room for. A robot arm cannot push harder than its motor allows. You can think of a rule as a fence that the marble is not allowed to cross. As long as the fenced-in area has no dents (if you can stand at any two allowed points and walk in a straight line between them without leaving the area), the problem is still a bowl. There is still exactly one lowest point inside the fence<sup>1</sup>.</p>
<div class="no-row-height column-margin column-container"><div id="fn1"><p><sup>1</sup>&nbsp;Strictly speaking, one lowest <em>value</em>. A bowl with a flat bottom has many equally good lowest points, and they all count as the answer.</p></div></div><div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center collapsed" data-bs-toggle="collapse" data-bs-target=".callout-1-contents" aria-controls="callout-1" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Jargon jar: words you will meet below
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-1" class="callout-1-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<ul>
<li><strong>Objective</strong>: the thing you want to make as small as possible (cost, error, wasted energy). It is the height of the bowl.</li>
<li><strong>Constraint</strong>: a rule the answer must obey. One of the fences.</li>
<li><strong>Linear program (LP)</strong> and <strong>quadratic program (QP)</strong>: two of the simplest and most common kinds of bowl.</li>
<li><strong>Dual</strong> or <strong>shadow price</strong>: how much better the answer would get if a particular fence were moved back a little. More on these soon. They are the hero of the interpretability section.</li>
</ul>
</div>
</div>
</div>
<section id="two-players-and-a-saddle" class="level3">
<h3 class="anchored" data-anchor-id="two-players-and-a-saddle">Two players and a saddle</h3>
<p>How do you actually find the bottom of a fenced-in bowl? One elegant method turns the problem into a game between two players.</p>
<p>The first player, the <em>builder</em>, tries to make the objective as small as possible. The second player, the <em>referee</em>, watches the fences. Every time the builder strays past one, the referee raises the fine for that fence. Every time the builder stays comfortably inside, the referee eases off. The two take turns, and the game ends when neither can do any better.</p>
<p>The point where they settle is called a <strong>saddle point</strong>. It is named after a horse’s saddle, which is a low point if you look from front to back and a high point if you look from side to side. The builder ends up with the best allowed answer. The referee ends up with a fine for every fence: the shadow prices. Keep those fines in mind. They turn out to be very useful.</p>
<p>A <strong>general</strong> convex solver lets you describe your bowl and your fences as ordinary code and leaves everything else to the computer. You do not derive the slopes by hand, because automatic differentiation (the same trick that trains neural networks) works them out. That matters more than it sounds, and we will come back to it at the end.</p>
</section>
</section>
<section id="when-data-is-scarce" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="when-data-is-scarce">1. When data is scarce</h2>
<p>Foundation models learn from trillions of words. Plenty of important problems come with a spreadsheet of two hundred rows.</p>
<p>With two hundred rows, a model with a billion adjustable knobs has nothing to anchor it. It will memorise the spreadsheet and learn very little. A convex model has far fewer knobs, and its bowl shape means those knobs settle in one sensible place rather than wandering off to fit the noise.</p>
<p>Here is a small experiment. Make up some data with fifty possible causes, of which only five really matter, add some noise, and keep just two hundred examples. A classic convex method called the <strong>lasso</strong> looks for an answer that fits the data <em>and</em> uses as few causes as possible. It reliably sets most of the fifty weights to exactly zero and points to the handful that matter<sup>2</sup>. It is like a detective who explains the crime with the fewest suspects.</p>
<div class="no-row-height column-margin column-container"><div id="fn2"><p><sup>2</sup>&nbsp;Fifty correlated features, five of them real, two hundred samples. The lasso is a bowl once each weight is split into a positive and a negative part, which leaves only “must be at least zero” fences.</p></div></div><p>There is a second, bigger advantage. When data is scarce, <em>knowledge</em> is precious, and convex problems let you write knowledge down directly as constraints instead of hoping the data will teach it:</p>
<ul>
<li><strong>“This should only ever go up.”</strong> Bigger houses cost more. Higher doses do not make a medicine <em>less</em> effective. One line of code that says “each value is at most the next one” turns a wobbly, noisy curve into a clean staircase that only climbs<sup>3</sup>.</li>
<li><strong>“Physics is not optional.”</strong> If you are tracking a moving vehicle, its position next second depends on its position and speed now. Write that down as an equality constraint and the model cannot propose a car that teleports.</li>
<li><strong>“Some of the data is rubbish.”</strong> Swap the usual squared error for a gentler penalty (the Huber function) that grows only in a straight line for large mistakes. Now a sensor that occasionally shouts nonsense cannot drag the answer away. Even when a fifth of the readings are wild outliers, the estimated path stays on the road<sup>4</sup>.</li>
</ul>
<div class="no-row-height column-margin column-container"><div id="fn3"><p><sup>3</sup>&nbsp;This is isotonic regression, and its whole definition fits in a single <code>lambda</code>. See the code sketch near the end.</p></div><div id="fn4"><p><sup>4</sup>&nbsp;A robust Kalman smoother over a thousand time steps. The ordinary least-squares smoother gets pulled off course by the outliers, while the Huber version calmly ignores them.</p></div></div><p>Each of those rules is worth a great many training examples, and none of them needs a GPU cluster to learn.</p>
</section>
<section id="when-you-need-to-know-why" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="when-you-need-to-know-why">2. When you need to know <em>why</em></h2>
<p>Ask a large language model why it gave an answer and it will give you a fluent, plausible explanation. Whether that explanation describes what really happened inside the model is a hard open research question.</p>
<p>Convex models tend to explain themselves in three more concrete ways.</p>
<p><strong>The weights mean something.</strong> A convex model often has a small number of weights attached to things a person can name. A search ranker might use seventeen features such as “how many of the query’s words appear in the title” or “how long is this document”. Each one gets one weight, and you can read the list like a recipe. The lasso goes further and hands you a short list of the causes it thinks matter, with everything else set to zero.</p>
<p><strong>The referee’s fines are explanations.</strong> Remember the saddle game? The fine on each fence tells you how much the objective would improve if that fence moved back a little. Economists call this a <em>shadow price</em>, and it answers questions people actually ask:</p>
<ul>
<li>A shipping company limits its carbon emissions. The fine on the emissions fence is, in effect, the <em>carbon price</em> the company is paying, in dollars per tonne, even though nobody wrote that price down<sup>5</sup>.</li>
<li>An investor caps how risky a portfolio may be. The fine on the risk fence says how much extra return one more unit of risk would buy<sup>6</sup>.</li>
<li>A lender requires that its loan-approval model treat two groups of applicants fairly. The fine on the fairness fence is the <strong>price of fairness</strong>: how much the model’s fit to past decisions suffers for each step towards equal treatment.</li>
</ul>
<div class="no-row-height column-margin column-container"><div id="fn5"><p><sup>5</sup>&nbsp;The fines on the demand constraints give each market’s marginal cost of supply in the same way.</p></div><div id="fn6"><p><sup>6</sup>&nbsp;Sweep the risk limit and that fine is the slope of the efficient frontier, read directly off the solver rather than estimated.</p></div></div><p>That last example has a twist worth telling. Suppose the lender’s past decisions were unfair, and it tries to fix this by simply hiding which group each applicant belongs to. It does not work. Other details, like a postcode, quietly carry the same information, and the model rediscovers the old bias. A constraint is more honest. It says exactly what “fair” should mean, enforces it, and the shadow price states plainly what that choice costs. In that made-up world the fair model even agrees <em>better</em> with what applicants deserved, because the bias was in the old labels, not in the people.</p>
<p><strong>The solver can prove it is finished.</strong> For a convex problem, the builder’s answer and the referee’s fines together give a number called the <em>duality gap</em>. When the gap is small, the answer is provably close to the best possible one, no matter how the solver got there. It is a homework answer that comes with its own marking scheme. Few other corners of machine learning can offer that.</p>
</section>
<section id="when-the-answer-is-needed-now" class="level2">
<h2 class="anchored" data-anchor-id="when-the-answer-is-needed-now">3. When the answer is needed <em>now</em></h2>
<p>A large language model performs billions of calculations for every word it writes. For a chatbot that is fine, since people read slowly. For a robot arm that must react within a few milliseconds, or a power grid balancing itself second by second, it is far too slow.</p>
<p>Convex problems used for control are often tiny. Picture a chain of four blocks joined by springs, with a wall at each end and three small motors pushing neighbouring blocks apart. Every half a second a controller looks at where the blocks are, plans the next few seconds of motor pushes, applies the first one, then plans again. This is called <strong>model predictive control</strong>. Each plan is a quadratic program with about a hundred variables. A good solver finishes it in a fraction of a millisecond.</p>
<div id="fig-fleet" class="quarto-float quarto-figure quarto-figure-center anchored" alt="An animation of four spring-and-block systems being steadied by their controllers, beside a cloud of dots, one per system, drawing in towards the centre.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-fleet-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://brendanvr.github.io/jaddle-blog/posts/convex-in-the-age-of-foundation-models/mpc_fleet.gif" class="img-fluid figure-img" style="width:100.0%" alt="An animation of four spring-and-block systems being steadied by their controllers, beside a cloud of dots, one per system, drawing in towards the centre.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-fleet-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: A thousand spring-and-block systems, each with its own convex controller, solved together in one batch every half a second.
</figcaption>
</figure>
</div>
<p>Now make it a fleet of a thousand such systems: a thousand delivery drones, say, or a thousand batteries on a grid. Their problems all have the same shape and differ only in their current state. A batched convex solver on a GPU handles all thousand at once, in lockstep, and each plan starts from the previous half-second’s plan shifted forward, which is an excellent first guess. On one laptop GPU this works out to about one fortieth of the time per problem of a well-tuned CPU solver working through the fleet one problem at a time.</p>
<p>There is an honest caveat. For a <em>single</em> system on its own, the CPU solver wins, because a lone tiny problem leaves a GPU sitting idle. A GPU is a bus, not a sports car. It shines when there are lots of passengers going the same way.</p>
<p>Two more things make convex solvers a comfortable fit for deadlines:</p>
<ul>
<li><strong>They keep their promises along the way.</strong> A solver can be built so that every intermediate answer already respects the hard limits, such as “the motor can’t push harder than this”. If the deadline arrives before the solver has fully converged, the plan you take is slightly less than perfect, but it is not illegal.</li>
<li><strong>The trained model is often tiny.</strong> A linear search ranker scores a document with seventeen multiplications and some additions. That is cheap enough to run on every candidate document without anyone noticing.</li>
</ul>
</section>
<section id="when-every-experiment-costs-money" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="when-every-experiment-costs-money">4. When every experiment costs money</h2>
<p>Training a large neural network involves dozens of settings: the learning rate, how it changes over time, the batch size, the random seed, and many more. Getting them wrong can quietly ruin a training run that cost a great deal to do. Teams search over these settings by training many slightly different models, and each one is a full, expensive run.</p>
<p>Convex problems remove most of that search, for a simple reason. <strong>The answer depends only on the bowl, not on how you rolled the marble.</strong> Different starting points, different step sizes and different random seeds all lead to the same bottom. The only settings left are the ones that <em>mean</em> something: how strongly to favour simple models, how fair the lender must be, how much risk the investor will accept.</p>
<p>Better still, those meaningful settings are cheap to explore:</p>
<ul>
<li><strong>Solve many bowls at once.</strong> Problems with the same shape can be stacked and solved together. A lasso can be solved for a whole range of simplicity settings in one batched call. The lender’s fairness trade-off can be traced across forty thresholds in a single solve, giving a curve of accuracy against fairness and the price of fairness at every point along it. A search ranker’s regularisation path of eleven problems takes about three seconds on a laptop GPU.</li>
<li><strong>Start from the last answer.</strong> Neighbouring settings have neighbouring answers, so each solve can warm-start from the one before. Tracing out an investor’s whole menu of risk-versus-return choices becomes a quick walk rather than a series of cold starts.</li>
<li><strong>Follow the slope instead of searching a grid.</strong> Because the bottom of the bowl moves smoothly as you change a setting, you can work out <em>which way</em> to change it, using the same calculus trick that trains neural networks. Give each of the lasso’s fifty features its own penalty, and a grid search over fifty settings becomes hopeless, since even two values each would mean more than a thousand trillion combinations. Gradient descent tunes all fifty at roughly the cost of tuning one, and brings the error on fresh test data down to the level of the noise itself, which is as good as any model can do.</li>
</ul>
<p>The same trick works one level up. To decide how big a home battery to buy, you need to know how cleverly it would be run, and running it well for a day is itself a small linear program. Solve a month of such days in one batch, ask how the total cost changes as the battery grows, and roll downhill on the battery’s size<sup>7</sup>. The convex solver becomes a building block inside a larger optimisation.</p>
<div class="no-row-height column-margin column-container"><div id="fn7"><p><sup>7</sup>&nbsp;Thirty-two scenario days, each with its own prices, demand and sunshine, and forty steps of gradient descent on the battery’s size. The sizing problem itself is not a bowl, because big batteries are cheaper per kilowatt-hour, which is exactly why it gets split up this way.</p></div></div></section>
<section id="and-yet-it-performs-surprisingly-well" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="and-yet-it-performs-surprisingly-well">And yet it performs surprisingly well</h2>
<p>All of this would be a nice story if convex methods were safe, cheap and a little bit rubbish. They are not.</p>
<p>Take search, which matters to anyone building a chatbot that looks things up before it answers. One common test asks a system to find the scientific papers that support or refute a claim, and scores it out of 1 on how good its top ten results are. Here is how some approaches fare on that test:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Approach</th>
<th>Score (out of 1)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Keyword matching (BM25)</td>
<td>0.681</td>
</tr>
<tr class="even">
<td>A small neural embedding model</td>
<td>0.720</td>
</tr>
<tr class="odd">
<td>Keywords and embeddings blended together</td>
<td>0.731</td>
</tr>
<tr class="even">
<td><strong>Convex reranker using all of the above as features</strong></td>
<td><strong>0.770</strong></td>
</tr>
</tbody>
</table>
<p>The winner is a ranking support vector machine: a convex quadratic program with seventeen weights. It adds about four points over the blended system, and a statistical check puts that gain between two and six points<sup>8</sup>. Every training run gives the same answer, the solver certifies it, and the seventeen weights can be printed on a postcard.</p>
<div class="no-row-height column-margin column-container"><div id="fn8"><p><sup>8</sup>&nbsp;Test nDCG@10 on SciFact, with a 95% bootstrap interval of +0.021 to +0.060 over reciprocal rank fusion.</p></div></div><p>Look closely at that table, though, because it holds the most important lesson in this essay. One of the reranker’s seventeen features <em>is</em> the neural embedding model. Without the embedding features, a keyword-only version of the same reranker scores 0.724. The convex model did not beat the neural network. It <em>used</em> the neural network, and did better than either could alone.</p>
</section>
<section id="friends-not-rivals" class="level2">
<h2 class="anchored" data-anchor-id="friends-not-rivals">Friends, not rivals</h2>
<p>That pattern repeats, and it suggests a sensible division of labour.</p>
<ul>
<li><strong>Foundation models make features, convex models make decisions.</strong> A neural network turns messy text, images or sound into useful numbers. A convex model combines those numbers under rules you can state and audit.</li>
<li><strong>Convex layers act as guardrails.</strong> A neural network proposes, and a convex problem projects that proposal onto the set of things that are allowed: within budget, within physical limits, within the fairness rules.</li>
<li><strong>Language models can write the bowl.</strong> Describing a problem in a modelling language is something language models are increasingly good at. The convex solver then supplies what the language model cannot: a certified optimum and a set of shadow prices that explain it. The language model writes the essay and the solver checks the arithmetic.</li>
</ul>
<p>There are also places where convexity is simply the wrong tool. Recognising a cat in a photo, translating a poem, holding a conversation: nobody knows how to write these down as bowls, and the mountain-range methods are the right answer. The trick is to notice when part of your problem <em>is</em> a bowl, and to treat that part accordingly.</p>
</section>
<section id="why-general-matters" class="level2">
<h2 class="anchored" data-anchor-id="why-general-matters">Why <em>general</em> matters</h2>
<p>For decades, convex optimisation came in specialised boxes: one solver for linear programs, another for quadratic programs, a third for this particular statistics problem. Each had its own input format and its own quirks. Using one meant first translating your problem into the box’s language, which was often the hardest part.</p>
<p>A general solver turns this around. You write the objective and the rules as ordinary code, and the solver works out the rest. Here is the whole definition of a monotone curve fit:</p>
<div class="code-copy-outer-scaffold"><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 class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> jaddle.jaddle_convex <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> jc</span>
<span id="cb1-3"></span>
<span id="cb1-4">y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.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>, <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> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># data to fit</span></span>
<span id="cb1-5"></span>
<span id="cb1-6">cp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jc.JaddleCP(</span>
<span id="cb1-7">    num_variables<span 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>
<span id="cb1-8">    objective<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: jnp.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>((p <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> <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;"># stay close to the data</span></span>
<span id="cb1-9">    constraints_eq<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: jnp.zeros(<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;"># no equality rules</span></span>
<span id="cb1-10">    constraints_ineq<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: p[:<span 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> p[<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;"># each value &lt;= the next</span></span>
<span id="cb1-11">    lower_bounds<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span>jnp.ones(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>),</span>
<span id="cb1-12">    upper_bounds<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>jnp.ones(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>),</span>
<span id="cb1-13">)</span>
<span id="cb1-14"></span>
<span id="cb1-15">result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jc.solve(cp)</span></code></pre></div></div>
<p>The same machinery then handles a search ranker, a fair lender, a thousand-strong fleet of controllers, a log-optimal investment portfolio or a noisy-channel capacity calculation, on a laptop CPU or a GPU, without a new solver for each. Because it is built on the same tools as modern deep learning, it can be batched, differentiated and dropped into a larger model like any other layer.</p>
<p>When the cost of trying a convex formulation falls to a few lines of code, it stops being the thing you reach for once everything else has failed and becomes the first thing you try. That is the real argument for a general solver. Convexity was never about the mathematics being pretty (though it is). It is about making the guarantees, explanations, speed and cheap experiments easy enough to use every day.</p>
</section>
<section id="in-short" class="level2">
<h2 class="anchored" data-anchor-id="in-short">In short</h2>
<p>Foundation models are superb mountaineers, and the world is full of mountains. But it is also full of bowls: budgets, schedules, controllers, rankers, fair classifiers, portfolios. When you meet one, you are in luck. You can find the very bottom, prove that you have found it, explain why it is there, and do it again for a thousand slightly different bowls before lunch.</p>
<p>So the next time a problem lands on your desk, before reaching for the biggest model you can find, it is worth asking a small question first:</p>
<p><em>Is this a bowl?</em></p>


</section>


 ]]></description>
  <category>optimisation</category>
  <category>convexity</category>
  <category>machine learning</category>
  <guid>https://brendanvr.github.io/jaddle-blog/posts/convex-in-the-age-of-foundation-models/convex-in-the-age-of-foundation-models.html</guid>
  <pubDate>Sat, 24 Oct 2026 16:00:00 GMT</pubDate>
  <media:content url="https://brendanvr.github.io/jaddle-blog/posts/convex-in-the-age-of-foundation-models/convex-in-the-age-of-foundation-models-logo.svg" medium="image" type="image/svg+xml"/>
</item>
<item>
  <title>Jaddle on the hard MIPLIB LP benchmark: 382 of 383</title>
  <dc:creator>Brendan van Rooyen</dc:creator>
  <link>https://brendanvr.github.io/jaddle-blog/posts/miplib-benchmark/miplib_benchmark.html</link>
  <description><![CDATA[ 





<p>With default settings, Jaddle certified 382 of the 383 LP relaxations in the PDLP papers’ hard MIPLIB 2017 set, to a relative tolerance of 1e-4, on a single laptop GPU. The shifted geometric mean solve time was 6.9 seconds, and half the instances finished in under 3.2 seconds.</p>
<p>Jaddle is a saddle-point solver written entirely in JAX, so it runs unchanged on CPU, GPU and TPU. Its algorithm is explained in the <a href="../how-jaddle-works/">How Jaddle Works</a> post. First-order LP solvers are fast per iteration but have a reputation for being fragile. Some instances stall for thousands of iterations, and the usual fix is per-instance tuning. This article shows that a fixed configuration can clear almost the whole benchmark.</p>
<p>It also looks at the one instance that did not converge, hgms62. It was within a factor of about four of the tolerance when the clock ran out. Changing one setting from its default gets it over the line.</p>
<section id="the-benchmark-set" class="level2">
<h2 class="anchored" data-anchor-id="the-benchmark-set">The benchmark set</h2>
<p>The test set is the 383 MIPLIB 2017 instances used to evaluate PDLP and cuPDLP, taken from the <code>mip_relaxations_instance_list</code> in FirstOrderLp.jl. Each instance has between 100,000 and 10 million nonzeros. Integrality is dropped, so every instance is solved as a plain LP.</p>
<p>The set is hard for first-order methods for reasons that have little to do with size:</p>
<ul>
<li><strong>Wide coefficient ranges.</strong> Many instances mix coefficients of 1 with big-M terms of 1e6 or more. Diagonal scaling cannot fully remove this, and it slows Jaddle down a lot.</li>
<li><strong>Hidden objectives.</strong> Some models put the objective in a dense equality row, such as <code>min z</code> subject to <code>z = Σ aⱼxⱼ</code>. The real cost structure is then hidden in the constraints, and the primal iterates drift off feasibility.</li>
<li><strong>Degeneracy and long tails.</strong> Many relaxations have large optimal faces or near-parallel constraints. Iterates then circle the optimum for a long time before the duality gap closes.</li>
</ul>
<p>The instances range from 150 to 1.45 million columns and from 36 to 2.95 million rows. One configuration has to handle all of them.</p>
</section>
<section id="experiment-setup" class="level2">
<h2 class="anchored" data-anchor-id="experiment-setup">Experiment setup</h2>
<p>Every instance ran through the same pipeline with the same solver settings. Nothing was tuned per instance.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Setting</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Hardware</td>
<td>One NVIDIA GeForce RTX 5070 Ti Laptop GPU</td>
</tr>
<tr class="even">
<td>Precision</td>
<td>float64</td>
</tr>
<tr class="odd">
<td>Solver settings</td>
<td><code>jl.solve</code> defaults: alternating update, adaptive step size, adaptive restarts, adaptive diagonal scaling with <code>diag_clamp=10</code></td>
</tr>
<tr class="even">
<td>Presolve</td>
<td>HiGHS presolve, then Jaddle’s defined-variable elimination</td>
</tr>
<tr class="odd">
<td>Tolerance</td>
<td>1e-4, relative, on primal feasibility, dual feasibility and duality gap</td>
</tr>
<tr class="even">
<td>Residual norm</td>
<td>l2</td>
</tr>
<tr class="odd">
<td>Time limit</td>
<td>3,600 seconds per instance</td>
</tr>
<tr class="even">
<td>Harness</td>
<td><code>benchmarks/benchmark.py --tol 1e-4 --max-seconds 3600</code></td>
</tr>
</tbody>
</table>
<p>An instance counts as converged when Jaddle returns a full optimality certificate on the presolved LP. All three residuals must be below the tolerance. Jaddle does not stop on a heuristic such as objective stalling.</p>
<p>The headline time is the iteration loop, including the one-off XLA compile on the first epoch. Wall time adds presolve, scaling, setup and postsolve. Unsolved instances count at the full 3,600 seconds in every average, as in the PDLP papers.</p>
<section id="why-a-tolerance-of-1e-4" class="level3">
<h3 class="anchored" data-anchor-id="why-a-tolerance-of-1e-4">Why a tolerance of 1e-4</h3>
<p>A relative tolerance of 1e-4 is the “quick and ready” setting. It puts the objective within about 0.01% of optimal, with constraint violations of the same relative size. For many uses that is the answer:</p>
<ul>
<li><strong>Planning and what-if runs</strong>, where the input data is far less accurate than 1e-4.</li>
<li><strong>MIP root relaxations and bounds</strong>, where a near-optimal dual bound is what matters.</li>
<li><strong>Warm starts and crossover.</strong> A 1e-4 point is close enough to the optimal face that a simplex repair is cheap. On mzzv11, crossover from a 1e-4 point took 125 to 6,077 pivots, against 47,774 for HiGHS’s simplex from a cold start.</li>
</ul>
<p>First-order methods also slow down as they approach the optimum, so each extra digit costs more than the last. Stopping at 1e-4 keeps the solver where it is fastest. The PDLP papers use 1e-4 as their moderate-accuracy level, which makes the results comparable.</p>
<p>When more accuracy is needed, Jaddle provides tools that start from a 1e-4 solution: feasibility polishing (<code>jl.solve_with_polishing</code>), iterative refinement (<code>jl.solve_with_refinement</code>) and crossover to an exact vertex (<code>jl.crossover</code>).</p>
</section>
</section>
<section id="presolve-highs-plus-a-jaddle-specific-reduction" class="level2">
<h2 class="anchored" data-anchor-id="presolve-highs-plus-a-jaddle-specific-reduction">Presolve: HiGHS plus a Jaddle-specific reduction</h2>
<p>Each instance goes through <code>jl.solve_with_presolve</code> with the defined-variable elimination switched on. It runs five steps:</p>
<ol type="1">
<li><strong>HiGHS presolve</strong> removes redundant rows and columns, fixes variables and tightens bounds.</li>
<li><strong>Defined-variable elimination</strong>, Jaddle’s own reduction, removes dense defining rows that HiGHS keeps.</li>
<li><strong>Jaddle</strong> solves the reduced LP.</li>
<li><strong>Elimination postsolve</strong> recovers the eliminated variables and the duals of their rows.</li>
<li><strong>HiGHS postsolve</strong> maps the primal and dual solution back to the original model.</li>
</ol>
<p>Presolve is cheap next to the solve. The median presolve time was 0.28 seconds. HiGHS kept a median of 95% of the columns. Presolve also solved one instance outright: neos-787933, with 236,376 columns, which counts as one of the 382.</p>
<section id="why-jaddle-needs-its-own-presolve" class="level3">
<h3 class="anchored" data-anchor-id="why-jaddle-needs-its-own-presolve">Why Jaddle needs its own presolve</h3>
<p>Standard presolvers are built for simplex and interior-point methods, which factorise matrices. For those methods, fill-in is expensive and a dense row is cheap. Jaddle has the opposite trade-off. It only does matrix-vector products, so extra nonzeros cost little. But some row structures damage the conditioning that decides how fast it converges.</p>
<p>The worst of these is a <strong>defined variable</strong>: a row <code>a·z + r·x = b</code> that defines <code>z</code> as a combination of other variables. In gmut-* and leo1, <code>z</code> is a zero-cost aggregate whose coefficients mirror the objective. In proteindesign*, <code>z</code> is the objective itself: <code>min z</code> subject to <code>z = Σ aⱼxⱼ</code>. In both cases, the dense row hides the objective structure from the scaling, and the primal iterates drift off feasibility.</p>
<p>HiGHS and SCIP leave these rows alone for three reasons. <code>z</code> is not a column singleton, because inequality rows use it too. It is not free, so substituting it needs an explicit bound row. And substituting a dense row breaks their fill-in limits.</p>
</section>
<section id="what-the-elimination-does" class="level3">
<h3 class="anchored" data-anchor-id="what-the-elimination-does">What the elimination does</h3>
<p><code>jaddle.presolve.eliminate_defined_variables</code> substitutes <code>z = E·x + const</code> throughout the model:</p>
<ul>
<li>The cost of <code>z</code> moves onto <code>x</code>, and any constant goes into the objective offset.</li>
<li>Inequality rows that use <code>z</code> get the substitution added to them.</li>
<li>Bounds on <code>z</code> become rows on <code>x</code>, unless the bounds on <code>x</code> already imply them.</li>
</ul>
<p>The rules are conservative. A zero-cost aggregate row needs at least 100 nonzeros to qualify. An objective row qualifies from 2 nonzeros, because a short objective row hides the objective just as well. <code>z</code> may appear in at most four inequality rows. Total fill-in is capped at the original nonzero count. Later passes follow objective chains only, pushing the cost down onto the decision variables. On radiationm40-10-02, for example, <code>min z, z = 1601 N + T</code> is followed by rows that define <code>N</code> and <code>T</code>.</p>
<p>The postsolve recovers both the eliminated primal values and the duals of the removed rows. The mapped primal-dual pair is exactly as feasible and optimal as the reduced one.</p>
</section>
</section>
<section id="results" class="level2">
<h2 class="anchored" data-anchor-id="results">Results</h2>
<p>Jaddle certified 382 of 383 instances, and most of them in seconds. Half finished in under 3.2 seconds, 90% in under 25 seconds, and only four needed more than five minutes.</p>
<div id="fig-certified" class="quarto-float quarto-figure quarto-figure-center anchored" alt="Step curve of instances certified against solve time on a log axis from 0.5 seconds to 1 hour. The curve rises steeply between 1 and 10 seconds, reaches 365 by 1 minute and 382 by the 1 hour limit, one short of the 383 instances in the set.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-certified-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://brendanvr.github.io/jaddle-blog/posts/miplib-benchmark/figures/certified_vs_time.png" class="img-fluid figure-img" alt="Step curve of instances certified against solve time on a log axis from 0.5 seconds to 1 hour. The curve rises steeply between 1 and 10 seconds, reaches 365 by 1 minute and 382 by the 1 hour limit, one short of the 383 instances in the set.">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-certified-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: 365 of 383 instances certified within a minute, 382 within the hour. Instances certified to 1e-4 against solve time (log scale), default settings, one laptop GPU. Times are the iteration loop, including XLA compile; neos-787933, solved by presolve, is counted at the start.
</figcaption>
</figure>
</div>
<table class="caption-top table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Outcome</th>
<th>Instances</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Certified optimal on the presolved LP</td>
<td><strong>382 / 383</strong> (381 by Jaddle, 1 by presolve)</td>
</tr>
<tr class="even">
<td>Time limit reached</td>
<td>1 (hgms62)</td>
</tr>
<tr class="odd">
<td>Errors or numerical failures</td>
<td>0</td>
</tr>
</tbody>
</table>
<p>The table below gives shifted geometric means with a 10-second shift (SGM10), the PDLP papers’ summary statistic. hgms62 counts at 3,600 seconds.</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>Time measured</th>
<th>SGM10</th>
<th>Median, certified instances</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Iteration loop, including first-epoch XLA compile</td>
<td>6.86 s</td>
<td>3.19 s</td>
</tr>
<tr class="even">
<td>Iteration loop, compile amortised out</td>
<td>4.72 s</td>
<td>1.29 s</td>
</tr>
<tr class="odd">
<td>Wall clock, including presolve, scaling, setup and postsolve</td>
<td>9.56 s</td>
<td>5.11 s</td>
</tr>
</tbody>
</table>
<p>The median instance needed only 8 epochs. The long tail is short. Apart from hgms62, the slowest instances were snp-02-004-104 at 761 seconds, hgms30 at 1,190 seconds and fhnw-binschedule1 at 1,550 seconds. All three finished well inside the limit.</p>
<p>The gap between the first two rows is compile time. Every new problem shape triggers an XLA compile, which costs one to two seconds. On small instances it is often most of the solve. Jaddle caches compiled programs on disk, so repeat solves of a same-shaped model skip it.</p>
</section>
<section id="case-study-hgms62-the-one-that-got-away" class="level2">
<h2 class="anchored" data-anchor-id="case-study-hgms62-the-one-that-got-away">Case study: hgms62, the one that got away</h2>
<p>hgms62 is the only instance Jaddle did not certify with default settings. It ran for the full hour, about 2.9 million iterations, and stopped close to the tolerance but not inside it.</p>
<section id="the-instance" class="level3">
<h3 class="anchored" data-anchor-id="the-instance">The instance</h3>
<p>hgms62 has 48,597 columns and 582,237 rows, or 37,235 by 569,830 after presolve. Size is not the problem. It ranks 46th by file size, and rwth-timetable, ten times larger, certified in 10 seconds. Its half-size sibling, hgms30, is telling. It was the second-slowest certified instance, taking 3,869 epochs and 1,190 seconds. The hgms family is hard for Jaddle because of its structure, and hgms62 is about twice the size of hgms30.</p>
<p>The family has a history in Jaddle’s development. On hgms30, an earlier version locked into a single restart cycle for more than 1,250 epochs, with the objective stuck 7% off optimal. That led to the artificial restart rule in the current solver, which forces a restart when a cycle runs too long without progress.</p>
</section>
<section id="how-close-it-got" class="level3">
<h3 class="anchored" data-anchor-id="how-close-it-got">How close it got</h3>
<p>When the clock ran out, the postsolved point had these residuals on the original LP:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Residual</th>
<th>Value at 3,600 s</th>
<th>Tolerance</th>
<th>Ratio to tolerance</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Primal feasibility</td>
<td>9.3e-5</td>
<td>1e-4</td>
<td>0.9 (met)</td>
</tr>
<tr class="even">
<td>Dual feasibility</td>
<td>2.1e-4</td>
<td>1e-4</td>
<td>2.1</td>
</tr>
<tr class="odd">
<td>Duality gap</td>
<td>3.9e-4</td>
<td>1e-4</td>
<td>3.9</td>
</tr>
</tbody>
</table>
<p>The primal was feasible to tolerance. The dual feasibility and the gap were the problem: still converging, but slowly. A 600-second run of an earlier build had left them at 1.7e-2 and 8.5e-2, so the solver was making steady progress, not stalling. It just needed more time than the limit allowed.</p>
</section>
<section id="the-fix-widen-the-diagonal-clamp" class="level3">
<h3 class="anchored" data-anchor-id="the-fix-widen-the-diagonal-clamp">The fix: widen the diagonal clamp</h3>
<p>The default that matters here is <code>diag_clamp</code>. Jaddle starts from Ruiz and Pock–Chambolle scaling, then adapts a per-coordinate diagonal metric at each restart. Coordinates that drift steadily get larger steps, and coordinates that oscillate get smaller ones. This adaptive scaling is on by default, because it cut the shifted geometric mean solve time on this benchmark by about 18%.</p>
<p><code>diag_clamp</code> limits how far that adaptation can move each coordinate from the baseline scaling. The default of 10 keeps every scale factor in [1/10, 10]. That is a safe bound across the whole benchmark, but on hgms62 it is too tight. The likely reason is that some coordinates need more rescaling than a factor of 10 allows, so the clamp holds them back.</p>
<p>Raising <code>diag_clamp</code> greatly lets the adaptive metric correct the scaling fully, and hgms62 then converges. Nothing else changes: the same presolve, tolerance and time limit.</p>
<div class="code-copy-outer-scaffold"><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> jaddle.jaddle_linear <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> jl</span>
<span id="cb1-2"></span>
<span id="cb1-3">result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jl.solve_with_presolve(</span>
<span id="cb1-4">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"hgms62.mps"</span>,</span>
<span id="cb1-5">    eliminate_defined_vars<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;"># as in the benchmark</span></span>
<span id="cb1-6">    primal_feasibility_tolerance<span 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-4</span>,</span>
<span id="cb1-7">    dual_feasibility_tolerance<span 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-4</span>,</span>
<span id="cb1-8">    dual_gap_tolerance<span 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-4</span>,</span>
<span id="cb1-9">    diag_clamp<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e8</span>,  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># default 10.0</span></span>
<span id="cb1-10">)</span></code></pre></div></div>
<p>With this setting, hgms62 converges in 73.65 seconds.</p>
</section>
<section id="why-the-default-stays-at-10" class="level3">
<h3 class="anchored" data-anchor-id="why-the-default-stays-at-10">Why the default stays at 10</h3>
<p>A wider clamp is not a free win. The adaptive metric changes the geometry the solver works in, and a loose bound gives it more room to go wrong on other instances. Jaddle’s primal weight shows the same risk: with a wide k_scale clamp and per-epoch updates it ran away on mzzv11, reaching 1e8 with the gap stuck at 0.94. The default is set for the whole benchmark, not for its hardest member.</p>
<p>This is the pattern for a first-order solver: one robust default that handles 382 of 383 instances, and a small set of documented settings for the rare instance that needs more. When a solve stalls, <code>diag_clamp</code> is a good first setting to try. Turning adaptive scaling off with <code>diag_adapt=False</code> is another.</p>
</section>
</section>
<section id="caveats-and-reproducing-the-run" class="level2">
<h2 class="anchored" data-anchor-id="caveats-and-reproducing-the-run">Caveats and reproducing the run</h2>
<p>Three points matter when reading these numbers.</p>
<ul>
<li><strong>The certificate is on the presolved LP.</strong> After HiGHS postsolve, 297 of the 383 points also pass the 1e-4 certificate on the original LP. Postsolve can enlarge a first-order solution’s small dual errors. Presolve can also rescale a problem a lot, which changes what a relative tolerance means. On leo1, presolve folds a row with 1e7 coefficients into the cost, so the objective is right but the original dual test fails. When the original certificate matters, polishing, iterative refinement or crossover can tighten the postsolved point.</li>
<li><strong>The time limit matters for the hardest few.</strong> With a 600-second limit, earlier runs certified 380 of 383. hgms30 and fhnw-binschedule1 need 20 to 26 minutes.</li>
<li><strong>GPU runs are not bit-for-bit repeatable.</strong> The order of scatter-add operations varies, so identical runs can differ by several epochs.</li>
</ul>
<p>The MPS files are not shipped with Jaddle. Download the 383 instances from the <a href="https://miplib.zib.de/">MIPLIB 2017 site</a> into <code>data/</code>, then run from the repository root:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python</span> benchmarks/benchmark.py <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--tol</span> 1e-4 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--max-seconds</span> 3600 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--highs-solver</span> none</span></code></pre></div></div>
<p><code>--highs-solver none</code> skips the HiGHS reference solve, which can take longer than Jaddle’s. Add <code>--diag-clamp 1e8</code> to rerun hgms62 with the wider clamp. The harness and its options are documented in <a href="https://github.com/BrendanVR/Jaddle/blob/main/benchmarks/README.md"><code>benchmarks/README.md</code></a>.</p>


</section>

 ]]></description>
  <category>jaddle</category>
  <category>optimisation</category>
  <category>linear programming</category>
  <category>benchmarks</category>
  <guid>https://brendanvr.github.io/jaddle-blog/posts/miplib-benchmark/miplib_benchmark.html</guid>
  <pubDate>Thu, 22 Oct 2026 16:00:00 GMT</pubDate>
</item>
<item>
  <title>How Jaddle Works</title>
  <dc:creator>Brendan van Rooyen</dc:creator>
  <link>https://brendanvr.github.io/jaddle-blog/posts/how-jaddle-works/how_jaddle_works.html</link>
  <description><![CDATA[ 





<div class="callout callout-style-default callout-warning 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">
<span class="screen-reader-only">Warning</span>Warning: nerd content ahead!
</div>
</div>
<div class="callout-body-container callout-body">
<p>This article is a bit nerdy. There are equations, Greek letters and at least one joke about horse saddles. You don’t need to understand every symbol: the words around each equation say what it means in plain language. If an equation looks scary, read the sentence after it and keep going.</p>
<p>You have been warned. Be prepared!</p>
</div>
</div>
<section id="what-problem-are-we-solving" class="level2">
<h2 class="anchored" data-anchor-id="what-problem-are-we-solving">What problem are we solving?</h2>
<p>Jaddle is a program that solves <strong>optimisation problems</strong>. That means: pick some numbers so that something is as small (or as big) as possible, while following a set of rules.</p>
<p>Here is a tiny one. You are baking. Flour costs $2 a kilo, and your recipe needs <strong>at least 3 kilos</strong>. How much flour should you buy to spend as little as possible?</p>
<p>You already know the answer: buy exactly 3 kilos and spend $6. Easy.</p>
<p>Now imagine an airline planning thousands of flights, a power company deciding which power stations to switch on, or a delivery company routing hundreds of trucks. These problems have millions of numbers to choose and millions of rules to follow. Nobody can eyeball those. We need a recipe that a computer can follow, step by step, and that recipe is what this article is about.</p>
<p>Mathematicians write this kind of problem (a <strong>linear program</strong>) like this:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctext%7Bminimise%20%7D%20c%5E%5Ctop%20x%20%5Cquad%20%5Ctext%7Bsuch%20that%7D%20%5Cquad%20Ax%20%5Cle%20b,%20%5Cquad%20%5Cell%20%5Cle%20x%20%5Cle%20u%0A"></p>
<p>In words:</p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?x"> is the list of numbers we get to choose (how much of each thing to buy).</li>
<li><img src="https://latex.codecogs.com/png.latex?c"> is the list of prices, and <img src="https://latex.codecogs.com/png.latex?c%5E%5Ctop%20x%20=%20c_1%20x_1%20+%20c_2%20x_2%20+%20%5Cdots"> is the total cost.</li>
<li>Each row of <img src="https://latex.codecogs.com/png.latex?Ax%20%5Cle%20b"> is one rule (“you need at least this much of that”).</li>
<li><img src="https://latex.codecogs.com/png.latex?%5Cell%20%5Cle%20x%20%5Cle%20u"> are simple limits on each number, like “you can’t buy a negative amount of flour”.</li>
</ul>
<p>For the flour problem there is just one number, <img src="https://latex.codecogs.com/png.latex?x"> = kilos of flour. The cost is <img src="https://latex.codecogs.com/png.latex?2x">, the rule is <img src="https://latex.codecogs.com/png.latex?x%20%5Cge%203"> (which we can write as <img src="https://latex.codecogs.com/png.latex?3%20-%20x%20%5Cle%200">) and the limit is <img src="https://latex.codecogs.com/png.latex?x%20%5Cge%200">.</p>
<p>Jaddle solves these problems in five big ideas. Let’s go through them one at a time.</p>
</section>
<section id="turn-rules-into-fines-dualising-and-the-lagrangian" class="level2">
<h2 class="anchored" data-anchor-id="turn-rules-into-fines-dualising-and-the-lagrangian">1. Turn rules into fines (dualising and the Lagrangian)</h2>
<p>Rules are awkward for computers. They act like invisible walls, and walls are hard to deal with when there are millions of them.</p>
<p>So we play a trick: <strong>we replace every rule with a fine.</strong></p>
<p>Imagine a <strong>referee</strong> who watches the shopper. For each rule, the referee chooses a fine, <img src="https://latex.codecogs.com/png.latex?y">, to charge for every kilo you are short. If you buy <img src="https://latex.codecogs.com/png.latex?x"> kilos you are <img src="https://latex.codecogs.com/png.latex?3%20-%20x"> kilos short, so the fine is <img src="https://latex.codecogs.com/png.latex?y%20%5Ctimes%20(3%20-%20x)">. Your total bill becomes</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL(x,%20y)%20=%20%5Cunderbrace%7B2x%7D_%7B%5Ctext%7Bcost%20of%20flour%7D%7D%20+%20%5Cunderbrace%7By%5C,(3%20-%20x)%7D_%7B%5Ctext%7Bfine%20for%20being%20short%7D%7D%0A"></p>
<p>This bill has a famous name: the <strong>Lagrangian</strong>, after Joseph-Louis Lagrange, a mathematician from the 1700s. For any linear program it looks like this:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL(x,%20y)%20=%20c%5E%5Ctop%20x%20+%20y%5E%5Ctop%20(A%20x%20-%20b),%20%5Cqquad%20y%20%5Cge%200%0A"></p>
<p>There is one fine for each rule, and the list of fines <img src="https://latex.codecogs.com/png.latex?y"> is called the <strong>dual variables</strong>. (The shopper’s numbers <img src="https://latex.codecogs.com/png.latex?x"> are the <strong>primal variables</strong>.) Swapping rules for fines like this is called <strong>dualising the constraints</strong>. Fines can’t be negative, because a referee doesn’t pay you for breaking a rule. If you have <em>extra</em> flour, <img src="https://latex.codecogs.com/png.latex?3%20-%20x"> is negative, and the referee’s best move is to set the fine to zero. (Rules of the form “exactly equal to” also work. Their fines are allowed to be negative.)</p>
<section id="a-game-with-two-players" class="level3">
<h3 class="anchored" data-anchor-id="a-game-with-two-players">A game with two players</h3>
<p>Now we have a game:</p>
<ul>
<li>The <strong>shopper</strong> chooses <img src="https://latex.codecogs.com/png.latex?x"> and wants the bill <img src="https://latex.codecogs.com/png.latex?L"> to be <strong>as small as possible</strong>.</li>
<li>The <strong>referee</strong> chooses <img src="https://latex.codecogs.com/png.latex?y"> and wants the bill <img src="https://latex.codecogs.com/png.latex?L"> to be <strong>as big as possible</strong>.</li>
</ul>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmin_%7Bx%7D%20%5C;%20%5Cmax_%7By%20%5Cge%200%7D%20%5C;%20L(x,%20y)%0A"></p>
<p>Why does this game give the same answer as the original problem? If the shopper breaks the rule, the referee can make the fine enormous, so breaking the rule is a terrible idea. If the shopper follows the rule, the fine does nothing. The shopper’s best move is to follow the rules and spend as little as possible, which is exactly the original problem. The difference is that there are no walls any more, just a bill to push down and fines to push up.</p>
</section>
<section id="the-saddle-point" class="level3">
<h3 class="anchored" data-anchor-id="the-saddle-point">The saddle point</h3>
<p>For the flour problem the game ends at <img src="https://latex.codecogs.com/png.latex?x%20=%203"> and <img src="https://latex.codecogs.com/png.latex?y%20=%202">. Check it:</p>
<ul>
<li>If the referee sets <img src="https://latex.codecogs.com/png.latex?y%20=%202">, the bill is <img src="https://latex.codecogs.com/png.latex?2x%20+%202(3%20-%20x)%20=%206"> whatever <img src="https://latex.codecogs.com/png.latex?x"> is. The shopper can’t do any better by changing <img src="https://latex.codecogs.com/png.latex?x">.</li>
<li>If the shopper buys <img src="https://latex.codecogs.com/png.latex?x%20=%203">, the bill is <img src="https://latex.codecogs.com/png.latex?6%20+%20y%20%5Ctimes%200%20=%206"> whatever <img src="https://latex.codecogs.com/png.latex?y"> is. The referee can’t do any better by changing <img src="https://latex.codecogs.com/png.latex?y">.</li>
</ul>
<p>Neither player wants to move. This spot is called a <strong>saddle point</strong>, because the bill looks like a horse saddle. Along the horse’s back the saddle curves <em>up</em> on both sides, so the middle is the lowest point (what the shopper wants). Across the horse it curves <em>down</em> on both sides, so the middle is the highest point (what the referee wants). The middle of a saddle is the lowest point one way and the highest point the other way.</p>
<p>That is where the name comes from: <strong>Jaddle</strong> = <strong>J</strong>AX + s<strong>addle</strong>.</p>
<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">
<span class="screen-reader-only">Tip</span>Bonus: fines are prices
</div>
</div>
<div class="callout-body-container callout-body">
<p>The answer <img src="https://latex.codecogs.com/png.latex?y%20=%202"> means something. If your recipe needed one <em>more</em> kilo, your cost would go up by exactly $2. The referee’s fine tells you how much each rule costs you. On big problems this is very useful: it tells a company which of its rules are the expensive ones.</p>
</div>
</div>
</section>
</section>
<section id="take-turns-walking-downhill-and-uphill-alternating-gradient-descentascent" class="level2">
<h2 class="anchored" data-anchor-id="take-turns-walking-downhill-and-uphill-alternating-gradient-descentascent">2. Take turns walking downhill and uphill (alternating gradient descent–ascent)</h2>
<p>How do the shopper and the referee find the saddle point? They feel which way the ground slopes and take small steps.</p>
<p>The <strong>gradient</strong> is the slope: it says which direction is uphill and how steep it is. For the flour bill the slopes are</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20x%7D%20=%202%20-%20y,%20%5Cqquad%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20y%7D%20=%203%20-%20x%0A"></p>
<p>These say something sensible:</p>
<ul>
<li>If the fine <img src="https://latex.codecogs.com/png.latex?y"> is less than $2, flour costs more than the fine it saves you, so the shopper wants to buy <strong>less</strong>.</li>
<li>If <img src="https://latex.codecogs.com/png.latex?x"> is less than 3, the shopper is short of flour, so the referee wants to make the fine <strong>bigger</strong>.</li>
</ul>
<p>For any linear program the slopes are <img src="https://latex.codecogs.com/png.latex?c%20+%20A%5E%5Ctop%20y"> for the shopper and <img src="https://latex.codecogs.com/png.latex?Ax%20-%20b"> for the referee.</p>
<p>The shopper walks <strong>downhill</strong> (makes the bill smaller) and the referee walks <strong>uphill</strong> (makes the bill bigger). They take turns, and the referee always reacts to the shopper’s <em>newest</em> choice. That is why it is called <strong>alternating</strong>:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ax_%7B%5Ctext%7Bnew%7D%7D%20=%20%5Ctext%7Bclip%7D%5CBig(x%20-%20%5Ctau%5C,%5Cbig(c%20+%20A%5E%5Ctop%20y%5Cbig)%5CBig)%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ay_%7B%5Ctext%7Bnew%7D%7D%20=%20%5Cmax%5CBig(0,%5C;%20y%20+%20%5Csigma%5C,%5Cbig(A%20x_%7B%5Ctext%7Bnew%7D%7D%20-%20b%5Cbig)%5CBig)%0A"></p>
<p>Here <img src="https://latex.codecogs.com/png.latex?%5Ctau"> (tau) and <img src="https://latex.codecogs.com/png.latex?%5Csigma"> (sigma) are <strong>step sizes</strong>: how far each player moves per turn. “clip” pushes <img src="https://latex.codecogs.com/png.latex?x"> back inside its limits, and <img src="https://latex.codecogs.com/png.latex?%5Cmax(0,%20%5Ccdot)"> stops fines going negative.</p>
<p>Each turn only needs two multiplications by the big table of rules: one by <img src="https://latex.codecogs.com/png.latex?A"> and one by its flipped version <img src="https://latex.codecogs.com/png.latex?A%5E%5Ctop">. That is cheap even when <img src="https://latex.codecogs.com/png.latex?A"> has millions of rows, and graphics cards (GPUs) are very good at it. Methods that only use slopes like this are called <strong>first-order methods</strong>.</p>
<section id="try-it" class="level3">
<h3 class="anchored" data-anchor-id="try-it">Try it</h3>
<p>Drag the sliders and watch the shopper and referee move. Each blue dot is one turn. Both start at zero.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code hidden" id="cb1" data-startfrom="198" data-source-offset="0" style="background: #f1f3f5;"><pre class="sourceCode js code-with-copy"><code class="sourceCode javascript" style="counter-reset: source-line 197;"><span id="cb1-198"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">simulate</span>(eta<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> n) {</span>
<span id="cb1-199">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</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;">0</span><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> <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> sx <span 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="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> sy <span 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="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb1-200">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> pts <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>
<span id="cb1-201">  <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;">let</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="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span> n<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>) {</span>
<span id="cb1-202">    x <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;">Math</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">max</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> x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> eta <span 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> y))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// shopper walks downhill</span></span>
<span id="cb1-203">    y <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;">Math</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">max</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> y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> eta <span 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> x))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// referee walks uphill</span></span>
<span id="cb1-204">    sx <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> sy <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></span>
<span id="cb1-205">    pts<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">push</span>({i<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> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">ax</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> sx <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> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">ay</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> sy <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></span>
<span id="cb1-206">  }</span>
<span id="cb1-207">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> pts<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb1-208">}</span></code></pre></div></div>
<div class="cell-output cell-output-display">
<div id="ojs-cell-1" data-nodetype="declaration">

</div>
</div>
</div>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code hidden" id="cb2" data-startfrom="212" data-source-offset="0" style="background: #f1f3f5;"><pre class="sourceCode js code-with-copy"><code class="sourceCode javascript" style="counter-reset: source-line 211;"><span id="cb2-212"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">saddlePlot</span>(pts<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> showAvg) {</span>
<span id="cb2-213">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> marks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb2-214">    Plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">line</span>(pts<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> {<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">x</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;">"x"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">y</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;">"y"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">stroke</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;">"#4c78a8"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">strokeOpacity</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="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb2-215">    Plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">dot</span>(pts<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> {<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">x</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;">"x"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">y</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;">"y"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">r</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;">2.5</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">fill</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;">"#4c78a8"</span>})</span>
<span id="cb2-216">  ]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb2-217">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (showAvg) {</span>
<span id="cb2-218">    marks<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">push</span>(Plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">line</span>(pts<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> {<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">x</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;">"ax"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">y</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;">"ay"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">stroke</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;">"#f58518"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">strokeWidth</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;">2.5</span>}))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb2-219">    marks<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">push</span>(Plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">dot</span>(pts<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">slice</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="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">x</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;">"ax"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">y</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;">"ay"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">r</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="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">fill</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;">"#f58518"</span>}))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb2-220">  }</span>
<span id="cb2-221">  marks<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">push</span>(Plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">dot</span>([{<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">x</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="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">y</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="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">x</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;">"x"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">y</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;">"y"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">r</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;">7</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">fill</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;">"#e45756"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">stroke</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;">"white"</span>}))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb2-222">  marks<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">push</span>(Plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">text</span>([{<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">x</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="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">y</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="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">x</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;">"x"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">y</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;">"y"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">text</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> d <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=&gt;</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"saddle point (3, 2)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">dy</span><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;">14</span>}))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb2-223">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> Plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>({</span>
<span id="cb2-224">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">width</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;">520</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb2-225">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">height</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;">420</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb2-226">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">grid</span><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;">true</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb2-227">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">x</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> {<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">domain</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 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.5</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">label</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;">"x = kilos of flour (shopper) →"</span>}<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb2-228">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">y</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> {<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">domain</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 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="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">label</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;">"↑ y = fine per missing kilo (referee)"</span>}<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb2-229">    marks</span>
<span id="cb2-230">  })<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb2-231">}</span></code></pre></div></div>
<div class="cell-output cell-output-display">
<div id="ojs-cell-2" data-nodetype="declaration">

</div>
</div>
</div>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code hidden" id="cb3" data-startfrom="236" data-source-offset="-0" style="background: #f1f3f5;"><pre class="sourceCode js code-with-copy"><code class="sourceCode javascript" style="counter-reset: source-line 235;"><span id="cb3-236">viewof eta1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Inputs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">range</span>([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</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 class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> {<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">value</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.3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">step</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.05</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">label</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;">"Step size"</span>})</span>
<span id="cb3-237">viewof n1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Inputs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">range</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;">300</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> {<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">value</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;">60</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">step</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 class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">label</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;">"Number of turns"</span>})</span>
<span id="cb3-238"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">saddlePlot</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">simulate</span>(eta1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> n1)<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;">false</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-display">
<div>
<div id="ojs-cell-3-1" data-nodetype="declaration">

</div>
</div>
</div>
<div class="cell-output cell-output-display">
<div>
<div id="ojs-cell-3-2" data-nodetype="declaration">

</div>
</div>
</div>
<div class="cell-output cell-output-display">
<div>
<div id="ojs-cell-3-3" data-nodetype="expression">

</div>
</div>
</div>
</div>
<p>Did you spot the problem? <strong>They go round in circles!</strong> The shopper reacts to the referee, the referee reacts to the shopper, and they chase each other around the saddle point like two kids running around a table. Neither ever catches the other. Smaller steps make smoother circles, but they are still circles.</p>
<p>The good news is in the picture too: the circles go <em>around</em> the answer.</p>
</section>
</section>
<section id="the-average-is-smarter-than-the-walker-averaging" class="level2">
<h2 class="anchored" data-anchor-id="the-average-is-smarter-than-the-walker-averaging">3. The average is smarter than the walker (averaging)</h2>
<p>What is in the middle of a circle? The centre! And if you take lots of points spread evenly around a circle and average them, you land near the centre.</p>
<p>So Jaddle keeps a running <strong>average</strong> of everywhere the players have been. Write <img src="https://latex.codecogs.com/png.latex?z%20=%20(x,%20y)"> for both players’ positions together. After <img src="https://latex.codecogs.com/png.latex?N"> turns the average is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbar%20z_N%20=%20%5Cfrac%7B%5Ceta_1%20z_1%20+%20%5Ceta_2%20z_2%20+%20%5Cdots%20+%20%5Ceta_N%20z_N%7D%7B%5Ceta_1%20+%20%5Ceta_2%20+%20%5Cdots%20+%20%5Ceta_N%7D%0A"></p>
<p>Each position is weighted by the step size <img src="https://latex.codecogs.com/png.latex?%5Ceta_n"> used to reach it (more on <img src="https://latex.codecogs.com/png.latex?%5Ceta"> in the next section), so bigger steps count for more. Jaddle doesn’t store every old position. It nudges the average a little towards each new point, which costs almost nothing.</p>
<p>Think of a dog on a lead running in circles around a post. Take a photo every second and average the dog’s position over all the photos: you get the post.</p>
<p>The orange line below is the average. Turn up the number of turns and watch it home in on the answer.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code hidden" id="cb4" data-startfrom="274" data-source-offset="-0" style="background: #f1f3f5;"><pre class="sourceCode js code-with-copy"><code class="sourceCode javascript" style="counter-reset: source-line 273;"><span id="cb4-274">viewof eta2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Inputs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">range</span>([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</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 class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> {<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">value</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.3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">step</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.05</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">label</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;">"Step size"</span>})</span>
<span id="cb4-275">viewof n2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Inputs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">range</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;">300</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> {<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">value</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;">150</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">step</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 class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">label</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;">"Number of turns"</span>})</span>
<span id="cb4-276"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">saddlePlot</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">simulate</span>(eta2<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> n2)<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;">true</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-display">
<div>
<div id="ojs-cell-4-1" data-nodetype="declaration">

</div>
</div>
</div>
<div class="cell-output cell-output-display">
<div>
<div id="ojs-cell-4-2" data-nodetype="declaration">

</div>
</div>
</div>
<div class="cell-output cell-output-display">
<div>
<div id="ojs-cell-4-3" data-nodetype="expression">

</div>
</div>
</div>
</div>
<p>There is a catch. The average gets there <strong>slowly</strong>: to be 10 times more accurate you need about 10 times more turns. It also never forgets. The very first guesses, which were far from the answer, keep dragging the average away forever. We fix both problems in section 5.</p>
</section>
<section id="choosing-step-sizes-eta-k-t-and-s" class="level2">
<h2 class="anchored" data-anchor-id="choosing-step-sizes-eta-k-t-and-s">4. Choosing step sizes: <img src="https://latex.codecogs.com/png.latex?%5Ceta">, <img src="https://latex.codecogs.com/png.latex?k">, <img src="https://latex.codecogs.com/png.latex?t"> and <img src="https://latex.codecogs.com/png.latex?s"></h2>
<p>Steps that are too big overshoot and fly off. Steps that are too small take forever. Worse, different numbers in the same problem can need very different step sizes. One might be measured in millimetres and another in kilometres.</p>
<p>Jaddle builds every step size out of four pieces:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctau_j%20=%20%5Cfrac%7B%5Ceta%7D%7Bk%7D%5C,%20t_j%20%5Cqquad%5Cqquad%20%5Csigma_i%20=%20%5Ceta%5C,%20k%5C,%20s_i%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?%5Ctau_j"> is the step for the shopper’s <img src="https://latex.codecogs.com/png.latex?j">-th number, and <img src="https://latex.codecogs.com/png.latex?%5Csigma_i"> is the step for the referee’s <img src="https://latex.codecogs.com/png.latex?i">-th fine. Think of a group out on a hike:</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>Piece</th>
<th>Who it affects</th>
<th>Hiking version</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><img src="https://latex.codecogs.com/png.latex?%5Ceta"> (eta)</td>
<td>everyone</td>
<td>how fast the whole group walks</td>
</tr>
<tr class="even">
<td><img src="https://latex.codecogs.com/png.latex?k"></td>
<td>shopper vs.&nbsp;referee</td>
<td>whether the shopper team or the referee team walks faster</td>
</tr>
<tr class="odd">
<td><img src="https://latex.codecogs.com/png.latex?t_j"></td>
<td>the shopper’s <img src="https://latex.codecogs.com/png.latex?j">-th number</td>
<td>how long one shopper’s stride is</td>
</tr>
<tr class="even">
<td><img src="https://latex.codecogs.com/png.latex?s_i"></td>
<td>the referee’s <img src="https://latex.codecogs.com/png.latex?i">-th fine</td>
<td>how long one referee’s stride is</td>
</tr>
</tbody>
</table>
<p>Before any of this starts, Jaddle <strong>rescales</strong> the problem so that the numbers in <img src="https://latex.codecogs.com/png.latex?A">, <img src="https://latex.codecogs.com/png.latex?b"> and <img src="https://latex.codecogs.com/png.latex?c"> are all roughly the same size. It is a bit like converting every measurement to the same units. (The methods are called Ruiz and Pock–Chambolle scaling, if you want to look them up.) On top of that, <img src="https://latex.codecogs.com/png.latex?t"> and <img src="https://latex.codecogs.com/png.latex?s"> start at 1 and are then <strong>adapted</strong> as the solver learns more about the problem. Giving each number its own multiplier like this is called <strong>diagonal preconditioning</strong>: “diagonal” because if you wrote the multipliers into a table, they would only appear along its diagonal.</p>
<section id="eta-a-step-that-checks-itself" class="level3">
<h3 class="anchored" data-anchor-id="eta-a-step-that-checks-itself"><img src="https://latex.codecogs.com/png.latex?%5Ceta">: a step that checks itself</h3>
<p>Nobody tells Jaddle the right step size. It works it out as it goes, like this.</p>
<p><strong>Take a trial step.</strong> Then measure two things:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctext%7Bmovement%7D%20=%20k%5C,%5ClVert%20%5CDelta%20x%5CrVert%5E2%20+%20%5Cfrac%7B1%7D%7Bk%7D%5C,%5ClVert%20%5CDelta%20y%20%5CrVert%5E2%0A%5Cqquad%5Cqquad%0A%5Ctext%7Binteraction%7D%20=%20%5Cbig%7C%5C,%5CDelta%20y%5E%5Ctop%20A%5C,%20%5CDelta%20x%5C,%5Cbig%7C%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?%5CDelta%20x"> and <img src="https://latex.codecogs.com/png.latex?%5CDelta%20y"> are how much each player changed, and <img src="https://latex.codecogs.com/png.latex?%5ClVert%20%5CDelta%20x%20%5CrVert%5E2"> means “square each change and add them up”. The <strong>movement</strong> is how far the players went. The <strong>interaction</strong> is how hard their two moves pushed against each other through the rules in <img src="https://latex.codecogs.com/png.latex?A">.</p>
<p>The interaction is what causes trouble. When the shopper’s move and the referee’s move push on each other too hard, they overreact and fly off. When the moves are big compared with how much they disturb each other, the step is safe. The largest safe step is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbar%5Ceta%20=%20%5Cfrac%7B%5Ctext%7Bmovement%7D%7D%7B2%20%5Ctimes%20%5Ctext%7Binteraction%7D%7D%0A"></p>
<ul>
<li><strong>If <img src="https://latex.codecogs.com/png.latex?%5Ceta"> was bigger than <img src="https://latex.codecogs.com/png.latex?%5Cbar%5Ceta"></strong>, the step was too greedy. Jaddle throws it away, shrinks <img src="https://latex.codecogs.com/png.latex?%5Ceta"> to just under <img src="https://latex.codecogs.com/png.latex?%5Cbar%5Ceta">, and tries again.</li>
<li><strong>If <img src="https://latex.codecogs.com/png.latex?%5Ceta"> was safe</strong>, Jaddle keeps the step and lets <img src="https://latex.codecogs.com/png.latex?%5Ceta"> grow a little for next time:</li>
</ul>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ceta_%7B%5Ctext%7Bnext%7D%7D%20=%20%5Cmin%5CBig(%5C;%20%5Cbig(1%20-%20(n+1)%5E%7B-0.3%7D%5Cbig)%5C,%5Cbar%5Ceta,%20%5C;%5C;%5C;%20%5Cbig(1%20+%20(n+1)%5E%7B-0.6%7D%5Cbig)%5C,%5Ceta%20%5C;%5CBig)%0A"></p>
<p>Don’t worry about the odd powers; <img src="https://latex.codecogs.com/png.latex?n"> just counts the steps taken so far. The first part says “stay a bit below the safe limit”. The second says “grow, but by less and less as time goes on”. At the start <img src="https://latex.codecogs.com/png.latex?%5Ceta"> can grow by up to 66% in one go. After a thousand steps it can only grow by about 1.6%.</p>
<p>This means Jaddle never needs to know in advance how big <img src="https://latex.codecogs.com/png.latex?A"> can make things, which is expensive to work out for a huge problem. It also fixes a bad starting guess for <img src="https://latex.codecogs.com/png.latex?%5Ceta"> within a few steps. (When <img src="https://latex.codecogs.com/png.latex?t"> and <img src="https://latex.codecogs.com/png.latex?s"> are switched on, the movement is measured with each number’s change divided by its own <img src="https://latex.codecogs.com/png.latex?t_j"> or <img src="https://latex.codecogs.com/png.latex?s_i">, but the idea is the same.)</p>
</section>
<section id="k-who-should-walk-faster" class="level3">
<h3 class="anchored" data-anchor-id="k-who-should-walk-faster"><img src="https://latex.codecogs.com/png.latex?k">: who should walk faster?</h3>
<p>Sometimes the shopper is nearly at the answer while the referee still has a long way to go, or the other way round. <img src="https://latex.codecogs.com/png.latex?k"> shares the step between them: a bigger <img src="https://latex.codecogs.com/png.latex?k"> makes the referee’s steps longer (<img src="https://latex.codecogs.com/png.latex?%5Ceta%20k">) and the shopper’s shorter (<img src="https://latex.codecogs.com/png.latex?%5Ceta%20/%20k">). It doesn’t make the steps riskier overall, because the two step sizes multiplied together are always <img src="https://latex.codecogs.com/png.latex?%5Ceta%5E2">, whatever <img src="https://latex.codecogs.com/png.latex?k"> is.</p>
<p>Jaddle updates <img src="https://latex.codecogs.com/png.latex?k"> at every restart (section 5). It looks at how far each player travelled during the last stretch:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ak_%7B%5Ctext%7Btarget%7D%7D%20=%20%5Cfrac%7B%5ClVert%20%5CDelta%20y%20%5CrVert%7D%7B%5ClVert%20%5CDelta%20x%20%5CrVert%7D%0A"></p>
<p>If the referee travelled further, the referee probably still has further to go, so the referee should get the longer stride. Jaddle doesn’t jump straight to the target, because one stretch of measurements can be misleading. It meets the old value halfway, in the multiplying sense:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ak_%7B%5Ctext%7Bnew%7D%7D%20=%20%5Csqrt%7Bk_%7B%5Ctext%7Bold%7D%7D%20%5Ctimes%20k_%7B%5Ctext%7Btarget%7D%7D%7D%0A"></p>
<p>This is the <strong>geometric mean</strong>. For example, if <img src="https://latex.codecogs.com/png.latex?k_%7B%5Ctext%7Bold%7D%7D%20=%201"> and <img src="https://latex.codecogs.com/png.latex?k_%7B%5Ctext%7Btarget%7D%7D%20=%20100">, then <img src="https://latex.codecogs.com/png.latex?k_%7B%5Ctext%7Bnew%7D%7D%20=%20%5Csqrt%7B100%7D%20=%2010">. Why not the ordinary average? Because <img src="https://latex.codecogs.com/png.latex?k"> is a ratio. <img src="https://latex.codecogs.com/png.latex?k%20=%20100"> and <img src="https://latex.codecogs.com/png.latex?k%20=%201/100"> are equally lopsided, just in opposite directions, and the geometric mean treats them fairly. Jaddle also keeps <img src="https://latex.codecogs.com/png.latex?k"> between <img src="https://latex.codecogs.com/png.latex?10%5E%7B-8%7D"> and <img src="https://latex.codecogs.com/png.latex?10%5E%7B8%7D"> so it can never run away. The very first <img src="https://latex.codecogs.com/png.latex?k"> is a guess based on the sizes of <img src="https://latex.codecogs.com/png.latex?c"> and <img src="https://latex.codecogs.com/png.latex?b">.</p>
</section>
<section id="t-and-s-every-number-gets-its-own-stride" class="level3">
<h3 class="anchored" data-anchor-id="t-and-s-every-number-gets-its-own-stride"><img src="https://latex.codecogs.com/png.latex?t"> and <img src="https://latex.codecogs.com/png.latex?s">: every number gets its own stride</h3>
<p><img src="https://latex.codecogs.com/png.latex?%5Ceta"> and <img src="https://latex.codecogs.com/png.latex?k"> treat big groups of numbers the same. But inside a problem with a million numbers, some are marching steadily towards their answer while others are jittering back and forth. <img src="https://latex.codecogs.com/png.latex?t"> and <img src="https://latex.codecogs.com/png.latex?s"> give each number its own stride.</p>
<p>At each restart, Jaddle looks back over the last stretch at each number <img src="https://latex.codecogs.com/png.latex?j"> and works out two things:</p>
<ul>
<li>its <strong>net distance</strong> <img src="https://latex.codecogs.com/png.latex?r_j">: how far it ended up from where it started;</li>
<li>its <strong>total wiggle</strong> <img src="https://latex.codecogs.com/png.latex?H_j">: the size of every step it took, squared and added up.</li>
</ul>
<p>Then it divides one by the other:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Crho_j%20=%20%5Cfrac%7Br_j%7D%7B%5Csqrt%7BH_j%7D%7D%0A"></p>
<p>Here is what <img src="https://latex.codecogs.com/png.latex?%5Crho"> (rho) looks like for three numbers that each took 16 steps of size 1, so <img src="https://latex.codecogs.com/png.latex?%5Csqrt%7BH%7D%20=%20%5Csqrt%7B16%7D%20=%204"> for all of them:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Number</th>
<th>What it did</th>
<th>Net distance <img src="https://latex.codecogs.com/png.latex?r"></th>
<th><img src="https://latex.codecogs.com/png.latex?%5Crho%20=%20r%20/%204"></th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>A</td>
<td>16 steps forward</td>
<td>16</td>
<td>4</td>
<td>marching steadily</td>
</tr>
<tr class="even">
<td>B</td>
<td>10 forward, 6 back</td>
<td>4</td>
<td>1</td>
<td>wandering a bit</td>
</tr>
<tr class="odd">
<td>C</td>
<td>forward, back, forward, back…</td>
<td>0</td>
<td>0</td>
<td>jittering on the spot</td>
</tr>
</tbody>
</table>
<p>A big <img src="https://latex.codecogs.com/png.latex?%5Crho"> means the number knows where it is going, so it should take <strong>bigger</strong> steps. A small <img src="https://latex.codecogs.com/png.latex?%5Crho"> means it keeps overshooting and coming back, so it should take <strong>smaller</strong> steps. Jaddle updates each number’s stride with</p>
<p><img src="https://latex.codecogs.com/png.latex?%0At_j%20%5C;%5Cleftarrow%5C;%20t_j%20%5Ctimes%20%5Cleft(%5Cfrac%7B%5Crho_j%7D%7B%5Crho_%7B%5Ctext%7Btypical%7D%7D%7D%5Cright)%5E%7B1/2%7D%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Crho_%7B%5Ctext%7Btypical%7D%7D"> is the geometric mean of all the <img src="https://latex.codecogs.com/png.latex?%5Crho">’s. If only A and B were in the problem, <img src="https://latex.codecogs.com/png.latex?%5Crho_%7B%5Ctext%7Btypical%7D%7D%20=%20%5Csqrt%7B4%20%5Ctimes%201%7D%20=%202">:</p>
<ul>
<li>A’s stride is multiplied by <img src="https://latex.codecogs.com/png.latex?%5Csqrt%7B4/2%7D%20%5Capprox%201.41">, so it gets 41% longer.</li>
<li>B’s stride is multiplied by <img src="https://latex.codecogs.com/png.latex?%5Csqrt%7B1/2%7D%20%5Capprox%200.71">, so it gets 29% shorter.</li>
</ul>
<p>Dividing by the typical value keeps the group’s overall pace the same, so <img src="https://latex.codecogs.com/png.latex?%5Ceta"> and <img src="https://latex.codecogs.com/png.latex?k"> stay in charge of that. Each stride is also kept within 10 times bigger or smaller than where it started, and numbers that didn’t move at all (for example ones stuck at a limit) give no information, so they keep their stride. The referee’s fines get exactly the same treatment to update <img src="https://latex.codecogs.com/png.latex?s_i">.</p>
<p>This idea borrows from machine learning methods called AdaGrad and DoG (“distance over gradients”). On a standard collection of hard test problems (the MIPLIB 2017 linear programs) it cut Jaddle’s solve times by about 18% on average.</p>
</section>
</section>
<section id="restarts-when-in-doubt-start-fresh" class="level2">
<h2 class="anchored" data-anchor-id="restarts-when-in-doubt-start-fresh">5. Restarts: when in doubt, start fresh</h2>
<p>Remember the two problems with averaging? It is slow, and it never forgets the bad early guesses.</p>
<p>The fix is the <strong>restart</strong>. Once the average has got a lot closer to the answer, Jaddle <strong>jumps</strong> both players to the average, throws away all the history, and starts a fresh average from there.</p>
<p>Why does that help so much? The size of the circle the players run around depends on how far from the answer they start. Start closer and the next circle is smaller, so the next average is closer still, and so on. Each restart shrinks the error by a fixed fraction. “Ten times more turns for ten times more accuracy” turns into “a few more restarts for each extra digit of accuracy”.</p>
<section id="when-to-restart" class="level3">
<h3 class="anchored" data-anchor-id="when-to-restart">When to restart</h3>
<p>Jaddle doesn’t know the answer (if it did, it could stop!), so it can’t measure the distance to it. Instead it uses a <strong>wrongness score</strong> made from three checks. The score is the largest of them:</p>
<ol type="1">
<li><strong>Broken rules.</strong> How badly does the shopper break the rules? For flour: <img src="https://latex.codecogs.com/png.latex?%5Cmax(0,%5C%203%20-%20x)">.</li>
<li><strong>Unfair fines.</strong> Could these fines ever be the right price list? For flour, a fine above $2 makes no sense, because buying more flour would be cheaper than paying it: <img src="https://latex.codecogs.com/png.latex?%5Cmax(0,%5C%20y%20-%202)">.</li>
<li><strong>The gap.</strong> The shopper pays <img src="https://latex.codecogs.com/png.latex?2x">. The fines tell you the least anyone could ever pay, here <img src="https://latex.codecogs.com/png.latex?3y">. At the answer these two numbers match (<img src="https://latex.codecogs.com/png.latex?2%20%5Ctimes%203%20=%203%20%5Ctimes%202%20=%206">), so the gap <img src="https://latex.codecogs.com/png.latex?%7C2x%20-%203y%7C"> should be zero.</li>
</ol>
<p>Each check is divided by the size of the problem’s numbers, so the score means the same thing for a tiny problem and a huge one. When all three checks are below a small tolerance (by default 0.001), Jaddle stops: it has found the answer.</p>
<p>Write <img src="https://latex.codecogs.com/png.latex?m"> for the score now and <img src="https://latex.codecogs.com/png.latex?m_%7B%5Ctext%7Blast%7D%7D"> for the score at the last restart. Jaddle restarts when one of these happens:</p>
<ul>
<li><strong>Good progress:</strong> <img src="https://latex.codecogs.com/png.latex?m%20%5Cle%200.2%5C,%20m_%7B%5Ctext%7Blast%7D%7D">. The score has dropped to a fifth. Lock in the gain.</li>
<li><strong>Stalling:</strong> <img src="https://latex.codecogs.com/png.latex?m%20%5Cle%200.8%5C,%20m_%7B%5Ctext%7Blast%7D%7D"> and the score has started going back <em>up</em>. We made some progress and it’s fading, so lock it in.</li>
<li><strong>Taking too long:</strong> the current stretch has gone on for a long time without either of the above, so restart anyway.</li>
</ul>
<p>At a restart Jaddle:</p>
<ol type="1">
<li>jumps to whichever of the <strong>current point</strong> or the <strong>average</strong> has the better score;</li>
<li>updates <img src="https://latex.codecogs.com/png.latex?k">, <img src="https://latex.codecogs.com/png.latex?t"> and <img src="https://latex.codecogs.com/png.latex?s"> using what it saw during the stretch;</li>
<li>throws away the old average and starts a new one;</li>
<li>keeps <img src="https://latex.codecogs.com/png.latex?%5Ceta">, since the step it learned is still useful.</li>
</ol>
</section>
<section id="see-the-difference" class="level3">
<h3 class="anchored" data-anchor-id="see-the-difference">See the difference</h3>
<p>Here is the flour problem again with a fixed step size, scored by the wrongness score above. The score axis is logarithmic: each step down the axis divides the score by the same amount.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code hidden" id="cb5" data-startfrom="509" data-source-offset="0" style="background: #f1f3f5;"><pre class="sourceCode js code-with-copy"><code class="sourceCode javascript" style="counter-reset: source-line 508;"><span id="cb5-509">restartData <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb5-510">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> merit <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> y) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Math</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">max</span>(</span>
<span id="cb5-511">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Math</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">max</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;">3</span> <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;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-512">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Math</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">max</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> 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;">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;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-513">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Math</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">abs</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> 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;">3</span> <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> (<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;">Math</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">abs</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> x) <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;">Math</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">abs</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> y))</span>
<span id="cb5-514">  )<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-515">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> step <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> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> eta) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=&gt;</span> {</span>
<span id="cb5-516">    x <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;">Math</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">max</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> x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> eta <span 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> y))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-517">    y <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;">Math</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">max</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> y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> eta <span 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> x))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-518">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [x<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></span>
<span id="cb5-519">  }<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-520">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> eta <span 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.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> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">300</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> 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;">1e-16</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> rows <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>
<span id="cb5-521"></span>
<span id="cb5-522">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// No restarts: score the current point and the average.</span></span>
<span id="cb5-523">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</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;">0</span><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> <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> sx <span 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="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> sy <span 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="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-524">  <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;">let</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="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span> N<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>) {</span>
<span id="cb5-525">    [x<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> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">step</span>(x<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> eta)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-526">    sx <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> sy <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></span>
<span id="cb5-527">    rows<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">push</span>({i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">score</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;">Math</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">max</span>(floor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">merit</span>(x<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> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">method</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;">"Current point only"</span>})<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-528">    rows<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">push</span>({i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">score</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;">Math</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">max</span>(floor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">merit</span>(sx <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> sy <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> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">method</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;">"Average"</span>})<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-529">  }</span>
<span id="cb5-530"></span>
<span id="cb5-531">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// With restarts.</span></span>
<span id="cb5-532">  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;">0</span><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> <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> sx <span 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="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> sy <span 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="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-533">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</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;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> mLast <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">merit</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;">0</span>)<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> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">Infinity</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-534">  <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;">let</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="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span> N<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>) {</span>
<span id="cb5-535">    [x<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> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">step</span>(x<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> eta)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-536">    n<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">++;</span> sx <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> sy <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></span>
<span id="cb5-537">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sx <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> ay <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sy <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>
<span id="cb5-538">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> ma <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">merit</span>(ax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> ay)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> mi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">merit</span>(x<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> m <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;">Math</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">min</span>(ma<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> mi)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-539">    rows<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">push</span>({i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">score</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;">Math</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">max</span>(floor<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> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">method</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;">"Average + restarts"</span>})<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-540">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> good <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;">&lt;=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> mLast<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-541">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> stalling <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;">&lt;=</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> mLast <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;&amp;</span> m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> prev<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-542">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (n <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;">10</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;&amp;</span> (good <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">||</span> stalling)) {</span>
<span id="cb5-543">      <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (ma <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span> mi) { x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ax<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> ay<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> }</span>
<span id="cb5-544">      sx <span 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="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> sy <span 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="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;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> mLast <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> prev <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;">Infinity</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-545">    } <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> {</span>
<span id="cb5-546">      prev <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></span>
<span id="cb5-547">    }</span>
<span id="cb5-548">  }</span>
<span id="cb5-549">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> rows<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-550">}</span></code></pre></div></div>
<div class="cell-output cell-output-display">
<div id="ojs-cell-5" data-nodetype="declaration">

</div>
</div>
</div>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code hidden" id="cb6" data-startfrom="554" data-source-offset="0" style="background: #f1f3f5;"><pre class="sourceCode js code-with-copy"><code class="sourceCode javascript" style="counter-reset: source-line 553;"><span id="cb6-554">Plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>({</span>
<span id="cb6-555">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">width</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;">620</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-556">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">height</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;">380</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-557">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">grid</span><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;">true</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-558">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">x</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> {<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">label</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;">"turns →"</span>}<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-559">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">y</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> {<span class="dt" style="color: #AD0000;
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;">"log"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">label</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;">"↑ wrongness score (log scale)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">tickFormat</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;">".0e"</span>}<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-560">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">color</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> {</span>
<span id="cb6-561">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">legend</span><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;">true</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-562">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">domain</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;">"Current point only"</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;">"Average"</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;">"Average + restarts"</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-563">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">range</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;">"#4c78a8"</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;">"#f58518"</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;">"#54a24b"</span>]</span>
<span id="cb6-564">  }<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-565">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">marks</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> [</span>
<span id="cb6-566">    Plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">line</span>(restartData<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> {<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">x</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;">"i"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">y</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;">"score"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">stroke</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;">"method"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">strokeWidth</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="cb6-567">  ]</span>
<span id="cb6-568">})</span></code></pre></div></div>
<div class="cell-output cell-output-display">
<div id="ojs-cell-6" data-nodetype="expression">

</div>
</div>
</div>
<p>The current point (blue) circles forever and never improves. The average (orange) slowly creeps in. With restarts (green) the score drops off a cliff and reaches the limit of what the computer can store (about 16 digits) in roughly 200 turns.</p>
</section>
</section>
<section id="putting-it-all-together" class="level2">
<h2 class="anchored" data-anchor-id="putting-it-all-together">Putting it all together</h2>
<p>Here is the whole recipe in one place:</p>
<pre class="text"><code>rescale the problem so its numbers are all about the same size
start with x = 0 (inside its limits), y = 0, η = 1, k = a guess, all t and s = 1

repeat:
    take a trial alternating step with τ = (η / k)·t and σ = η·k·s
    if the step was too big:  shrink η and try again
    otherwise:                keep it, update the average, let η grow a little

    every so often:
        work out the wrongness score
        if all three checks are tiny:  stop, we have the answer!
        if a restart rule fires:
            jump to the better of the current point and the average
            update k, t and s
            start a fresh average</code></pre>
<p>Every part of this is either a multiplication by <img src="https://latex.codecogs.com/png.latex?A"> or <img src="https://latex.codecogs.com/png.latex?A%5E%5Ctop">, or simple arithmetic on each number separately. There are no big equation-solving steps, which is why it works on problems with millions of numbers and runs so well on graphics cards. Jaddle is written in JAX, a Python library that turns the whole loop into one fast compiled program. The same code runs on a laptop CPU, a GPU or a TPU.</p>
<p>Jaddle also has a second solver for curvier problems (convex programs), where the cost and rules can be more complicated than straight lines. It uses the same five ideas. The only difference is that it can’t write the slopes down as <img src="https://latex.codecogs.com/png.latex?c%20+%20A%5E%5Ctop%20y">, so JAX works them out automatically.</p>
</section>
<section id="cheat-sheet" class="level2">
<h2 class="anchored" data-anchor-id="cheat-sheet">Cheat sheet</h2>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Symbol</th>
<th>Name</th>
<th>What it means</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><img src="https://latex.codecogs.com/png.latex?x"></td>
<td>primal variables</td>
<td>the shopper’s choices</td>
</tr>
<tr class="even">
<td><img src="https://latex.codecogs.com/png.latex?y"></td>
<td>dual variables</td>
<td>the referee’s fines, one per rule</td>
</tr>
<tr class="odd">
<td><img src="https://latex.codecogs.com/png.latex?L(x,%20y)"></td>
<td>Lagrangian</td>
<td>the total bill: cost plus fines</td>
</tr>
<tr class="even">
<td><img src="https://latex.codecogs.com/png.latex?%5Ctau,%5C%20%5Csigma"></td>
<td>step sizes</td>
<td>how far the shopper / referee move per turn</td>
</tr>
<tr class="odd">
<td><img src="https://latex.codecogs.com/png.latex?%5Cbar%20z"></td>
<td>average</td>
<td>the weighted average of everywhere they’ve been</td>
</tr>
<tr class="even">
<td><img src="https://latex.codecogs.com/png.latex?%5Ceta"></td>
<td>base step</td>
<td>the whole group’s pace, learned as it goes</td>
</tr>
<tr class="odd">
<td><img src="https://latex.codecogs.com/png.latex?%5Cbar%5Ceta"></td>
<td>safe step</td>
<td>the largest step that won’t make them overreact</td>
</tr>
<tr class="even">
<td><img src="https://latex.codecogs.com/png.latex?k"></td>
<td>primal weight</td>
<td>shares the pace between shopper and referee</td>
</tr>
<tr class="odd">
<td><img src="https://latex.codecogs.com/png.latex?t_j,%5C%20s_i"></td>
<td>diagonal</td>
<td>each number’s personal stride</td>
</tr>
<tr class="even">
<td><img src="https://latex.codecogs.com/png.latex?%5Crho_j"></td>
<td>drift ratio</td>
<td>net distance ÷ total wiggle</td>
</tr>
<tr class="odd">
<td><img src="https://latex.codecogs.com/png.latex?m"></td>
<td>wrongness score</td>
<td>the largest of: broken rules, unfair fines, gap</td>
</tr>
</tbody>
</table>


</section>

 ]]></description>
  <category>jaddle</category>
  <category>optimisation</category>
  <category>duality</category>
  <category>algorithms</category>
  <guid>https://brendanvr.github.io/jaddle-blog/posts/how-jaddle-works/how_jaddle_works.html</guid>
  <pubDate>Wed, 21 Oct 2026 16:00:00 GMT</pubDate>
  <media:content url="https://brendanvr.github.io/jaddle-blog/posts/how-jaddle-works/how-jaddle-works-logo.svg" medium="image" type="image/svg+xml"/>
</item>
<item>
  <title>How Much Wobble Can You Handle?</title>
  <dc:creator>Brendan van Rooyen</dc:creator>
  <link>https://brendanvr.github.io/jaddle-blog/posts/portfolio/portfolio.html</link>
  <description><![CDATA[ 





<p>Suppose someone hands you a pile of money and a list of 500 things you could invest it in. Some are expected to grow a lot. Some are expected to grow a little. All of them wobble up and down unpredictably. How do you decide what to buy?</p>
<p>If you only cared about growth, the answer would be easy: put everything into whatever is expected to grow fastest. But that’s like crossing a river by jumping onto the single biggest stepping stone: thrilling, until it wobbles. Most people would rather give up a little expected growth to sleep better at night.</p>
<p>This example explores that trade-off.</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">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>The data in this example is entirely made up. Nothing here is financial advice!</p>
</div>
</div>
<section id="the-rules-of-the-game" class="level2">
<h2 class="anchored" data-anchor-id="the-rules-of-the-game">The rules of the game</h2>
<p>We need to decide what <strong>fraction</strong> of our money goes into each of the 500 assets. The rules are:</p>
<ul>
<li>The fractions must add up to 100%. We invest all the money, no more and no less.</li>
<li>No fraction can be negative. In finance-speak, the portfolio is <em>long-only</em>: we can’t bet against an asset or borrow to invest.</li>
<li>The portfolio’s <strong>volatility</strong> (how much it’s expected to wobble) must stay below a limit we choose.</li>
</ul>
<p>Within those rules, we want the highest <strong>expected return</strong>.</p>
</section>
<section id="two-kinds-of-wobble" class="level2">
<h2 class="anchored" data-anchor-id="two-kinds-of-wobble">Two kinds of wobble</h2>
<p>To measure wobble, we use what’s called a <strong>factor model</strong>. It says each asset’s ups and downs come from two sources.</p>
<p><strong>Shared wobble.</strong> Some forces push lots of assets around at once. Think of an ice-cream shop and a sunscreen company: a heatwave is good for both, and a rainy summer is bad for both. In our example there are 50 of these shared forces, or <em>factors</em>, and each asset reacts to them in its own way.</p>
<p><strong>Personal wobble.</strong> Every asset also has its own private surprises. A particular shop might get a rave review or a broken freezer. These are unrelated to everything else.</p>
<p>This distinction matters because the two kinds of wobble behave differently when you combine assets:</p>
<ul>
<li><strong>Personal wobbles tend to cancel out.</strong> If you own lots of different things, one asset’s bad day is often balanced by another’s good day. This is the magic of <strong>diversification</strong>.</li>
<li><strong>Shared wobbles don’t cancel.</strong> If all your assets love sunny weather, owning more of them doesn’t protect you from rain.</li>
</ul>
</section>
<section id="where-the-ice-cream-cone-comes-in" class="level2">
<h2 class="anchored" data-anchor-id="where-the-ice-cream-cone-comes-in">Where the ice-cream cone comes in</h2>
<p>To work out a portfolio’s total wobble, you collect all its exposures, to each shared factor and to each asset’s personal risk, and measure their combined size, the way you’d measure the length of an arrow from its parts.</p>
<p>The rule “this combined size must be below the limit” has a surprising shape. If you drew all the portfolios that satisfy it (and let the limit itself vary), you’d get a shape like a cone. It even has a nickname among optimisation folk: the <strong>ice-cream cone</strong>. Its formal name is the <em>second-order cone</em>.</p>
<p>Why does that matter? Because some optimisation solvers can only handle flat-sided shapes, built from straight-line rules like “this plus that must be at most ten”. A cone has a curved, rounded surface, so those solvers can’t take it directly. <a href="https://github.com/BrendanVR/Jaddle">Jaddle</a> can: at each step it takes a stride towards a better answer, then gently pushes the result back inside the cone if it’s wandered out.</p>
</section>
<section id="tracing-the-efficient-frontier" class="level2">
<h2 class="anchored" data-anchor-id="tracing-the-efficient-frontier">Tracing the efficient frontier</h2>
<p>Rather than picking one wobble limit, we try five, ranging from cautious (6% volatility) to adventurous (20%). For each one, we find the best possible portfolio.</p>
<p>If you plot the results, with risk along the bottom and expected return up the side, you get a curve that finance people call the <strong>efficient frontier</strong>. Every point on it is a portfolio where you can’t get more expected return without accepting more risk. Any portfolio <em>below</em> the curve is leaving money on the table.</p>
<p>Two things make tracing this curve quicker:</p>
<ul>
<li><strong>Warm starts.</strong> Each new solve begins from the previous answer instead of from scratch. Moving the limit from 6% to 8% doesn’t change the best portfolio completely, so starting nearby saves a lot of work.</li>
<li><strong>Reusing the setup.</strong> The problem is described once, and only the wobble limit changes between solves.</li>
</ul>
</section>
<section id="the-slope-tells-a-story" class="level2">
<h2 class="anchored" data-anchor-id="the-slope-tells-a-story">The slope tells a story</h2>
<p>Every time the solver obeys a rule, it can also report how much that rule is costing. For the wobble limit, this number answers the question: <strong>“If I allowed a little more wobble, how much extra return would I get?”</strong></p>
<p>That’s precisely the slope of the efficient frontier at that point, and it shrinks as the limit grows. When you’re being very cautious, accepting a bit more risk buys you a healthy boost in return. When you’re already being adventurous, each extra bit of risk buys less and less. Like adding sugar to tea: the first spoonful makes a big difference; the fifth, not so much.</p>
</section>
<section id="what-the-portfolios-look-like" class="level2">
<h2 class="anchored" data-anchor-id="what-the-portfolios-look-like">What the portfolios look like</h2>
<p>At the cautious end, the best portfolio is spread across many assets, leaning hard on diversification to cancel out personal wobbles.</p>
<p>As the limit loosens, the portfolio <strong>concentrates</strong> on fewer assets with higher expected returns. Freed from the need to stay calm, it stops spreading its bets and starts backing its favourites.</p>
</section>
<section id="checking-our-work" class="level2">
<h2 class="anchored" data-anchor-id="checking-our-work">Checking our work</h2>
<p>To make sure Jaddle’s answers are right, we solve every problem a second time with Clarabel, a well-respected solver that ships with CVXPY. The returns agree to within a tiny fraction of a percent, and so do the portfolios themselves.</p>
</section>
<section id="the-takeaway" class="level2">
<h2 class="anchored" data-anchor-id="the-takeaway">The takeaway</h2>
<p>Investing well isn’t just about picking winners. It’s about understanding how your choices wobble <em>together</em>. By describing risk as the size of an arrow, and using a solver that’s comfortable with the curved, cone-shaped rules that come with it, we can find, for any level of nerve, the best portfolio for that amount of risk. And we get a bonus: a clear reading of exactly how much extra return each extra bit of courage would buy.</p>


</section>

 ]]></description>
  <category>jaddle</category>
  <category>optimisation</category>
  <category>cone programming</category>
  <category>finance</category>
  <guid>https://brendanvr.github.io/jaddle-blog/posts/portfolio/portfolio.html</guid>
  <pubDate>Tue, 20 Oct 2026 16:00:00 GMT</pubDate>
  <media:content url="https://brendanvr.github.io/jaddle-blog/posts/portfolio/portfolio-logo.png" medium="image" type="image/png" height="144" width="144"/>
</item>
<item>
  <title>The Goldilocks Battery</title>
  <dc:creator>Brendan van Rooyen</dc:creator>
  <link>https://brendanvr.github.io/jaddle-blog/posts/battery-sizing/battery-sizing.html</link>
  <description><![CDATA[ 





<p>Picture a building with solar panels on the roof. At midday the sun is blazing and the panels are churning out electricity. In the evening, when everyone gets home and turns on the oven, the TV and the kettle, the sun has gone down and the power has to come from the grid, at the most expensive time of day.</p>
<p>The obvious fix is a battery: soak up cheap or free energy when it’s plentiful and use it later when it’s pricey. But batteries aren’t free. Buy one that’s too small and you miss out on savings. Buy one that’s too big and you’ve paid for capacity you never use.</p>
<p>So how big should it be? Like Goldilocks, we’re looking for the one that’s <em>just right</em>.</p>
<section id="two-questions-hiding-inside-one" class="level2">
<h2 class="anchored" data-anchor-id="two-questions-hiding-inside-one">Two questions hiding inside one</h2>
<p>It turns out “how big a battery should I buy?” contains a second question tucked inside it: <strong>“how would I actually use it?”</strong></p>
<p>You can’t judge a battery without knowing how it would be run. A big battery used badly might save less than a small battery used cleverly. So, to size the battery, we need to know how to operate it well on every kind of day it might face.</p>
<p>That gives us two layers:</p>
<ol type="1">
<li><strong>The operating problem</strong> (every day): given a battery of a certain size, when should it charge and when should it discharge to make the electricity bill as small as possible?</li>
<li><strong>The sizing problem</strong> (once): given that we’ll always run the battery as well as we can, which size gives the lowest total cost, counting both the battery’s price and the bills?</li>
</ol>
</section>
<section id="one-day-at-a-time" class="level2">
<h2 class="anchored" data-anchor-id="one-day-at-a-time">One day at a time</h2>
<p>We split each day into 24 hours. Each hour has:</p>
<ul>
<li>an <strong>electricity price</strong>, which peaks in the morning and, even more, in the evening;</li>
<li>a <strong>demand</strong>, the amount of power the building needs, which follows a similar pattern;</li>
<li>some <strong>solar output</strong>, which peaks around lunchtime and depends on how cloudy the day is.</li>
</ul>
<p>The battery has two numbers that describe its size, and it’s worth knowing the difference:</p>
<ul>
<li><strong>Energy capacity</strong> (in kilowatt-hours) is how much it can <em>store</em>. Think of it as the size of a water tank.</li>
<li><strong>Power rating</strong> (in kilowatts) is how fast it can <em>charge or discharge</em>. Think of it as the width of the tap.</li>
</ul>
<p>A huge tank with a tiny tap can’t empty fast enough to cover the evening rush. A wide tap on a tiny tank runs dry in minutes. You need a sensible match.</p>
<p>Planning one day’s battery use, given the prices, the demand, the sunshine and the battery’s size, is a classic <strong>linear program</strong>: a puzzle where you choose a bunch of numbers (how much to charge, discharge and buy each hour) to minimise a cost, while obeying straightforward rules like “you can’t store more than the tank holds” and “the lights must stay on”. The battery also has to end the day as full as it started, so it can’t cheat by borrowing from tomorrow.</p>
</section>
<section id="thirty-two-days-of-weather" class="level2">
<h2 class="anchored" data-anchor-id="thirty-two-days-of-weather">Thirty-two days of weather</h2>
<p>One day isn’t enough to make a decision. Some days are sunny, some are gloomy, and prices bounce around. So we generate <strong>32 sample days</strong>, each with its own mix of prices, demand and sunshine, and judge each battery size by its average performance across all of them.</p>
<p>That means every time we want to test a battery size, we have to solve 32 operating puzzles. That could get slow.</p>
</section>
<section id="the-bulk-discount-twist" class="level2">
<h2 class="anchored" data-anchor-id="the-bulk-discount-twist">The bulk-discount twist</h2>
<p>There’s one more wrinkle, and it’s the reason this example exists. Batteries enjoy <strong>economies of scale</strong>: a bigger battery costs more, but not proportionally more. In our model, doubling the battery’s size makes it about 1.74 times as expensive, not twice as expensive.</p>
<p>That sounds like good news for your wallet, and it is. But it’s bad news for the maths. If cost were perfectly proportional to size, the whole thing (sizing plus all 32 days) could be bundled into one giant linear program and solved directly, very quickly. The bulk discount bends the cost curve, and that bend breaks the neat linear structure. We need a different approach.</p>
</section>
<section id="feeling-for-the-slope" class="level2">
<h2 class="anchored" data-anchor-id="feeling-for-the-slope">Feeling for the slope</h2>
<p>Here is the trick. Imagine you’re standing on a hillside in thick fog, and you want to reach the bottom of the valley. You can’t see the bottom, but you <em>can</em> feel which way the ground slopes under your feet. So you take a step downhill, feel again, take another step, and so on. This is called <strong>gradient descent</strong>, and it’s one of the most widely used ideas in all of computing.</p>
<p>To use it, we need the slope: if we made the battery a tiny bit bigger, would the total cost go up or down, and by how much?</p>
<p>The lovely part is that the operating puzzles hand us this information <strong>for free</strong>. When a linear program finishes, its solution comes with extra numbers that say how much the best possible cost would change if each limit were loosened a little. One of those limits is “the battery can’t hold more than its capacity”, so the solver already knows how much a slightly bigger battery would have saved on that day. We don’t have to do any extra work to get the slope.</p>
<p><a href="https://github.com/BrendanVR/Jaddle">Jaddle</a> solves all 32 daily puzzles <strong>at once</strong>, in a single batch, collects the slopes, and combines them with the slope of the battery’s price. Then we take a step downhill and repeat. Forty steps later, we’ve arrived.</p>
</section>
<section id="the-answer" class="level2">
<h2 class="anchored" data-anchor-id="the-answer">The answer</h2>
<p>Across our 32 sample days:</p>
<ul>
<li>With <strong>no battery</strong>, the site spends about <strong>$6.02 per day</strong> on electricity.</li>
<li>With the battery that gradient descent found, the total daily cost (battery <em>and</em> bills) drops to about <strong>$3.83 per day</strong>.</li>
</ul>
<p>The best battery stores roughly <strong>14 to 15 kilowatt-hours</strong>.</p>
</section>
<section id="did-we-find-the-real-bottom" class="level2">
<h2 class="anchored" data-anchor-id="did-we-find-the-real-bottom">Did we find the real bottom?</h2>
<p>Because of that bulk-discount bend, the landscape isn’t a perfectly smooth bowl. Gradient descent always walks downhill, but it can get stuck in a small dip that isn’t the deepest point. And there’s no exact solver for this problem we can check against.</p>
<p>So we did the next best thing: we started again from a completely different place. The first run began with a tiny battery and grew it. The second began with an absurdly oversized battery and shrank it. Both hikers walked down through the fog and met in the same valley. Their costs agreed to within 0.03%.</p>
<p>Their battery sizes agreed slightly less closely (about 14.3 versus 14.6 kilowatt-hours). That’s not a problem; it tells us the valley floor is wide and flat. Near the best size, a little bigger or a little smaller makes almost no difference to the cost. That’s handy in real life, where batteries only come in certain sizes anyway.</p>
</section>
<section id="what-the-battery-actually-does" class="level2">
<h2 class="anchored" data-anchor-id="what-the-battery-actually-does">What the battery actually does</h2>
<p>If you look at the plan for the sunniest day, the battery behaves just as you’d hope. It fills up around midday when the solar panels are producing more than the building needs, then drains during the expensive evening peak, cutting the amount of power bought from the grid when prices are highest.</p>
</section>
<section id="the-takeaway" class="level2">
<h2 class="anchored" data-anchor-id="the-takeaway">The takeaway</h2>
<p>Big decisions often depend on lots of small ones. Here, a one-off purchase depends on thousands of hour-by-hour choices across many different days. By solving all the small problems together, and using the slopes they give away for free, we can steer the big decision step by step until it’s just right. Not too big, not too small.</p>


</section>

 ]]></description>
  <category>jaddle</category>
  <category>optimisation</category>
  <category>linear programming</category>
  <category>energy</category>
  <guid>https://brendanvr.github.io/jaddle-blog/posts/battery-sizing/battery-sizing.html</guid>
  <pubDate>Tue, 20 Oct 2026 16:00:00 GMT</pubDate>
  <media:content url="https://brendanvr.github.io/jaddle-blog/posts/battery-sizing/battery-sizing-logo.png" medium="image" type="image/png" height="144" width="144"/>
</item>
<item>
  <title>Ignoring the Fibbers</title>
  <dc:creator>Brendan van Rooyen</dc:creator>
  <link>https://brendanvr.github.io/jaddle-blog/posts/robust-kalman/robust-kalman.html</link>
  <description><![CDATA[ 





<p>Imagine you’re trying to follow a remote-controlled boat drifting around a pond, but you can’t see it directly. Instead, a friend on the bank shouts out its position every few seconds. Most of the time they’re pretty accurate, give or take a little. But your friend is easily distracted, and about one time in five they shout out something completely off, as if they’d glanced at a duck instead.</p>
<p>Can you still work out where the boat actually went?</p>
<section id="two-sources-of-information" class="level2">
<h2 class="anchored" data-anchor-id="two-sources-of-information">Two sources of information</h2>
<p>You have two things to go on:</p>
<ol type="1">
<li><strong>The measurements</strong>, your friend’s shouted positions. Most are close; some are wild.</li>
<li><strong>The physics.</strong> Boats don’t teleport. They glide along, speeding up and slowing down smoothly, nudged now and then by gusts of wind you can’t predict.</li>
</ol>
<p>The trick is to combine them. You’re looking for a path that <strong>obeys the physics</strong> and stays <strong>reasonably close to the measurements</strong>. If a measurement says the boat jumped five metres to the left in an instant, the physics says “that’s not possible”, and the path should mostly ignore it.</p>
<p>Finding the best such path, after the fact, using all the measurements at once, is called <strong>smoothing</strong>.</p>
</section>
<section id="the-classic-recipe-the-kalman-smoother" class="level2">
<h2 class="anchored" data-anchor-id="the-classic-recipe-the-kalman-smoother">The classic recipe: the Kalman smoother</h2>
<p>In the 1960s, Rudolf Kalman developed a beautifully efficient method for exactly this kind of problem. Versions of his ideas helped steer the Apollo spacecraft to the Moon, and today they run inside phones, cars, drones and countless other gadgets.</p>
<p>The classic approach chooses the path that keeps two things small at once:</p>
<ul>
<li>the <strong>wind gusts</strong> needed to explain the path (smaller gusts mean a more natural path), and</li>
<li>the <strong>misses</strong>, the gaps between the path and each measurement.</li>
</ul>
<p>To combine all the misses into one number, it <strong>squares</strong> each one and adds them up. Squaring makes sense when errors are small and well-behaved: it treats misses in either direction equally and is very convenient mathematically.</p>
</section>
<section id="the-problem-with-squaring" class="level2">
<h2 class="anchored" data-anchor-id="the-problem-with-squaring">The problem with squaring</h2>
<p>Squaring has a weakness. A miss that’s twice as big counts <em>four</em> times as much. A miss that’s twenty times as big counts <em>four hundred</em> times as much.</p>
<p>In our example, the occasional wild measurement is about twenty times noisier than a normal one. So when the classic smoother tries to keep the squared misses small, those few wild measurements dominate everything. They act like bullies, dragging the estimated path towards themselves, because ignoring them would be “too expensive”.</p>
<p>It’s a bit like a teacher averaging test scores where one student’s answer of “a million” for a question with answer “seven” drags the class average into the stratosphere.</p>
</section>
<section id="a-fairer-referee-the-huber-penalty" class="level2">
<h2 class="anchored" data-anchor-id="a-fairer-referee-the-huber-penalty">A fairer referee: the Huber penalty</h2>
<p>The fix is to change how misses are scored. Instead of squaring every miss, we use the <strong>Huber penalty</strong>, named after the statistician Peter Huber:</p>
<ul>
<li>For <strong>small misses</strong>, it behaves just like squaring. Normal, everyday noise is treated exactly as before.</li>
<li>For <strong>big misses</strong>, it switches to growing in a straight line. A miss twice as big costs only about twice as much, not four times.</li>
</ul>
<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">
<span class="screen-reader-only">Tip</span>A helpful way to picture it
</div>
</div>
<div class="callout-body-container callout-body">
<p>The squared penalty is like a rubber band that pulls harder and harder the further you stretch it. The Huber penalty is a rubber band that, beyond a certain point, stops getting stronger and just pulls with a steady force. Wild measurements still tug on the path, but they can no longer yank it off course.</p>
</div>
</div>
<p>This version is called the <strong>robust Kalman smoother</strong>. “Robust”, in statistics, means “doesn’t fall apart when some of the data is bad”.</p>
</section>
<section id="the-experiment" class="level2">
<h2 class="anchored" data-anchor-id="the-experiment">The experiment</h2>
<p>We simulate an object moving around a flat plane for 50 seconds, measured 1,000 times. It has a position and a speed, a little friction slowing it down, and random pushes knocking it around. Each measurement has a one-in-five chance of being an outlier.</p>
<p>Then we estimate the path twice: once with the classic smoother and once with the robust one.</p>
<p>The results are striking. The classic smoother’s path is visibly dragged around by the outliers, wandering off towards the wild measurements. The robust smoother’s path sticks closely to the true trajectory, as if it had simply noticed which shouts to take with a pinch of salt.</p>
</section>
<section id="what-jaddle-does-differently" class="level2">
<h2 class="anchored" data-anchor-id="what-jaddle-does-differently">What Jaddle does differently</h2>
<p>Both versions of the problem are written in <a href="https://www.cvxpy.org/">CVXPY</a>, a popular language for describing optimisation problems. In fact, this is one of CVXPY’s own showcase examples.</p>
<p>The classic smoother is a relatively simple kind of problem (a <em>quadratic program</em>), and <a href="https://github.com/BrendanVR/Jaddle">Jaddle</a> solves it directly.</p>
<p>The robust version is more interesting. Normally CVXPY would translate the Huber penalty into a bundle of cone-shaped rules, which works but makes the problem bigger and more complicated. Jaddle takes a different route: it keeps the Huber penalty as the smooth, gently curving function it really is, and solves the problem in that form. It also notices that the “miss” for each measurement is completely determined by the path, so it can drop those as separate unknowns and work with the path alone. Fewer moving parts, same answer.</p>
<p>We check both of Jaddle’s answers against Clarabel, a well-established solver that comes with CVXPY, and they agree.</p>
</section>
<section id="the-takeaway" class="level2">
<h2 class="anchored" data-anchor-id="the-takeaway">The takeaway</h2>
<p>Real-world data is rarely perfectly behaved. Sensors glitch, signals bounce, people mishear. A method that assumes every measurement is roughly right can be thrown badly off course by a handful that aren’t.</p>
<p>By changing one small thing, how we score big misses, the robust Kalman smoother learns to listen to the crowd without being bullied by the loudest fibbers. It’s a good lesson beyond boats and ponds: when you’re combining lots of evidence, make sure no single piece gets to shout over all the rest.</p>


</section>

 ]]></description>
  <category>jaddle</category>
  <category>optimisation</category>
  <category>estimation</category>
  <category>robust statistics</category>
  <guid>https://brendanvr.github.io/jaddle-blog/posts/robust-kalman/robust-kalman.html</guid>
  <pubDate>Tue, 20 Oct 2026 16:00:00 GMT</pubDate>
  <media:content url="https://brendanvr.github.io/jaddle-blog/posts/robust-kalman/robust-kalman-logo.png" medium="image" type="image/png" height="144" width="144"/>
</item>
<item>
  <title>Herding a Thousand Springy Robots</title>
  <dc:creator>Brendan van Rooyen</dc:creator>
  <link>https://brendanvr.github.io/jaddle-blog/posts/mpc-fleet/mpc-fleet.html</link>
  <description><![CDATA[ 





<p>Imagine four blocks sitting in a row on a slippery table, joined to each other by springs, with the two end blocks also attached by springs to walls. Give one a poke and the whole chain starts jiggling back and forth. Now add three little motors, one between each neighbouring pair of blocks, that can push them apart or pull them together. And just to make life interesting, every so often a random shove knocks the blocks around.</p>
<p>Your job is to keep the chain calm, and keep every block within a safe zone around its resting spot.</p>
<p>Now imagine doing that for <strong>a thousand chains at once</strong>.</p>
<section id="the-jiggling-blocks-system" class="level2">
<h2 class="anchored" data-anchor-id="the-jiggling-blocks-system">The jiggling-blocks system</h2>
<p>This setup comes from a well-known 2010 research paper by Yang Wang and Stephen Boyd, and it’s become a standard test for control methods. The rules are:</p>
<ul>
<li>Each motor has a <strong>strength limit</strong>. It can only push or pull so hard.</li>
<li>Each block should stay within a <strong>safe zone</strong> around its resting position.</li>
<li>We want the blocks to settle down without the motors working harder than necessary.</li>
</ul>
</section>
<section id="thinking-ahead-model-predictive-control" class="level2">
<h2 class="anchored" data-anchor-id="thinking-ahead-model-predictive-control">Thinking ahead: model predictive control</h2>
<p>How do you decide what the motors should do? One of the most powerful approaches is called <strong>model predictive control</strong>, or <strong>MPC</strong>.</p>
<p>MPC works a lot like a good chess player:</p>
<ol type="1">
<li><strong>Look at the board.</strong> Measure where every block is and how fast it’s moving.</li>
<li><strong>Think several moves ahead.</strong> Using a model of how springs and blocks behave, plan the best sequence of motor pushes for the next 15 steps (about seven and a half seconds).</li>
<li><strong>Make only the first move.</strong> Apply the first push in the plan.</li>
<li><strong>Look again and re-plan.</strong> The world never goes exactly as predicted (those random shoves!), so throw the old plan away and make a fresh one from the new position.</li>
</ol>
<p>This loop of plan, act, re-plan makes MPC both clever and adaptable. It’s used in self-driving cars, chemical plants, robots, drones and even some heating systems.</p>
<p>Each plan is a small optimisation puzzle called a <strong>quadratic program</strong>, with 105 numbers to choose and 120 rules to obey.</p>
</section>
<section id="soft-walls-instead-of-hard-ones" class="level2">
<h2 class="anchored" data-anchor-id="soft-walls-instead-of-hard-ones">Soft walls instead of hard ones</h2>
<p>There’s a subtle problem. Sometimes a random shove knocks a block so hard that <em>no</em> plan, however clever, can keep it inside its safe zone. If the rules said “the block must stay inside, no exceptions”, the puzzle would have no solution and the controller would be stuck.</p>
<p>So the safe zones are made <strong>soft</strong>. A block is allowed to stray outside, but the plan pays a hefty fine for every bit it goes over. The fine is large enough that whenever staying inside is possible, the plan does exactly that. It’s only when there’s no other choice that a block is allowed to go a little over, and even then the plan does its best to bring it back.</p>
<p>The motor limits, on the other hand, stay hard. A motor physically can’t push harder than its maximum, so the plan is never allowed to ask it to.</p>
</section>
<section id="one-at-a-time-versus-all-together" class="level2">
<h2 class="anchored" data-anchor-id="one-at-a-time-versus-all-together">One at a time versus all together</h2>
<p>A single one of these puzzles is tiny. A good solver running on an ordinary computer processor (CPU) can crack one in well under a thousandth of a second. That’s very hard to beat.</p>
<p>But we don’t have one system. We have a thousand, each starting from its own random position and getting its own random shoves. At every step, we need a thousand fresh plans.</p>
<p>Here’s where the two approaches differ:</p>
<ul>
<li><strong>The one-at-a-time approach</strong> solves the thousand puzzles one after another, using a well-known solver called SCS. It’s a bit like a single brilliant runner delivering a thousand letters, one trip at a time.</li>
<li><strong>The all-together approach</strong> uses <a href="https://github.com/BrendanVR/Jaddle">Jaddle</a> on a graphics card (GPU). All thousand chains have the same springs, motors and rules; only their positions differ. So Jaddle solves all thousand puzzles <strong>side by side, in lockstep</strong>, like a marching band where every musician takes the same step at the same time.</li>
</ul>
<p>Both approaches use a <strong>warm start</strong>: each new plan begins from the previous step’s plan, shifted forward by one step. Since the world hasn’t changed much in half a second, the old plan is usually a great starting guess.</p>
</section>
<section id="and-the-winner-is" class="level2">
<h2 class="anchored" data-anchor-id="and-the-winner-is">And the winner is…</h2>
<p>It depends on how many systems you have, which is the most interesting part of the whole example.</p>
<ul>
<li><strong>One system:</strong> the one-at-a-time solver wins. A single tiny puzzle leaves a GPU almost entirely idle; it spends most of its time just getting started.</li>
<li><strong>Tens of systems:</strong> the batched approach catches up.</li>
<li><strong>A thousand systems:</strong> the batched approach on a laptop GPU spends about <strong>one-fortieth</strong> as long per puzzle as the one-at-a-time solver, and its plans are slightly <em>more</em> accurate too. Even running the batched approach on an ordinary CPU, it’s about three times faster than looping.</li>
<li><strong>Ten thousand systems:</strong> something new happens…</li>
</ul>
</section>
<section id="the-slowest-member-of-the-band" class="level2">
<h2 class="anchored" data-anchor-id="the-slowest-member-of-the-band">The slowest member of the band</h2>
<p>Remember the marching band? The band only finishes when its <strong>slowest</strong> member finishes. In a fleet of ten thousand, there are always a few chains in a tricky spot, perhaps just shoved past the edge of their safe zone, and their puzzles take longer to solve. Everyone else ends up waiting for them.</p>
<p>The fix is something every real controller needs anyway: a <strong>deadline</strong>. We give the solver a fixed amount of effort per step. Any system that hasn’t fully finished by then simply uses the best plan it has so far. Because Jaddle always keeps its answers within the motor limits as it works, even an unfinished plan never asks a motor to do the impossible.</p>
<p>With a deadline in place, the time per puzzle for ten thousand systems drops back down near its best.</p>
</section>
<section id="do-they-agree" class="level2">
<h2 class="anchored" data-anchor-id="do-they-agree">Do they agree?</h2>
<p>Speed is no use if the answers are wrong. So we check both approaches against a reference answer solved to extremely high accuracy. Both are close, and over the whole run, the two fleets end up with nearly identical total costs. The blocks stay calm, the motors stay within their limits, and the chains settle down in both cases.</p>
</section>
<section id="the-takeaway" class="level2">
<h2 class="anchored" data-anchor-id="the-takeaway">The takeaway</h2>
<p>This example isn’t saying “always use a GPU”. If you’re controlling <strong>one</strong> robot and need an answer within a strict fraction of a millisecond, a traditional CPU solver is still the right tool.</p>
<p>But a surprising number of real problems involve lots of similar decisions at once: a fleet of delivery robots, a wind farm full of turbines, or testing one controller against thousands of possible futures to see how it copes. In those cases, it’s worth asking the marching-band question: <em>could all of these take the same step together?</em> When the answer is yes, solving them side by side can be dramatically faster than solving them one at a time.</p>


</section>

 ]]></description>
  <category>jaddle</category>
  <category>optimisation</category>
  <category>control</category>
  <category>GPU</category>
  <guid>https://brendanvr.github.io/jaddle-blog/posts/mpc-fleet/mpc-fleet.html</guid>
  <pubDate>Tue, 20 Oct 2026 16:00:00 GMT</pubDate>
  <media:content url="https://brendanvr.github.io/jaddle-blog/posts/mpc-fleet/mpc-fleet-logo.png" medium="image" type="image/png" height="144" width="144"/>
</item>
<item>
  <title>Teaching a Robot Banker to Play Fair</title>
  <dc:creator>Brendan van Rooyen</dc:creator>
  <link>https://brendanvr.github.io/jaddle-blog/posts/fair-logistic-regression/fair-logistic-regression.html</link>
  <description><![CDATA[ 





<p>Imagine a bank that wants to save time. Instead of a person reading every loan application, it wants a computer program to read them and say “yes” or “no”. To teach the program, the bank hands over a big pile of old decisions made by its human loan officers and says: <em>learn to do what they did.</em></p>
<p>That sounds sensible. But there is a catch. What if the old decisions were unfair?</p>
<section id="a-world-with-a-thumb-on-the-scale" class="level2">
<h2 class="anchored" data-anchor-id="a-world-with-a-thumb-on-the-scale">A world with a thumb on the scale</h2>
<p>In this example we build a small make-believe world so we can see exactly what is going on. There are applicants from two groups, which we’ll call <strong>Group A</strong> and <strong>Group B</strong>. About three in ten applicants belong to Group B.</p>
<p>Every applicant has a few “skill” scores, things like income stability or repayment history. Here is the important part: <strong>the skills are spread out in exactly the same way in both groups.</strong> Nobody in Group B is any less capable, on average, than anyone in Group A.</p>
<p>In a fair world, whether you get a loan depends only on those skills. But the historical decisions the bank kept came from a world with a thumb on the scale. Every Group B applicant had a fixed amount knocked off their chances, just for being in Group B.</p>
<p>So the pile of old decisions, the one we’re asking the computer to learn from, quietly contains a bias.</p>
</section>
<section id="just-dont-tell-it-the-group" class="level2">
<h2 class="anchored" data-anchor-id="just-dont-tell-it-the-group">“Just don’t tell it the group!”</h2>
<p>The first idea most people have is simple: don’t show the computer which group each applicant is in. If it can’t see the group, surely it can’t discriminate?</p>
<p>Sadly, it isn’t that easy. In our world, each applicant also has two extra details, a bit like a postcode, that happen to differ a little between the groups. They say nothing about whether someone will repay a loan. They are what statisticians call <strong>proxies</strong>: clues that let you guess the group without being told.</p>
<p>A computer trained to copy the old decisions is very good at spotting patterns. It notices that applicants with a certain kind of “postcode” were turned down more often, and it learns to lean on that clue. Sure enough, when we train a plain model, it puts a <em>negative</em> weight on those postcode-like features and approves Group B applicants far less often than Group A.</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">
<span class="screen-reader-only">Note</span>The lesson
</div>
</div>
<div class="callout-body-container callout-body">
<p>Hiding a sensitive attribute is not the same as being fair. If other information can stand in for it, a model can rebuild the bias on its own.</p>
</div>
</div>
</section>
<section id="how-the-robot-banker-thinks" class="level2">
<h2 class="anchored" data-anchor-id="how-the-robot-banker-thinks">How the robot banker thinks</h2>
<p>The model we use is called <strong>logistic regression</strong>, and it is one of the oldest and most trusted tools in machine learning. You can think of it as a points system:</p>
<ul>
<li>Each feature (each skill score, each postcode detail) earns or loses the applicant some points.</li>
<li>The points are added up into a single score.</li>
<li>If the score is above zero, the answer is “yes”. Below zero, “no”.</li>
</ul>
<p>“Learning” just means adjusting how many points each feature is worth until the model’s answers match the training data as closely as possible. Finding the best point values is an <strong>optimisation problem</strong>: a search for the settings that make some measure of error as small as it can be.</p>
</section>
<section id="adding-a-fairness-rule" class="level2">
<h2 class="anchored" data-anchor-id="adding-a-fairness-rule">Adding a fairness rule</h2>
<p>To push back against the bias, we give the optimiser an extra rule to obey while it learns. The rule we want is <strong>demographic parity</strong>: both groups should be approved at the same rate.</p>
<p>There is a small snag. Approval rates jump in steps: one applicant flips from “no” to “yes” and the rate suddenly hops. Optimisers prefer smooth hills to staircases. So, following a well-known approach by Zafar and colleagues (2017), we use a smoother stand-in for the rule:</p>
<blockquote class="blockquote">
<p>The model’s <em>average score</em> shouldn’t depend on which group you’re in.</p>
</blockquote>
<p>We also add a dial, which we’ll call the <strong>fairness bound</strong>. Turn the dial all the way up and there’s no rule at all: you get the plain, biased model. Turn it all the way down to zero and the two groups must have exactly the same average score. Anywhere in between is a compromise.</p>
</section>
<section id="what-we-found" class="level2">
<h2 class="anchored" data-anchor-id="what-we-found">What we found</h2>
<p>We trained two models: one with no fairness rule, and one with the dial at zero.</p>
<p>The fair model closes most of the gap in approval rates between the two groups. And something interesting happens to its accuracy:</p>
<ul>
<li>Measured against the <strong>biased historical decisions</strong>, the fair model looks <em>worse</em>. Of course it does: it is refusing to copy the bias.</li>
<li>Measured against the <strong>fair decisions</strong> (the ones that depend only on skill), the fair model looks <em>better</em>.</li>
</ul>
<p>In real life we would never get to see those “fair decisions”. We only know them here because we built the world ourselves. But it shows something important: if the bias lives in the labels rather than in the people, then “accuracy against the past” can be a misleading report card.</p>
</section>
<section id="the-whole-trade-off-in-one-go" class="level2">
<h2 class="anchored" data-anchor-id="the-whole-trade-off-in-one-go">The whole trade-off in one go</h2>
<p>One fair model and one unfair model only tell part of the story. What about all the settings in between?</p>
<p>We picked 40 different positions for the fairness dial and asked <a href="https://github.com/BrendanVR/Jaddle">Jaddle</a>, the optimisation library this example is built with, to train all 40 models <strong>at the same time</strong>, as one batch. That gives us a full curve showing how accuracy changes as the model becomes fairer, rather than a couple of dots.</p>
<p>Reading the curve from the unfair end to the fair end, accuracy against the biased labels slides down while accuracy against the fair labels climbs up.</p>
</section>
<section id="the-price-of-fairness" class="level2">
<h2 class="anchored" data-anchor-id="the-price-of-fairness">The price of fairness</h2>
<p>Here is my favourite part. When an optimiser obeys a rule, it can also tell you <strong>how much that rule is costing</strong>. Mathematicians call this a <em>dual variable</em> or <em>shadow price</em>. Think of it as a receipt that comes with each answer, saying: “if you tightened this rule by one more notch, the training error would go up by this much.”</p>
<p>For our fairness rule, that receipt is a <strong>price of fairness</strong>. We found that:</p>
<ul>
<li>When the dial is loose enough that the rule isn’t actually stopping anything, the price is zero. The rule is just along for the ride.</li>
<li>As the dial tightens, the price climbs steadily. Each extra step towards equal treatment costs a little more than the one before.</li>
</ul>
<p>That’s useful information for real decision-makers. Rather than arguing in the abstract about whether fairness is “worth it”, you can see the actual exchange rate and talk about it openly.</p>
</section>
<section id="a-few-footnotes" class="level2">
<h2 class="anchored" data-anchor-id="a-few-footnotes">A few footnotes</h2>
<p>Fairness is a deep and much-debated subject, and this example is a small, tidy illustration rather than a final answer.</p>
<ul>
<li><strong>Demographic parity is only one definition of fairness.</strong> Others ask for equal error rates, or equal treatment of similar individuals. They can disagree with each other, and choosing between them is a question about values, not just maths.</li>
<li><strong>Our world is made up.</strong> Real data is messier, and real bias is rarely a single fixed penalty.</li>
<li><strong>The rule we used is a stand-in.</strong> Making average scores equal tends to bring approval rates together, but it doesn’t guarantee they match exactly.</li>
</ul>
</section>
<section id="the-takeaway" class="level2">
<h2 class="anchored" data-anchor-id="the-takeaway">The takeaway</h2>
<p>A model trained to imitate the past will happily imitate the past’s mistakes, even when you hide the obvious clues. Adding a fairness rule to the optimisation is a direct, transparent way to push back. And because the optimiser hands you a price for every rule it follows, you can see exactly what that fairness is costing, and decide with your eyes open.</p>


</section>

 ]]></description>
  <category>jaddle</category>
  <category>optimisation</category>
  <category>machine learning</category>
  <category>fairness</category>
  <guid>https://brendanvr.github.io/jaddle-blog/posts/fair-logistic-regression/fair-logistic-regression.html</guid>
  <pubDate>Tue, 20 Oct 2026 16:00:00 GMT</pubDate>
  <media:content url="https://brendanvr.github.io/jaddle-blog/posts/fair-logistic-regression/fair-logistic-regression-logo.png" medium="image" type="image/png" height="144" width="144"/>
</item>
<item>
  <title>Max-Cut: The Art of Splitting a Room</title>
  <dc:creator>Brendan van Rooyen</dc:creator>
  <link>https://brendanvr.github.io/jaddle-blog/posts/max-cut/max-cut.html</link>
  <description><![CDATA[ 





<p>Imagine you’re organising a quiz night with 30 people, and you need to split them into two teams. There’s a complication: some pairs of people simply cannot stop bickering. You’ve drawn a map of every bickering pair. Your goal is to put as many of those pairs as possible on <em>opposite</em> teams, so they’re too busy competing to argue with each other.</p>
<p>This puzzle has a name, <strong>Max-Cut</strong>, and it is famously, stubbornly hard.</p>
<section id="drawing-the-problem" class="level2">
<h2 class="anchored" data-anchor-id="drawing-the-problem">Drawing the problem</h2>
<p>Mathematicians picture this as a <strong>graph</strong>: a set of dots (the people) joined by lines (the bickering pairs). Splitting the people into two teams is like drawing a line across the page. Every connection that crosses your line counts as “cut”, and you want to cut as many as possible.</p>
<p>In our example there are 30 people, and each pair has a 30% chance of being a bickering pair. That gives a graph with 119 connections.</p>
</section>
<section id="why-its-hard" class="level2">
<h2 class="anchored" data-anchor-id="why-its-hard">Why it’s hard</h2>
<p>With 30 people, each person can go on one of two teams, so there are 2 × 2 × 2 × … (thirty times) possible splits. That’s over a <strong>billion</strong> options. Add one more person and the number doubles. Add ten more and it’s a thousand times bigger.</p>
<p>Checking every option is hopeless for big graphs, and as far as anyone knows, there’s no clever shortcut that always finds the perfect answer quickly. Max-Cut belongs to a family of problems called <strong>NP-hard</strong>, which is computer-science speak for “nobody knows how to solve these fast, and many people suspect nobody ever will.”</p>
<p>So we settle for the next best thing: an answer that’s provably <em>close</em> to perfect.</p>
</section>
<section id="the-trick-pretend-its-easier" class="level2">
<h2 class="anchored" data-anchor-id="the-trick-pretend-its-easier">The trick: pretend it’s easier</h2>
<p>In 1995, Michel Goemans and David Williamson came up with a beautiful idea.</p>
<p>In the real problem, each person has to pick a team. You could picture each person holding a compass needle that must point either straight <strong>north</strong> (Team North) or straight <strong>south</strong> (Team South). Bickering pairs want their needles pointing in opposite directions.</p>
<p>Now let’s loosen the rules. Instead of only north or south, each person’s needle can point in <strong>any direction at all</strong>, not just on a flat map but in a space with many dimensions. Bickering pairs still want their needles to point as far apart as possible, but now there’s a whole sphere of in-between options.</p>
<p>This loosened version is called a <strong>relaxation</strong>, and it has two wonderful properties:</p>
<ol type="1">
<li><strong>It’s much easier to solve.</strong> The relaxed problem is a type of puzzle called a <em>semidefinite program</em>, which, unlike the original, can be solved efficiently and exactly.</li>
<li><strong>Its answer is a ceiling.</strong> Since every real split is also allowed in the relaxed version (north and south are still valid directions), the relaxed problem can only do better than or equal to the real one. So its score tells us: <em>no split can possibly cut more than this.</em></li>
</ol>
</section>
<section id="back-to-reality-the-random-slice" class="level2">
<h2 class="anchored" data-anchor-id="back-to-reality-the-random-slice">Back to reality: the random slice</h2>
<p>Of course, people can’t actually join “Team Slightly North-East-Up”. We need to turn the needle directions back into two teams.</p>
<p>Goemans and Williamson’s answer is delightfully simple: <strong>slice the space with a random flat sheet.</strong> Everyone whose needle points to one side of the sheet goes on one team; everyone on the other side goes on the other.</p>
<p>Why does this work? Bickering pairs ended up with needles pointing far apart. The further apart two needles point, the more likely a random sheet will pass between them, putting them on opposite teams. Pairs who got along fine ended up with needles close together, so they’ll usually stay on the same side.</p>
<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">
<span class="screen-reader-only">Tip</span>The famous guarantee
</div>
</div>
<div class="callout-body-container callout-body">
<p>Goemans and Williamson proved that, on average, a random slice cuts at least <strong>87.8%</strong> as many connections as the ceiling. Since the ceiling is at least as good as the best possible split, the slice is guaranteed to be close to optimal, without ever finding the optimum.</p>
</div>
</div>
<p>That was a landmark result. For decades, nobody has found an efficient method with a better guarantee, and there are good theoretical reasons to believe nobody can.</p>
<p>In our example, we try 100 random slices and keep the best one. It comfortably clears the 87.8% bar.</p>
</section>
<section id="a-certificate-of-quality" class="level2">
<h2 class="anchored" data-anchor-id="a-certificate-of-quality">A certificate of quality</h2>
<p>There’s one more neat feature. When the solver finishes the relaxed problem, it also produces a set of numbers, one per person, called <strong>dual values</strong>. Add them all up and you get exactly the same ceiling, approached from the opposite direction.</p>
<p>This acts like a <strong>certificate</strong>. Anyone can take those numbers and check, without re-solving anything, that no split could ever beat the ceiling. You don’t have to trust the solver; you can verify its homework.</p>
</section>
<section id="how-jaddle-solves-it" class="level2">
<h2 class="anchored" data-anchor-id="how-jaddle-solves-it">How Jaddle solves it</h2>
<p>The tricky bit of the relaxed problem is a rule saying the needle directions must “fit together” consistently. (In technical terms, a certain table of numbers must be <em>positive semidefinite</em>.) <a href="https://github.com/BrendanVR/Jaddle">Jaddle</a> handles this by taking small steps towards a better answer and, after each step, nudging the result back into the set of answers that fit together.</p>
<p>The example writes the problem in <a href="https://www.cvxpy.org/">CVXPY</a>, a popular language for describing optimisation problems, and hands it to Jaddle to solve. We double-check Jaddle’s ceiling against SCS, another well-established solver, and they agree.</p>
</section>
<section id="the-takeaway" class="level2">
<h2 class="anchored" data-anchor-id="the-takeaway">The takeaway</h2>
<p>When a problem is too hard to solve exactly, you don’t have to give up. You can:</p>
<ol type="1">
<li><strong>Relax</strong> it into an easier problem that gives you a ceiling;</li>
<li><strong>Solve</strong> the easy version;</li>
<li><strong>Round</strong> its answer back into a real solution;</li>
<li><strong>Compare</strong> that solution to the ceiling to know exactly how good it is.</li>
</ol>
<p>This “relax and round” recipe shows up all over computer science, from scheduling to circuit design to clustering data. And it all started with a clever question: <em>what if the needles could point anywhere?</em></p>


</section>

 ]]></description>
  <category>jaddle</category>
  <category>optimisation</category>
  <category>semidefinite programming</category>
  <category>graphs</category>
  <guid>https://brendanvr.github.io/jaddle-blog/posts/max-cut/max-cut.html</guid>
  <pubDate>Tue, 20 Oct 2026 16:00:00 GMT</pubDate>
  <media:content url="https://brendanvr.github.io/jaddle-blog/posts/max-cut/max-cut-logo.png" medium="image" type="image/png" height="144" width="144"/>
</item>
<item>
  <title>A Gentle Tour of Saddle Point Solvers</title>
  <dc:creator>Brendan van Rooyen</dc:creator>
  <link>https://brendanvr.github.io/jaddle-blog/posts/saddle-point-solvers/saddle-point-solvers.html</link>
  <description><![CDATA[ 





<p>Many of the hardest problems in machine learning and engineering are optimisation problems. Sometimes there’s a catch: you want the cheapest design, but it must satisfy certain rules. Saddle point solvers are one of the most elegant ways to handle that catch, and the path to understanding them is a story about turning rules into a game.</p>
<section id="step-1-turn-the-rules-into-a-fight" class="level2">
<h2 class="anchored" data-anchor-id="step-1-turn-the-rules-into-a-fight">Step 1: Turn the rules into a fight</h2>
<p>Suppose you want to minimise a cost, but you must obey a rule. A classic trick is to add a <em>penalty</em> for breaking the rule and let a second player decide how big that penalty should be.</p>
<p>Call the first player X, who is trying to make the cost as small as possible. Call the second player Y, who is trying to make the penalty as large as possible whenever the rule is broken. Y is a bit of a troublemaker. If X breaks a rule, Y pounces and charges X for it.</p>
<p>Mathematicians call this <em>dualising</em> the constraints. A problem that said, “minimise the cost, subject to the rules” becomes “X minimises, while Y maximises, a combined score.” The problem has gone from a minimisation to a <strong>min-max</strong>.</p>
</section>
<section id="step-2-meet-the-two-player-game" class="level2">
<h2 class="anchored" data-anchor-id="step-2-meet-the-two-player-game">Step 2: Meet the two-player game</h2>
<p>What we’ve built is a two-player zero-sum game. One player’s gain is exactly the other’s loss. X wants the score low, Y wants it high, and neither can simply walk away.</p>
<p>Picture a tug-of-war on a seesaw. Every move X makes pushes the score up or down, and every move Y makes pushes it back. Neither player is trying to be nice.</p>
</section>
<section id="step-3-the-sweet-spot-is-a-nash-equilibrium" class="level2">
<h2 class="anchored" data-anchor-id="step-3-the-sweet-spot-is-a-nash-equilibrium">Step 3: The sweet spot is a Nash equilibrium</h2>
<p>In game theory, a <em>Nash equilibrium</em> is a situation where neither player can improve their outcome by changing their move alone. Each player is doing the best they can, given what the other is doing.</p>
<p>In our min-max game, this equilibrium has a lovely geometric name: a <strong>saddle point</strong>. Think of a horse’s saddle. Walk along it from front to back and you’re climbing up; walk side to side and you’re going down. The saddle point is the spot that is the lowest in one direction and the highest in the other at the same time.</p>
<p>That is exactly the answer to our original problem. The saddle point gives us the best design that also obeys the rules.</p>
</section>
<section id="step-4-let-the-players-learn-from-each-other" class="level2">
<h2 class="anchored" data-anchor-id="step-4-let-the-players-learn-from-each-other">Step 4: Let the players learn from each other</h2>
<p>How do we actually find the saddle point? One approach is to let both players learn as they play. Each player watches what the other has been doing and adjusts their strategy accordingly.</p>
<p>The simplest version is called <em>gradient descent-ascent</em>. X takes a small step downhill on the score, Y takes a small step uphill, and both repeat. It is like two hikers feeling their way across a foggy landscape, one trying to get lower and the other trying to get higher, each reacting to where the other has just stepped.</p>
</section>
<section id="step-5-the-twist-and-the-magic" class="level2">
<h2 class="anchored" data-anchor-id="step-5-the-twist-and-the-magic">Step 5: The twist (and the magic)</h2>
<p>Here is the honest bit. In general, these learning dynamics <strong>don’t converge</strong>. In some games, the players chase each other in circles, or their steps grow larger and the game spirals away from the answer. Two stubborn players reacting instantly to each other are a recipe for wobbling.</p>
<p>But then something rather magical happens. If you stop looking at each individual move and instead track the <strong>long-running average</strong> of what each player has done, the averages settle down. The wobbles cancel out, and the averaged strategies drift steadily towards the saddle point, the Nash equilibrium, the answer we were looking for.</p>
</section>
<section id="why-it-matters" class="level2">
<h2 class="anchored" data-anchor-id="why-it-matters">Why it matters</h2>
<p>Saddle point problems appear everywhere: training generative adversarial networks, robust control under uncertainty, fair resource allocation, and constrained learning in general. The core lesson is delightfully simple. When two opposing learners keep playing, don’t trust any single moment. Trust the average over time.</p>
</section>
<section id="from-idea-to-engine-jaddle" class="level2">
<h2 class="anchored" data-anchor-id="from-idea-to-engine-jaddle">From Idea to Engine: Jaddle</h2>
<p>Everything above is a story about ideas: turn the rules into a fight, find the calm point where neither player wants to move, let both learners adapt, and trust the long-run average. Turning those ideas into code that actually runs fast is what <strong>Jaddle</strong> is for.</p>
<p>Jaddle (short for <em>JAX Saddle Point Solver</em>) is a library built on <a href="https://github.com/jax-ml/jax"><strong>JAX</strong></a>, Google’s numerical computing library. It takes the two-player picture seriously. You describe the game, one player minimising and one maximising, and Jaddle runs the learning dynamics for you.</p>
<p>Here’s how the story maps onto the library:</p>
<ul>
<li><strong>Dualising the constraints</strong> becomes a function you write: the cost, plus the penalised constraints, as one combined score.</li>
<li><strong>The two-player game</strong> is the object Jaddle solves. Each player’s update is computed from the gradient of that shared score, so you never hand-derive the learning rules.</li>
<li><strong>The Nash equilibrium</strong> is the output you care about: the saddle point, the pair of strategies where neither player gains by moving alone.</li>
<li><strong>Learning from the other player</strong> is the iterative update loop, with both players stepping at once and reacting to each other.</li>
<li><strong>The long-running average</strong> is where the magic from the last section lives in practice.</li>
</ul>
<p>JAX makes this practical. Automatic differentiation supplies the gradients, jit compiles the update step so it runs at speed, and vmap lets many problems run side by side. The same code runs on CPU, GPU, or TPU without rewriting.</p>
<p>So if the earlier sections gave you the intuition, Jaddle is the workshop where you put it to use. Try it on a small game first, watch the iterates wobble, then watch the averages settle. That moment is worth seeing for yourself.</p>


</section>

 ]]></description>
  <category>jaddle</category>
  <category>optimisation</category>
  <category>machine learning</category>
  <guid>https://brendanvr.github.io/jaddle-blog/posts/saddle-point-solvers/saddle-point-solvers.html</guid>
  <pubDate>Tue, 20 Oct 2026 16:00:00 GMT</pubDate>
  <media:content url="https://brendanvr.github.io/jaddle-blog/posts/saddle-point-solvers/saddle-point-solvers-logo.svg" medium="image" type="image/svg+xml"/>
</item>
<item>
  <title>Small Problems? Use Clarabel. Big Problems? Use Jaddle.</title>
  <dc:creator>Brendan van Rooyen</dc:creator>
  <link>https://brendanvr.github.io/jaddle-blog/posts/clarabel-vs-jaddle/clarabel-vs-jaddle.html</link>
  <description><![CDATA[ 





<p>Imagine you need to get across town. You could take one expensive sports car that makes the trip in a few careful, powerful moves. Or you could take a bus that makes lots of small, cheap stops, one after another. For one passenger, the sports car wins. For a thousand passengers, the bus is the only sensible choice.</p>
<p>Optimisation solvers face the same choice. Some solvers make a small number of very careful, expensive steps. Others make many quick, cheap steps. Which one wins depends on the size of the problem, and it’s a lesson I keep running into across my recent posts.</p>
<section id="two-ways-to-climb-a-hill" class="level2">
<h2 class="anchored" data-anchor-id="two-ways-to-climb-a-hill">Two ways to climb a hill</h2>
<p>Finding the best answer to an optimisation problem is a bit like climbing a hill in thick fog. You can’t see the top, but you can feel which way is uphill, and you keep moving that way.</p>
<p>Some solvers, such as <strong>Clarabel</strong>, take careful steps. Each step is expensive: the solver works out a lot about the shape of the hill around it, which means solving a sizeable calculation. Because each step is so well-informed, you need only a few of them to reach the top.</p>
<p><strong>Jaddle</strong> takes a different approach. Its steps are cheap. Each one is a handful of quick sums, the sort of arithmetic a computer can repeat at great speed. You need many more steps to reach the top, but each one costs very little.</p>
</section>
<section id="why-many-cheap-steps-can-win" class="level2">
<h2 class="anchored" data-anchor-id="why-many-cheap-steps-can-win">Why many cheap steps can win</h2>
<p>It sounds backwards. Surely a few smart steps should beat lots of dumb ones? Not always. Here’s the catch: the cost of one careful step grows quickly as the problem gets bigger. Double the size of the problem and a careful step can cost several times as much. A cheap step grows much more gently. Eventually the careful solver spends so long on each step that the cheap solver, taking far more steps, still arrives first.</p>
<p>There’s a second benefit too. Cheap steps are easy to run in parallel. A graphics card, for example, is built to do thousands of simple sums at the same time. Lots of small, similar steps fit that kind of hardware beautifully.</p>
</section>
<section id="the-evidence" class="level2">
<h2 class="anchored" data-anchor-id="the-evidence">The evidence</h2>
<p>I’ve tried this on several problems in the series, and the picture is consistent.</p>
<p><strong>Small problems: the careful solver wins.</strong> When I ran a <a href="../../posts/robust-kalman/robust-kalman.html">robust Kalman filter</a>, which tracks a moving object through noisy and sometimes wrong measurements, Clarabel finished in a fraction of a second. Jaddle took a few seconds. On a 30-node version of the network-splitting puzzle, the same story held: SCS was far quicker. The problems were small enough that the cheap steps didn’t have enough work to add up.</p>
<p><strong>Big problems: the cheap solver wins.</strong> On a network with 1,000 nodes and about 150,000 links, which means a grid of a million numbers to arrange, SCS took about 126 seconds. Jaddle finished in about 18 seconds, roughly seven times faster, and both reached the same answer.</p>
<p><strong>Many problems at once: the cheap solver wins.</strong> When I controlled a <a href="../../posts/mpc-fleet/mpc-fleet.html">fleet of 1,000 small machines</a>, each one needed its own plan every moment. Solving them one at a time with SCS took about 420 microseconds per plan. Jaddle, solving all the plans together in one batch, took about 10 microseconds each, around forty times faster on a laptop graphics card. Even on an ordinary processor, the batched version was about three times faster than the one-at-a-time approach.</p>
</section>
<section id="the-honest-bit" class="level2">
<h2 class="anchored" data-anchor-id="the-honest-bit">The honest bit</h2>
<p>This is not a magic trick, and the crossover is real. On a single small problem, the careful solver is usually the right choice, and I’d rather tell you that than sell you something you don’t need. Jaddle’s advantage shows up when problems are large, or when you have many of them to solve together. If you’re solving one tiny problem a week, use the specialist tool.</p>
</section>
<section id="a-quick-decision-guide" class="level2">
<h2 class="anchored" data-anchor-id="a-quick-decision-guide">A quick decision guide</h2>
<ul>
<li><strong>One small problem, needed occasionally:</strong> use Clarabel.</li>
<li><strong>One large problem:</strong> try Jaddle, and compare it against Clarabel on your own data.</li>
<li><strong>Many similar problems at once:</strong> Jaddle, especially batched on a GPU.</li>
<li><strong>You need a high-accuracy answer fast on a tiny problem:</strong> Clarabel. Its careful steps pay off when precision matters more than throughput.</li>
</ul>
</section>
<section id="the-takeaway" class="level2">
<h2 class="anchored" data-anchor-id="the-takeaway">The takeaway</h2>
<p>When you choose a solver, don’t ask only which one is cleverest. Ask how big your problem is, how many you need to solve, and whether a crowd of cheap steps can beat a small team of expensive ones. For small problems, use Clarabel. For big ones, lots of cheap steps often win, and Jaddle is built around exactly that idea.</p>


</section>

 ]]></description>
  <category>jaddle</category>
  <category>optimisation</category>
  <category>Clarabel</category>
  <category>GPU</category>
  <guid>https://brendanvr.github.io/jaddle-blog/posts/clarabel-vs-jaddle/clarabel-vs-jaddle.html</guid>
  <pubDate>Tue, 20 Oct 2026 16:00:00 GMT</pubDate>
  <media:content url="https://brendanvr.github.io/jaddle-blog/posts/clarabel-vs-jaddle/clarabel-vs-jaddle-logo.svg" medium="image" type="image/svg+xml"/>
</item>
</channel>
</rss>
