<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="http://pepijndevos.nl/feed.xml" rel="self" type="application/atom+xml" /><link href="http://pepijndevos.nl/" rel="alternate" type="text/html" /><updated>2026-02-17T14:10:32+00:00</updated><id>http://pepijndevos.nl/feed.xml</id><title type="html">Wishful Coding</title><entry><title type="html">Hardware in the Loop Continuous Integration for FPGA tools</title><link href="http://pepijndevos.nl/2026/02/17/hardware-in-the-loop-continuous-integration-for-fpga-tools.html" rel="alternate" type="text/html" title="Hardware in the Loop Continuous Integration for FPGA tools" /><published>2026-02-17T00:00:00+00:00</published><updated>2026-02-17T00:00:00+00:00</updated><id>http://pepijndevos.nl/2026/02/17/hardware-in-the-loop-continuous-integration-for-fpga-tools</id><content type="html" xml:base="http://pepijndevos.nl/2026/02/17/hardware-in-the-loop-continuous-integration-for-fpga-tools.html"><![CDATA[<p>Imagine you are the maintainer of an open source FPGA toolchain such as <a href="https://github.com/YosysHQ/apicula">Apicula</a> and you want to make sure your code works. The industry standard for making sure software works is to set up continuous integration that tests your code for every change you make.</p>

<p>But what do you test? We are reverse engineering the inner workings of complicated devices, and if you’re slightly wrong you can generate a good looking bitstream that just doesn’t work. So of course during development you test your bitstreams on real hardware to make sure they work. And on top of that we <em>do</em> set up CI, but it can only test that we can generate a bitstream.</p>

<p>This is not idle thought, just <a href="https://github.com/YosysHQ/apicula/pull/462">yesterday</a> we had one FPGA silently fail due to improvements in another. It is very hard to catch these regressions reliably without constantly manually testing every single devices, which is completely infeasible.</p>

<p>For a long time I’ve dreamed of setting up a raspberry pi with a bunch of FPGAs and somehow running automated tests on it. But it always felt like a huge ordeal to really put everything together. Running and maintaining a raspberry pi with a bunch of FPGAs, writing useful self-tests that automatically verify correct behaviour, triggering a test run, downloading or generating bitstreams, programming the FPGAs, obtaining and verifying the results, and communicating that back to Github.</p>

<p>But this week I looked at the problem again and realized all the pieces had falling into place to make this not only feasible but almost trivial.</p>

<ol>
  <li>I am already running a Raspberry Pi with Home Assistant on my very own Mini-ITX motherboard: <a href="https://www.crowdsupply.com/sanctuary-systems/sentinel-core">Sentinel Core</a></li>
  <li>For running LLMs on my pi, I built <a href="https://github.com/sanctuary-systems-com/llm-addons">custom Docker addons</a> for Home Assistant</li>
  <li>To build those Docker containers, I’m using a Github <a href="https://docs.github.com/en/actions/concepts/runners/self-hosted-runners">self-hosted runner</a> on my VPS</li>
  <li>We have <a href="https://github.com/YosysHQ/apicula/blob/master/examples/femto-riscv-18.v">femto RISC-V UART</a> examples now based on Bruno Levy’s <a href="https://github.com/BrunoLevy/learn-fpga">FPGA tutorials</a></li>
</ol>

<p>So the plan is simple, plug some FPGAs into Home Assistant, run a self-hosted runner as an addon, and add a CI task that uploads an example and verifies its UART output against a reference.</p>

<p>For the addon I just forked an existing addon and added USB access and openFPGALoader: <a href="https://github.com/pepijndevos/home-assistant-github-runner-add-on">home-assistant-github-runner-add-on</a></p>

<p>For the CI side, it basically just adds a self-hosted step after our current bitstream generation that fetches the bitstream artifacts, uploads them, and diffs the UART output: <a href="https://github.com/YosysHQ/apicula/pull/461">Add hardware-in-the-loop CI test infrastructure</a></p>

<p>I’m excited to bring this new level of reliability to Apicula, and curious if other projects could do something similar.</p>]]></content><author><name></name></author><category term="fpga" /><summary type="html"><![CDATA[Imagine you are the maintainer of an open source FPGA toolchain such as Apicula and you want to make sure your code works. The industry standard for making sure software works is to set up continuous integration that tests your code for every change you make.]]></summary></entry><entry><title type="html">Playing Mini Byzantine Newcombs Problem</title><link href="http://pepijndevos.nl/2025/09/22/playing-mini-byzantine-newcombs-problem.html" rel="alternate" type="text/html" title="Playing Mini Byzantine Newcombs Problem" /><published>2025-09-22T00:00:00+00:00</published><updated>2025-09-22T00:00:00+00:00</updated><id>http://pepijndevos.nl/2025/09/22/playing-mini-byzantine-newcombs-problem</id><content type="html" xml:base="http://pepijndevos.nl/2025/09/22/playing-mini-byzantine-newcombs-problem.html"><![CDATA[<p>It seems that people on my part of Twitter, in particular the rationalists, are very into Newcomb’s problem, probably because their guru wrote about it, so they all believe you should pick one box.</p>

<p>So I thought it would be a fun idea to take two boxes and some money to Treeweek 2 and try to actually play Newcombs Problem.</p>

<h3 id="rules-of-byzantine-mini-newcombs-problem">Rules of Byzantine Mini Newcomb’s problem</h3>

<p>This is a thought experiment made real, as a thought experiment about what it means to really play this game. What’s up with the name? Mini: I am not made of money so we are playing at reduced stakes and on a donation basis. Byzantine: How do you tell Omega from a charlatan? How reliable are my predictions really? Are you really precommitted? If I know that you know that I know that you know that…</p>

<h4 id="the-rules">The Rules</h4>
<ul>
  <li>Think carefully about the nature of the game and your strategy, this is the most important step!!</li>
  <li>Give me your (nick)name, twitter handle, and a <em>minimum</em> donation of €10</li>
  <li>I give this information to my reliable predictor and prepare the boxes accordingly.</li>
  <li>You will be offered two boxes, a transparent one containing €10 and an opaque one that contains either €100 (if I predict that you will only choose the opaque box), or €0 (if I predict that you will take both boxes).</li>
  <li>You take any money from your chosen box(es) and reflect on what happened. Any money in any remaining boxes goes back to the bank.</li>
</ul>

<h4 id="byrules">Byrules</h4>
<p>To keep it fun for everyone:</p>

<ul>
  <li>Don’t donate more than you’re willing to lose.</li>
  <li>Officially you can only play once, but if you ask nicely maybe you can go again.</li>
  <li>Anyone can choose to join the “ethical review board” and I’ll explain my whole spiel but then you can’t play or leak info.</li>
  <li>At the end of the week all will be revealed, and if you wrote down your strategy we can compare notes.</li>
</ul>

<h3 id="my-reasoning-behind-all-of-this">My reasoning behind all of this</h3>

<p>First of all, why “mini byzantine”? I would argue it is the only version that can be played.</p>

<p>First of all. There is no scenario where I trust someone who claims to be an alien in posession of millions of earthly USD and giving it to me. In the real world you’re always dealing with the question of who is this guy, why is he giving me money, and how can he possibly know my choice?</p>

<p>There are only two types of scenarios where you are offered large sums of money: scams and games of chance like a lottery or casino. And both of them make money rather than lose it, by asking for a fee.</p>

<p>You could run this game like a scam or game of chance where there is just a vanishingly small chance there is anything in the opaque box, but that’s not really in the spirit of the problem is it?</p>

<p>Although you could argue that a predictor that reliably predicts two boxes could be argued to be a logical if not ethical solution to the problem. “this is obviously a scam so I should take two boxes” as a sort of self fulfilling prophecy.</p>

<p>So lets impose that this is a zero sum game where I’m neither scamming you out of your money or just giving away money for free, which is the only fair and reasonable way to play.</p>

<p>The introduction of a donation actually meaningfully alters the game under this constraint.</p>

<p>Your donation can be seen as the expected value you place in this game. If you donate 10 euro you obviously place low odds on there being anything in the opaque box, and should logically take two boxes to not lose money. While if you donate 100 euro you obviously expect there to be money in the opaque box that you intend to take. These are the two stable equilibrium.</p>

<p>If you donate 110 you expect to outsmart the predictor which is irrational to singal, and if you donate something inbetween you’re not sure what to expect. So the question is, if your expected value/donation is 50 euro, what should the prediction be?</p>

<p>A reasonable choice in this case depends on the past donations. If people keep donating 50 euro I can’t just keep paying out 100, so we need to look at past donations and payouts compute our account balance and historical expected value. So in order for a logical prediction to be one box, it needs to be higher than or equal to the historical average payout, and the current account balance needs to actually be enough to fill the boxes. That can be easily implemented in an excel spreadsheet, which I did.</p>

<p>I’m really curious if we’ll just get stuck on the 10 euro equilibrium or a trickle of higher donations and mispredictions will lower the expected value low enough for a 100 euro payout to happen. Or will there be people with sufficient trust and money who have come to the same conclusion as me.</p>

<h3 id="my-predictor">My predictor</h3>

<p>I put the following equation in a spreadsheet that checks if your donation is more than the average payout, and the current account balance allows a payout.</p>

<p><code class="language-plaintext highlighter-rouge">=IF(AND(C2&gt;AVERAGE($E1:E$2), SUM($C$2:C2)-SUM($E1:E$2)&gt;=110), "one box", "two box")</code></p>

<h3 id="results">Results</h3>

<p>Based on the conversations I’ve had and the number of actual games played (0), we can conclude:</p>

<p>The only winning move is not to play.</p>]]></content><author><name></name></author><category term="rationalism" /><summary type="html"><![CDATA[It seems that people on my part of Twitter, in particular the rationalists, are very into Newcomb’s problem, probably because their guru wrote about it, so they all believe you should pick one box.]]></summary></entry><entry><title type="html">Bugasnoo: rock your baby to sleep with Lego</title><link href="http://pepijndevos.nl/2024/12/19/bugasnoo-rock-your-baby-to-sleep-with-lego.html" rel="alternate" type="text/html" title="Bugasnoo: rock your baby to sleep with Lego" /><published>2024-12-19T00:00:00+00:00</published><updated>2024-12-19T00:00:00+00:00</updated><id>http://pepijndevos.nl/2024/12/19/bugasnoo-rock-your-baby-to-sleep-with-lego</id><content type="html" xml:base="http://pepijndevos.nl/2024/12/19/bugasnoo-rock-your-baby-to-sleep-with-lego.html"><![CDATA[<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/bHtuO1WuFp8?si=ml7V0x8cl87AZmDZ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""> </iframe>

<p>I have recently become a dad, which is sometimes amazing, and sometimes suffering, such as when the baby is in a fussy mood and refusing to sleep. Some friends were very enthusiastic about a $1.5k smart crib that makes white noise and rocks your baby to sleep.</p>

<p>You know what also makes noise, rocks your baby to sleep, and doesn’t cost $1.5k? That’s right, this Lego model. This is already by far my most useful Lego creation, and we’re using it regularly to great effect.</p>

<p>At the core of this creation are two bogies, driven by Lego motors, strapped to the wheel of a stroller with rubber bands.
Each bogie is driven by two motors for a total of 4 driven wheels and 4 motors for maximum traction and power.</p>

<p>You could honestly get away with two motors and maybe gear them down 12:20. This design has excess torque and velocity and is limited by traction.
The traction is provided by the rubber bands, and kept straight with coaster wheels.</p>

<p>This particular design uses parts from the Lego Mindstorms Robot Inventor kit, which has been discontinued. It should however be possible to construct a similar model from Powered Up motors using the Technic Hub.</p>

<p><img src="/images/bugasnoo/IMG_20241219_104533_326.jpg" alt="instructions" />
<img src="/images/bugasnoo/IMG_20241219_104627_180.jpg" alt="instructions" />
<img src="/images/bugasnoo/IMG_20241219_104725_077.jpg" alt="instructions" />
<img src="/images/bugasnoo/IMG_20241219_104808_066.jpg" alt="instructions" />
<img src="/images/bugasnoo/IMG_20241219_104820_213.jpg" alt="instructions" />
<img src="/images/bugasnoo/IMG_20241219_104841_605.jpg" alt="instructions" />
<img src="/images/bugasnoo/IMG_20241219_104851_344.jpg" alt="instructions" />
<img src="/images/bugasnoo/IMG_20241219_104957_461.jpg" alt="instructions" /></p>

<p>ps: bugasnoo is obviously from buggy and snooze and not from any similar sounding trademarks ;)<br />
pps: I have considered turning this into a product but I’d need a cofounder who’s more into Industrial Design Engineering and business.</p>]]></content><author><name></name></author><category term="lego" /><category term="parenting" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Claude by the token in Open WebUI</title><link href="http://pepijndevos.nl/2024/12/12/claude-by-the-token-in-open-webui.html" rel="alternate" type="text/html" title="Claude by the token in Open WebUI" /><published>2024-12-12T00:00:00+00:00</published><updated>2024-12-12T00:00:00+00:00</updated><id>http://pepijndevos.nl/2024/12/12/claude-by-the-token-in-open-webui</id><content type="html" xml:base="http://pepijndevos.nl/2024/12/12/claude-by-the-token-in-open-webui.html"><![CDATA[<p>Last month I subscribed to Claude Pro, but was dismayed to learn it doesn’t give you API access to use it in VS Code or Home Assistant or whatever.
So I didn’t renew my subscription and instead bought API access, thinking I’d just use some chat app.
Turns out it’s not that easy to find a good chat app where you can just plug in your API token.</p>

<p>The solution I settled on is to use <a href="https://docs.litellm.ai/">LiteLLM</a> with <a href="https://docs.openwebui.com/">Open WebUI</a>.
Open WebUI is a great chat interface that is primarily used with <a href="https://ollama.com/">Ollama</a>, but it also supports OpenAI compatible APIs.
LiteLLM is a proxy that translates a ton of LLMs to a unified OpenAPI compatible API.
Badabing badaboom, give LiteLLM your Anthropic key, plug it into Open WebUI and bob’s your uncle.</p>

<p>It’s actually great if you are a very heavy or very casual user because you pay by the token.
That means if you use it only a little, it’s cheaper than Claude Pro, and if you use it a lot, you aren’t limited to a certain amount of messages.
Surprisingly it also does better RAG than Claude, letting you do web searches and include more and bigger documents than would fit in the context window.</p>

<p>Here is my Docker compose file to set it all up.
It is modified from <a href="https://github.com/mattcurf/ollama-intel-gpu">ollama-intel-gpu</a> to include LiteLLM with an <a href="https://docs.litellm.ai/docs/providers/anthropic#2-start-the-proxy">Anthropic config.yaml</a>.
But if you’re on team green or red, you can just change the first image to use <a href="https://hub.docker.com/r/ollama/ollama"><code class="language-plaintext highlighter-rouge">ollama/ollama</code></a> I suppose.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>services:
  ollama-intel-gpu:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: ollama-intel-gpu
    image: ollama-intel-gpu:latest
    restart: always
    devices:
      - /dev/dri:/dev/dri
    volumes:
      - ollama-intel-gpu:/root/.ollama
    ports:
      - "11434:11434"
  ollama-webui:
    image: ghcr.io/open-webui/open-webui:latest
    container_name: ollama-webui
    volumes:
      - ollama-webui:/app/backend/data
    depends_on:
      - ollama-intel-gpu
      - litellm
    ports:
      - ${OLLAMA_WEBUI_PORT-3000}:8080
    environment:
      - OLLAMA_BASE_URL=http://ollama-intel-gpu:11434
      - OPENAI_API_BASE_URL=http://litellm:4000
    extra_hosts:
      - host.docker.internal:host-gateway
    restart: always
  litellm:
    image: ghcr.io/berriai/litellm:main-latest
    container_name: litellm
    volumes:
      - ./litellm_config.yaml:/app/config.yaml
    ports:
      - 4000:4000
    environment:
      - ANTHROPIC_API_KEY=YOURKEYHERE
    restart: always
    command: --config /app/config.yaml
volumes:
  ollama-webui: {}
  ollama-intel-gpu: {}
</code></pre></div></div>]]></content><author><name></name></author><category term="machinelearning" /><summary type="html"><![CDATA[Last month I subscribed to Claude Pro, but was dismayed to learn it doesn’t give you API access to use it in VS Code or Home Assistant or whatever. So I didn’t renew my subscription and instead bought API access, thinking I’d just use some chat app. Turns out it’s not that easy to find a good chat app where you can just plug in your API token.]]></summary></entry><entry><title type="html">JPEG compress your LLM weights</title><link href="http://pepijndevos.nl/2024/12/01/jpeg-compress-your-llm-weights.html" rel="alternate" type="text/html" title="JPEG compress your LLM weights" /><published>2024-12-01T00:00:00+00:00</published><updated>2024-12-01T00:00:00+00:00</updated><id>http://pepijndevos.nl/2024/12/01/jpeg-compress-your-llm-weights</id><content type="html" xml:base="http://pepijndevos.nl/2024/12/01/jpeg-compress-your-llm-weights.html"><![CDATA[<p>So quantization is kinda bad lossy compression right? JPEG is good lossy compression.
This may sound stupid, and maybe it is, but hear me out.</p>

<p>I’ve read that LLM performance is usually constrained by memory bandwidth, and for us plebs also by memory size, and there is a precedent in for example <a href="https://www.zfshandbook.com/docs/advanced-zfs/compression/">ZFS compression</a> which has shown to <em>increase</em> disk performance because you’re IO constrained rather than compute constrained.
So it might be beneficial to decompress LLM parameters on the fly, and if you’re doing that you might want to use a good lossy compression algorithm instead of blunt quantization.
<a href="/2023/07/15/chatlmza.html">It is said that compression is equivalent to general intelligence</a>, so in that sense lossy compression would be expected to reduce intelligence, so you’d want to get a good compression ratio with minimal loss.</p>

<p>The way JPEG works is basically</p>
<ul>
  <li>break down the pixels in chunks - after decompression chunk boundaries are visible as JPEG artifacts.</li>
  <li>Discrete Cosine Transform them - lossless transformation in the family of Fourier transforms</li>
  <li>quantize them - data loss happens here, creating longer runs</li>
  <li>Run Length Encode them - compression happens here</li>
</ul>

<p>RLE is a lossless compression technique, which gets turbocharged by discarding some data to create longer runs.
In the case of image data, the DCT concentrates most information in the low frequencies so you can quantize high frequencies with minor loss in image quality.
Now, I don’t expect LLM parameters to be “smooth” like image data, so naive JPEG compression of LLM weights is not likely to be effective.</p>

<p>BUT!</p>

<p>You can reorder the columns and rows of a matrix without affecting the result. It’s like \(a+b+c=d \rightarrow c+b+a=d\).
So you could reorder your rows and columns to maximize clustering of similar values.
Not sure how you’d do this, maybe just sort by vector sum, or some genetic algorithm, or <a href="https://www.mathworks.com/help/matlab/math/sparse-matrix-reordering.html">other cleverness</a>.</p>

<p>So my proposed LLM compression would work like this</p>
<ul>
  <li>reorder the matrices to improve value clustering</li>
  <li>break down the values in chunks</li>
  <li>DCT them</li>
  <li>quantize them</li>
  <li>RLE them</li>
</ul>

<p>And then inference would</p>
<ul>
  <li>RLE expand a chunk</li>
  <li>inverse DCT it</li>
  <li>perform the multiplications</li>
</ul>

<p>So the compressed data would exist in VRAM and be decompressed on the fly chunk by chunk to perform a matrix vector product.
It’d take more compute, <a href="/2018/07/04/loefflers-discrete-cosine-transform-algorithm-in-futhark.html">11 multiplications to be precise</a>, but if you’re memory constrained it could be worth it.</p>

<p>I guess the real question is if you can obtain any useful clustering in LLM data.
In a sense the parameters are already compressed(=intelligence), but there is no information in their order, so reordering and transforming parameters could improve RLE compression without incurring extra quantization loss.</p>]]></content><author><name></name></author><category term="machinelearning" /><summary type="html"><![CDATA[So quantization is kinda bad lossy compression right? JPEG is good lossy compression. This may sound stupid, and maybe it is, but hear me out.]]></summary></entry><entry><title type="html">Backwards Game of Life</title><link href="http://pepijndevos.nl/2024/10/01/backwards-game-of-life.html" rel="alternate" type="text/html" title="Backwards Game of Life" /><published>2024-10-01T00:00:00+00:00</published><updated>2024-10-01T00:00:00+00:00</updated><id>http://pepijndevos.nl/2024/10/01/backwards-game-of-life</id><content type="html" xml:base="http://pepijndevos.nl/2024/10/01/backwards-game-of-life.html"><![CDATA[<p>I got a litlte bit nerd sniped by the following video and decided to implement game of life in clojure.core.logic, because any logic program can be evaluated forwards and backwards.</p>

<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/g8pjrVbdafY?si=AOnhIGwLQzy3MDaN" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe>

<p>Without further ado here is my implementation:</p>

<div class="language-clojure highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nf">ns</span><span class="w"> </span><span class="n">pepijndevos.lifeclj</span><span class="w">
  </span><span class="p">(</span><span class="no">:refer-clojure</span><span class="w"> </span><span class="no">:exclude</span><span class="w"> </span><span class="p">[</span><span class="nb">==</span><span class="p">])</span><span class="w">
  </span><span class="p">(</span><span class="no">:use</span><span class="w"> </span><span class="n">clojure.core.logic</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="no">:gen-class</span><span class="p">))</span><span class="w">

</span><span class="c1">;; A helper to get the neighbouring cells.</span><span class="w">
</span><span class="c1">;; Clips to zero.</span><span class="w">
</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">get-neighbours</span><span class="w"> </span><span class="p">[</span><span class="n">rows</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">for</span><span class="w"> </span><span class="p">[</span><span class="n">dx</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="mi">-1</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
        </span><span class="n">dy</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="mi">-1</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
        </span><span class="no">:when</span><span class="w"> </span><span class="p">(</span><span class="nb">not</span><span class="w"> </span><span class="p">(</span><span class="nb">=</span><span class="w"> </span><span class="n">dx</span><span class="w"> </span><span class="n">dy</span><span class="w"> </span><span class="mi">0</span><span class="p">))]</span><span class="w">
    </span><span class="p">(</span><span class="nf">get-in</span><span class="w"> </span><span class="n">rows</span><span class="w"> </span><span class="p">[(</span><span class="nb">+</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">dx</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="n">dy</span><span class="p">)]</span><span class="w"> </span><span class="mi">0</span><span class="p">)))</span><span class="w">

</span><span class="c1">;; Produces binary vectors of a certain number of bits.</span><span class="w">
</span><span class="c1">;; This is used to generate all neighbour combinations.</span><span class="w">
</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">bitrange</span><span class="w"> </span><span class="p">[</span><span class="n">n</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">sort-by</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="nb">+</span><span class="w"> </span><span class="n">%</span><span class="p">)</span><span class="w">
           </span><span class="p">(</span><span class="k">for</span><span class="w"> </span><span class="p">[</span><span class="n">i</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="p">(</span><span class="nb">bit-shift-left</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="n">n</span><span class="p">))]</span><span class="w">
             </span><span class="p">(</span><span class="nf">vec</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">bit-and</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">(</span><span class="nb">bit-shift-right</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="n">%</span><span class="p">))</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="n">n</span><span class="p">))))))</span><span class="w">

</span><span class="c1">;; Encode the game of life rules as a 256 element conde.</span><span class="w">
</span><span class="c1">;; Depending on the number of ones in a vector,</span><span class="w">
</span><span class="c1">;; the corresponding rule is generated</span><span class="w">
</span><span class="c1">;; that equates the pattern to the neigbours</span><span class="w">
</span><span class="c1">;; and the appropriate next state.</span><span class="w">
</span><span class="c1">;;</span><span class="w">
</span><span class="c1">;; This can be asked simply what the next state is for</span><span class="w">
</span><span class="c1">;; given neighbours and current state.</span><span class="w">
</span><span class="c1">;; OR you could drive it backwards any way you like.</span><span class="w">
</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">lifegoals</span><span class="w"> </span><span class="p">[</span><span class="n">neigh</span><span class="w"> </span><span class="n">self</span><span class="w"> </span><span class="nb">next</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">or*</span><span class="w">
   </span><span class="p">(</span><span class="k">for</span><span class="w"> </span><span class="p">[</span><span class="n">adj</span><span class="w"> </span><span class="p">(</span><span class="nf">bitrange</span><span class="w"> </span><span class="mi">8</span><span class="p">)</span><span class="w">
         </span><span class="no">:let</span><span class="w"> </span><span class="p">[</span><span class="n">n</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="nb">+</span><span class="w"> </span><span class="n">adj</span><span class="p">)]]</span><span class="w">
     </span><span class="p">(</span><span class="k">cond</span><span class="w">
       </span><span class="p">(</span><span class="nb">or</span><span class="w"> </span><span class="p">(</span><span class="nb">&lt;</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">&gt;</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="mi">3</span><span class="p">))</span><span class="w"> </span><span class="p">(</span><span class="nf">all</span><span class="w"> </span><span class="p">(</span><span class="nb">==</span><span class="w"> </span><span class="nb">next</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">==</span><span class="w"> </span><span class="n">neigh</span><span class="w"> </span><span class="n">adj</span><span class="p">))</span><span class="w">
       </span><span class="p">(</span><span class="nb">=</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w">              </span><span class="p">(</span><span class="nf">all</span><span class="w"> </span><span class="p">(</span><span class="nb">==</span><span class="w"> </span><span class="nb">next</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">==</span><span class="w"> </span><span class="n">neigh</span><span class="w"> </span><span class="n">adj</span><span class="p">))</span><span class="w">
       </span><span class="no">:else</span><span class="w">             </span><span class="p">(</span><span class="nf">all</span><span class="w"> </span><span class="p">(</span><span class="nb">==</span><span class="w"> </span><span class="nb">next</span><span class="w"> </span><span class="n">self</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">==</span><span class="w"> </span><span class="n">neigh</span><span class="w"> </span><span class="n">adj</span><span class="p">))))))</span><span class="w">

</span><span class="c1">;; Relate two grids to each other according to the above rules.</span><span class="w">
</span><span class="c1">;; Applies lifegoals to every cell and its neighbours.</span><span class="w">
</span><span class="c1">;; in the forwards direction executes one life step,</span><span class="w">
</span><span class="c1">;; in the backwards direction generates grids</span><span class="w">
</span><span class="c1">;; that would produce the next step.</span><span class="w">
</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">stepo</span><span class="w"> </span><span class="p">[</span><span class="n">size</span><span class="w"> </span><span class="n">vars</span><span class="w"> </span><span class="nb">next</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">rows</span><span class="w"> </span><span class="p">(</span><span class="nf">-&gt;&gt;</span><span class="w"> </span><span class="n">vars</span><span class="w"> </span><span class="p">(</span><span class="nf">partition</span><span class="w"> </span><span class="n">size</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="n">vec</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">into</span><span class="w"> </span><span class="p">[]))</span><span class="w">
        </span><span class="n">neig</span><span class="w"> </span><span class="p">(</span><span class="k">for</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="n">size</span><span class="p">)</span><span class="w">
                   </span><span class="n">y</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="n">size</span><span class="p">)]</span><span class="w">
               </span><span class="p">(</span><span class="nf">get-neighbours</span><span class="w"> </span><span class="n">rows</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="p">))]</span><span class="w">
    </span><span class="p">(</span><span class="nf">everyg</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">lifegoals</span><span class="w"> </span><span class="n">%</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="nb">vector</span><span class="w"> </span><span class="n">neig</span><span class="w"> </span><span class="n">vars</span><span class="w"> </span><span class="nb">next</span><span class="p">))))</span><span class="w">

</span><span class="c1">;; Make a grid of unbound variables.</span><span class="w">
</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">grid</span><span class="w"> </span><span class="p">[</span><span class="n">size</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">repeatedly</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">size</span><span class="w"> </span><span class="n">size</span><span class="p">)</span><span class="w"> </span><span class="n">lvar</span><span class="p">))</span><span class="w">

</span><span class="c1">;; Simply execute a life step on the state.</span><span class="w">
</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">fwdlife</span><span class="w"> </span><span class="p">[</span><span class="n">size</span><span class="w"> </span><span class="n">state</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">vars</span><span class="w"> </span><span class="p">(</span><span class="nf">grid</span><span class="w"> </span><span class="n">size</span><span class="p">)</span><span class="w">
        </span><span class="nb">next</span><span class="w"> </span><span class="p">(</span><span class="nf">grid</span><span class="w"> </span><span class="n">size</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">run</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">[</span><span class="n">q</span><span class="p">]</span><span class="w">
         </span><span class="p">(</span><span class="nb">==</span><span class="w"> </span><span class="n">q</span><span class="w"> </span><span class="nb">next</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nb">==</span><span class="w"> </span><span class="n">vars</span><span class="w"> </span><span class="n">state</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">stepo</span><span class="w"> </span><span class="n">size</span><span class="w"> </span><span class="n">vars</span><span class="w"> </span><span class="nb">next</span><span class="p">))))</span><span class="w">

</span><span class="c1">;; Produce three backward steps on state.</span><span class="w">
</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">revlife</span><span class="w"> </span><span class="p">[</span><span class="n">size</span><span class="w"> </span><span class="n">state</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">start</span><span class="w"> </span><span class="p">(</span><span class="nf">grid</span><span class="w"> </span><span class="n">size</span><span class="p">)</span><span class="w">
        </span><span class="n">s1</span><span class="w"> </span><span class="p">(</span><span class="nf">grid</span><span class="w"> </span><span class="n">size</span><span class="p">)</span><span class="w">
        </span><span class="n">s2</span><span class="w"> </span><span class="p">(</span><span class="nf">grid</span><span class="w"> </span><span class="n">size</span><span class="p">)</span><span class="w">
        </span><span class="n">end</span><span class="w"> </span><span class="p">(</span><span class="nf">grid</span><span class="w"> </span><span class="n">size</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">run</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">[</span><span class="n">q</span><span class="p">]</span><span class="w">
          </span><span class="p">(</span><span class="nb">==</span><span class="w"> </span><span class="n">q</span><span class="w"> </span><span class="p">[</span><span class="n">start</span><span class="w"> </span><span class="n">s1</span><span class="w"> </span><span class="n">s2</span><span class="w"> </span><span class="n">end</span><span class="p">])</span><span class="w">
          </span><span class="p">(</span><span class="nb">==</span><span class="w"> </span><span class="n">end</span><span class="w"> </span><span class="n">state</span><span class="p">)</span><span class="w">
          </span><span class="p">(</span><span class="nf">stepo</span><span class="w"> </span><span class="n">size</span><span class="w"> </span><span class="n">s2</span><span class="w"> </span><span class="n">end</span><span class="p">)</span><span class="w">
          </span><span class="p">(</span><span class="nf">stepo</span><span class="w"> </span><span class="n">size</span><span class="w"> </span><span class="n">s1</span><span class="w"> </span><span class="n">s2</span><span class="p">)</span><span class="w">
          </span><span class="p">(</span><span class="nf">stepo</span><span class="w"> </span><span class="n">size</span><span class="w"> </span><span class="n">start</span><span class="w"> </span><span class="n">s1</span><span class="p">)</span><span class="w">
         </span><span class="p">)))</span><span class="w">

</span><span class="c1">;; Nicely print the board.</span><span class="w">
</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">printlife</span><span class="w"> </span><span class="p">[</span><span class="n">size</span><span class="w"> </span><span class="n">grids</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">doseq</span><span class="w"> </span><span class="p">[</span><span class="n">g</span><span class="w"> </span><span class="n">grids</span><span class="p">]</span><span class="w">
    </span><span class="p">(</span><span class="nb">doseq</span><span class="w"> </span><span class="p">[</span><span class="n">row</span><span class="w"> </span><span class="p">(</span><span class="nf">-&gt;&gt;</span><span class="w"> </span><span class="n">g</span><span class="w"> </span><span class="p">(</span><span class="nf">partition</span><span class="w"> </span><span class="n">size</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="n">vec</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">into</span><span class="w"> </span><span class="p">[]))]</span><span class="w">
      </span><span class="p">(</span><span class="nb">doseq</span><span class="w"> </span><span class="p">[</span><span class="n">t</span><span class="w"> </span><span class="n">row</span><span class="p">]</span><span class="w">
        </span><span class="p">(</span><span class="nb">print</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="s">""</span><span class="p">))</span><span class="w">
      </span><span class="p">(</span><span class="nb">print</span><span class="w"> </span><span class="s">"\n"</span><span class="p">))</span><span class="w">
    </span><span class="p">(</span><span class="nb">print</span><span class="w"> </span><span class="s">"\n"</span><span class="p">)))</span><span class="w">

</span><span class="c1">;; Test with a glider.</span><span class="w">
</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">-main</span><span class="w"> </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">args</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">-&gt;&gt;</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w">
        </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w">
        </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">0</span><span class="w">
        </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w">
        </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">0</span><span class="w">
        </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">]</span><span class="w">
       </span><span class="p">(</span><span class="nf">revlife</span><span class="w"> </span><span class="mi">6</span><span class="p">)</span><span class="w">
       </span><span class="nb">first</span><span class="w">
       </span><span class="p">(</span><span class="nf">printlife</span><span class="w"> </span><span class="mi">6</span><span class="p">)))</span><span class="w">
</span></code></pre></div></div>

<p>output:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ clj -Mrun
1 0 1 0 1 1 
1 0 0 0 0 1 
0 0 1 0 0 0 
0 0 0 0 0 1 
1 0 1 1 0 0 
1 0 1 1 1 1 

0 1 0 0 1 1 
0 0 0 1 1 1 
0 0 0 0 0 0 
0 1 1 1 0 0 
0 0 1 0 0 1 
0 0 1 0 1 0 

0 0 0 1 0 1 
0 0 0 1 0 1 
0 0 0 0 0 0 
0 1 1 1 0 0 
0 0 0 0 1 0 
0 0 0 1 0 0 

0 0 0 0 0 0 
0 0 0 0 0 0 
0 0 0 1 1 0 
0 0 1 1 0 0 
0 0 0 0 1 0 
0 0 0 0 0 0
</code></pre></div></div>

<p>Sadly, this is nowhere near fast enough to solve the play button problem.</p>]]></content><author><name></name></author><category term="clojure" /><category term="core.logic" /><summary type="html"><![CDATA[I got a litlte bit nerd sniped by the following video and decided to implement game of life in clojure.core.logic, because any logic program can be evaluated forwards and backwards.]]></summary></entry><entry><title type="html">Why Are Guillotine Blades Angled? (analyzed)</title><link href="http://pepijndevos.nl/2024/08/24/why-is-a-guillotine-blade-diagonal.html" rel="alternate" type="text/html" title="Why Are Guillotine Blades Angled? (analyzed)" /><published>2024-08-24T00:00:00+00:00</published><updated>2024-08-24T00:00:00+00:00</updated><id>http://pepijndevos.nl/2024/08/24/why-is-a-guillotine-blade-diagonal</id><content type="html" xml:base="http://pepijndevos.nl/2024/08/24/why-is-a-guillotine-blade-diagonal.html"><![CDATA[<p>This is a theoretical counterpart to an experimental collaboration between <a href="https://www.youtube.com/@KnowArt">KnowArt</a> and <a href="https://www.youtube.com/@properprinting">Proper Printing</a> to see if a diagonal guillotine blade cuts better than a horizontal one.</p>

<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/53fSsfUjUeI?si=lC3MDF0hAtdnQhn6" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe>

<p>A raised guillotine blade has a certain potential energy which is transferred to the contact point between the blade and the neck. So let’s assume a frictionless spherical cow, I mean neck, and calculate the contact point, force, and energy as a function of the angle of the blade.</p>

<p>The equation of a circle is given by \(x^2-y^2=r^2\), so a horizontal blade intersecting the circle of radius \(r\) at height \(y\) intersects the circle at \(x=\pm\sqrt{r^2-y^2}\). The length of the cut is therefore \(L(y)=2\sqrt{r^2-y^2}\).</p>

<p>For a diagonal blade, we can rotate the reference frame to be aligned to the blade, such that the knife has a horizontal component of \(y\sin(\theta)\) and a vertical component of \(y\cos(\theta)\), creating a contact patch of \(L(y\cos(\theta))=2\sqrt{r^2-y^2\cos^2(\theta)}\).</p>

<p><img src="/images/guillotine/geometry.gif" alt="rotated guillotine" /></p>

<p>So now we can express the force from the contact area in terms of some constant \(k\) as \(F(y)=kL(y)\) perpendicular to the blade edge, resulting in a vertial and horizontal component:</p>

\[\begin{aligned}
F_x(y)&amp;=\sin(\theta)kL(y)\\
F_x(y)&amp;=\sin(\theta)k2\sqrt{r^2-y^2\cos^2(\theta)}\\
F_y(y)&amp;=\cos(\theta)kL(y)\\
F_y(y)&amp;=\cos(\theta)k2\sqrt{r^2-y^2\cos^2(\theta)}
\end{aligned}\]

<p>This means that an angled blade creates “leverage” where less force but a longer travel is required. To be precise, the travel is given by \(\frac{2r}{\cos(\theta)}\).</p>

<p><img src="/images/guillotine/vertical_cut_force_plot.svg" alt="vertical component of cutting force" /></p>

<p>To compute the total energy required for a cut, assuming the dominant force scales with the length of the contact patch, is the integral of the force over the vertical travel component.</p>

\[W_y = k \cos(\theta) \int_{-\frac{r}{\cos(\theta)}}^{\frac{r}{\cos(\theta)}} L(y\cos(\theta)) dy\]

<p>Now we can simplify by substituting \(u=y\cos(\theta)\), \(dy=\frac{du}{\cos(\theta)}\) and adjust the integration bounds accordingly, cancelling all \(\theta\) terms!</p>

\[W_y = k \int_{-r}^{r} L(u) du\]

<p>So just as much vertical energy is required to cut through the circle no matter the angle, but we’re also excerting a horizontal force. If the guillotine blade is running on math bearings this is of no concern, but for a wooden sled there is a (Coulomb) friction of \(F_c=\mu F_x\) giving</p>

\[\begin{aligned}
W_x &amp;= \mu k \sin(\theta) \int_{-\frac{r}{\cos(\theta)}}^{\frac{r}{\cos(\theta)}} L(y\cos(\theta)) dy\\
W_x &amp;= \mu k \tan(\theta)\int_{-r}^{r} L(u) du
\end{aligned}\]

<p>But what about the pointy blade? I’m not glad you asked because the math is more messy. We can consider one half of the pointy blade at a rotated reference frame. So we get the same \(L\) as before, but now the blade stops at the tangent line of the blade angle, as follows. But then once the point of the blade exits the circle it becomes two flat sections, forming a piecewise function.</p>

<p><img src="/images/guillotine/desmos-graph.svg" alt="pointy blade geometry" /></p>

<p>For the first part we have half of a flat blade length \(L\), the uncut section \(M\), and the pointy blade section \(N\)</p>

\[\begin{aligned}
L&amp;=\sqrt{r^2-(\cos(\theta)h)^2} \\
M&amp;=\tan(\theta)\cos(\theta)h\\
&amp;=\sin(\theta)h \\
N&amp;=L-M \\
&amp;=\sqrt{r^2-(\cos(\theta)h)^2}-\sin(\theta)h
\end{aligned}\]

<p>Giving us</p>

\[\begin{aligned}
L_{tot} &amp;=\begin{cases} 
      2\left(\sqrt{r^2-\cos^2(\theta)h^2}-\sin(\theta)h\right) &amp; -r &lt; h \leq r \\
      4\sqrt{r^2-\cos^2(\theta)h^2} &amp; r &lt; h \leq \frac{h}{\cos(\theta)} \\
   \end{cases} \\
F_y &amp;= \cos(\theta)k L_{tot}
\end{aligned}\]

<p><img src="/images/guillotine/pointy_blade_cut_force.svg" alt="pointy force" /></p>

<p>For the total work, we already know the square root terms are independent of the angle. And since the sum of the integral is the same as the integral of the sum, we only need to concern ourselves with the \(\sin(\theta)h\) term.</p>

\[\begin{aligned}
W_p &amp;= k\cos(\theta)\sin(\theta)\int_{-r}^r h dh \\
W_p &amp;= k\cos(\theta)\sin(\theta)\left(\frac{1}{2}(-r)^2-\frac{1}{2}(r)^2\right) \\
W_p &amp;= 0
\end{aligned}\]

<p>So all our blades take the same amount of work to cut through an uniform saussage, with the diagonal blades being worse due to friction. The brachistochrone blade is left as an exercise to the reader.</p>

<p>But there is more to cutting than uniform math saussages. KnowArt indeed found that the diagonal blade seemed to perform worse than the flat blade due to friction. But the pointy blade performed better. The most likely explanation for this is that a horizontal slicing motion is beneficial for cutting. This makes intuitive sense to anyone who’s done some cooking, and was also explained to me by a medical doctor relative as the difference between a sword and a sabre.</p>

<p>But the story goes that the real reason the blade is diagonal is that the king suggested it might help with people with fat necks. Ironically his own fat neck ended on the block some time later.</p>]]></content><author><name></name></author><category term="history" /><category term="mathematics" /><summary type="html"><![CDATA[This is a theoretical counterpart to an experimental collaboration between KnowArt and Proper Printing to see if a diagonal guillotine blade cuts better than a horizontal one.]]></summary></entry><entry><title type="html">Earth could be round, Earth could be flat, Earth could have violet sky</title><link href="http://pepijndevos.nl/2024/04/14/earth-could-be-round-earth-could-be-flat.html" rel="alternate" type="text/html" title="Earth could be round, Earth could be flat, Earth could have violet sky" /><published>2024-04-14T00:00:00+00:00</published><updated>2024-04-14T00:00:00+00:00</updated><id>http://pepijndevos.nl/2024/04/14/earth-could-be-round-earth-could-be-flat</id><content type="html" xml:base="http://pepijndevos.nl/2024/04/14/earth-could-be-round-earth-could-be-flat.html"><![CDATA[<p>My dad likes to argue with flat earthers on Facebook, so I decided to steel man a consistent flat earth theory. The premise is simple: you just take a 3D azimuthal equidistant projection of the spherical world and rewrite physics in the new coordinate system. This is what it looks like:</p>

<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/sZvHGFJVeT0?si=JiubTzPoxAXl_F71" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""> </iframe>

<p>This is very similar to a geocentric model of the universe, it’s not fundamentally wrong to choose earth as your reference frame, just very inconvenient for describing orbits around the sun. In the same way it’s not fundamentally wrong to assume the earth is flat, but it warps the rest of the universe in strange ways.</p>

<p>For example, here is a round earth. We observe ships disappearing over the horizon, and we observe day and night.</p>

<p><img src="/images/flatearth/roundearth.png" alt="a round earth with a sun shining on it and a person looking at a ship on the horizon" /></p>

<p>Most flat earth models don’t tend to have satisfactory explanations for sunset, things disappearing behind the horizon, and eclipses. The solution is simple: light curves away from the earth. At some point sunlight curves back into space.</p>

<p><img src="/images/flatearth/flatearth.png" alt="the inverse polar transform of the above image" /></p>

<p>In this model the sun is still further away than the moon, so a solar eclipse is simply the moon moving in front of the sun as usual. In a lunar eclipse, the light of the sun has to bend so deep that it would touch the earth before it could bend back up to the moon.</p>

<p>A lovely result from this flat earth model is that it clearly answers the questions what is below the earth: the singularity. Even better is that the density of the earth goes down the closer you get to the singularity, meaning the earth is in a sense hollow. Finally a grand unifying theory of hollow earth and flat earth models!</p>

<p>The downside of this model that physics isn’t independent of location and direction. For example the atmosphere is denser in the middle of the disk. A simple equation like \(F=ma\) becomes hellishly complicated if you want it to work everywhere. There is also a magic Pacman teleportation point on the south pole.</p>

<p>This model is internally consistent and impossible to falsify since it is simply a coordinate transform of conventional physics. You can’t make any observations that would disagree with the model and agree with a spherical model since they are the same universe. It is not possible to measure which way of looking at things is “real” because all your observations and tools are curved in the same way. Therefore, you could interpret the earth to be flat.</p>

<p>With the combined skills in Blender and mathematics of me and my brother, we managed to implement the flat earth coordinate transformation in Blender geometry nodes so that you can make a 3D model and see what it looks like on a flat earth.</p>

<p><img src="/images/flatearth/geometry.png" alt="Blender geometry nodes" /></p>

<p>We struggled to get lighting to work since Blender would render linear light after the transform, so instead we drew physical light cones with luminescent hemispheres that pass through the transform correctly. Unfortunately this means we can’t render eclipses, but the upside is you can really see the wild curves light makes on a flat earth.</p>

<p>We’ve theorized that it might be possible to bake lighting into the texture as is commonly done for video games, but it’s nice weather outside so we pressed render and left eclipses as an exercise to the reader.</p>]]></content><author><name></name></author><category term="math" /><category term="astronomy" /><summary type="html"><![CDATA[My dad likes to argue with flat earthers on Facebook, so I decided to steel man a consistent flat earth theory. The premise is simple: you just take a 3D azimuthal equidistant projection of the spherical world and rewrite physics in the new coordinate system. This is what it looks like:]]></summary></entry><entry><title type="html">Library 2000 part 2: Tablets for Sale</title><link href="http://pepijndevos.nl/2024/02/25/library-2000-part-2-tablets-for-sale.html" rel="alternate" type="text/html" title="Library 2000 part 2: Tablets for Sale" /><published>2024-02-25T00:00:00+00:00</published><updated>2024-02-25T00:00:00+00:00</updated><id>http://pepijndevos.nl/2024/02/25/library-2000-part-2-tablets-for-sale</id><content type="html" xml:base="http://pepijndevos.nl/2024/02/25/library-2000-part-2-tablets-for-sale.html"><![CDATA[<p>I’m making a clay tablet library with media from around the 20th century, to preserve our culture for the ages and answer the question: What do we want the distant future to know about us?</p>

<p>A lot has happened since the <a href="/2023/05/20/lithopedia-part-1-intro-and-clay-experiments.html">first part</a>:</p>

<ul>
  <li>I renamed the project after learning about <a href="https://en.wikipedia.org/wiki/Lithopedion">lithopedion</a></li>
  <li>I had a private lesson from <a href="https://www.ilsescholten.nl/">ceramic artist Ilse Scholten</a></li>
  <li>I rewrote my text to gcode scripts</li>
  <li>I made a bunch of tablets and had them fired</li>
  <li>I started working towards a website for the project</li>
</ul>

<p>Let’s start from the result and work back through the details. I made a bunch of clay tablets, had them fired by Ilse, and put them up <strong>for sale on <a href="https://www.etsy.com/shop/Library2000">my Etsy store</a></strong>. Here is a photo album to check out:</p>

<p><a data-flickr-embed="true" href="https://www.flickr.com/photos/pepijndevos/albums/72177720315047612" title="Library 2000"><img src="https://live.staticflickr.com/65535/53551511959_c7733cc50c_w.jpg" width="500" height="375" alt="Library 2000" /></a><script async="" src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script></p>

<p>The reason it took so long to get the second update out is that I wanted to build a proper website with the concept, motivation, tutorials, a gallery, and a webshop. But as a suprpise to no one that’s quite a big project on its own, so I finally decided to write another update on my blog meanwhile.</p>

<p>The first key element to the new proccess is my newfound gcode swiss army knife: vpype. With <a href="https://github.com/plottertools/vpype-gcode">vpype-gcode</a> turning an SVG or text file into gcode is a one-liner, you just need a config file for your CNC and a few command-line flags.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vpype <span class="nt">--config</span> vpype.toml pagesize a5 text <span class="nt">--position</span> 1cm 1cm <span class="nt">--wrap</span> 12.5cm <span class="nt">--size</span> 22pt <span class="nt">--hyphenate</span> en <span class="nt">--justify</span> <span class="s2">"</span><span class="nv">$RANDOM_PAGE_TEXT</span><span class="s2">"</span> linemerge show gwrite <span class="nt">--profile</span> cnc random.gcode
</code></pre></div></div>

<p>But the biggest change was in the preparation of the clay slab itself. I abandoned my failing attempts at making a mold, and followed Ilse’s advice to roll the clay between strips of wood.</p>

<h3 id="tutorial">Tutorial</h3>

<p>Materials needed:</p>
<ul>
  <li>clay (0.2mm chamotte)</li>
  <li>clay wire cutter</li>
  <li>clay rib</li>
  <li>dough roller</li>
  <li>plasterboard</li>
  <li>hardwood strips (5mm to 10mm thick)</li>
  <li>some old bedsheet</li>
  <li>knife</li>
</ul>

<p>To make a clay tablet you obviously need clay. It is important that the clay contains chamotte, which makes it less likely to explode in the oven. It’s also nice if the chamotte is small to get smooth writing. Clay with 0.2mm chamotte has worked the best for me.</p>

<p>We need a very flat surface, we’re aiming for sub milimeter precision to get consistent writing.
Plasterboard is the surface that Ilse recommended, which has worked well for me. (unlike wood which will warp)
I cut off roughly square sheets and taped the edges so we can safely move the drying tablets around.</p>

<p>It is very important to work on top of a smooth non-strechy cloth. I use a cut up cotton bedsheet.
This allows us to flip the tablet over, inspect it for air bubbles, and prevents it from sticking to the plasterboard and warping while drying.</p>

<p><img src="/images/lib2k/IMG_20240224_160928_273.jpg" alt="working surface" /></p>

<p>Now we can get to work. Cut off a piece of clay with a wire cutter, place it on the cloth betweent the wood strips, and use the dough roller to flatten it out. If you’re reusing scraps from a pervious tablet, first knead the clay into a ball, while making sure not to knead any air bubbles.</p>

<p><img src="/images/lib2k/IMG_20240224_161108_407.jpg" alt="roll the clay" /></p>

<p>After rolling it flat we can cut off the excess clay with a knife. I use a folded piece of paper as a reference. Then take the rib and smooth out the surface, making it nice and shiny.</p>

<p>An important step at this point is to slightly lift the fabric, bending the tablet. If there are any air bubbles they will show up as little blisters. Poke them with a knife and flatten the tablet again.</p>

<p>Now cover the tablet with a cloth and flip it over, taking care not to leave fingerprints. Then take the rib and smooth the other side. Then flip it to whichever side looks nicer to become the front.</p>

<p><img src="/images/lib2k/IMG_20240224_161627_909.jpg" alt="finish the tablet" /></p>

<p>With my current process I put the entire board with the wet clay tablet right under the CNC.
I give it a final roll with the dough roller because clay has a little bit of memory.
Then I zero the CNC on the wood strip to get the correct height without leaving a mark.
For the CNC bit I actually use a thick needle in a 2mm chuck at 0 RPM, I found that the stationary sharp point produces the finest writing in the wet clay.</p>

<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/I9oRr1zIfXg?si=4fJ2ZQkMWpgtY0MY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe>

<p>A sneak peak at my current experiments is that I’m trying out what happens if you let the clay dry a little bit. Maybe there is a sweet spot where you can CNC it without accumulating or chipping, but I’ve not yet found it. I’m also experimenting with V-carving to be able to engrave more complicated graphics such as mathematical equations. My current goal is to engrave Maxwell equations on a tablet, so that’s a whole new can of worms.</p>]]></content><author><name></name></author><category term="lib2k" /><category term="cnc" /><summary type="html"><![CDATA[I’m making a clay tablet library with media from around the 20th century, to preserve our culture for the ages and answer the question: What do we want the distant future to know about us?]]></summary></entry><entry><title type="html">Theremin Singing Bowl</title><link href="http://pepijndevos.nl/2024/02/18/theremin-singing-bowl.html" rel="alternate" type="text/html" title="Theremin Singing Bowl" /><published>2024-02-18T00:00:00+00:00</published><updated>2024-02-18T00:00:00+00:00</updated><id>http://pepijndevos.nl/2024/02/18/theremin-singing-bowl</id><content type="html" xml:base="http://pepijndevos.nl/2024/02/18/theremin-singing-bowl.html"><![CDATA[<p>I built my own electro-mechanical music instrument by combining ideas from some of the coolest instruments I know of:</p>

<p>A theremin is an electronic instrument that you play by moving your hands in the air. It uses an analog circuit with two tuned oscillators, one of which is modulated by the antenna impedance which is perturbed by the presence of your body in the near field. The slight frequency differences are fed into a mixer to produce an audible frequency.</p>

<p>A singing bowl is a very old insturment that is usually played by rotating a suede covered mallet around its outside rim. The stick-slip motion of the mallet excites vibration modes of which the nodes are pushed around by the mallet. Some more experimental musicians also play singing bowls by drawing a violin bow across the rim.</p>

<p>A hurdy-gurdy is a string instrument, which uses a hand-cranked wheel with rosin on it rather than a bow. It also has other unique features like drone strings and a keyboard on the neck.</p>

<p>So here is the idea: what if you used a theremin to drive a hurdy-gurdy wheel and use that to play a singing bowl? Magic theremin singing bowl!</p>

<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/DvJmAOT3pbM?si=RvBnf3VKHoUuQq2C" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""> </iframe>

<p>For the theremin I built myself a copy of the <a href="http://www.thereminworld.com/Forums/T/29231/my-new-year-gift-to-tw-a-new-theremin-circuit">Thierrymin</a> (using J113 jfets), which I fed into a comparator, stepper motor driver, and stepper motor. From there the only things I had to tweak were a few resistors to add a little hysteresis, and I played around with the microstepping pins to change the speed of the stepper motor. Here is a rough schematic:</p>

<p><img src="/images/Thierrymin_v3_schem.png" alt="schematic" /></p>

<p>That was the easy part. Then I did a bunch of experiments with different types of wheels. I tried rosin, suede, wood, plastic. I tried to spin the wheel on a static bowl, or to spin the bowl against a static baton. It kind of worked, but nothing worked quite the way I wanted it. And then life got rather busy and the project sat dormant for a while. So that’s when I decided to just write a blogpost with my progress and shelve the project. Maybe I’ll get back to it one day, or inspire someone else.</p>]]></content><author><name></name></author><category term="electronics" /><category term="music" /><summary type="html"><![CDATA[I built my own electro-mechanical music instrument by combining ideas from some of the coolest instruments I know of:]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://pepijndevos.nl/images/Thierrymin_v3_schem.png" /><media:content medium="image" url="http://pepijndevos.nl/images/Thierrymin_v3_schem.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>