<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Stephan -All Input Is Error- Bökelmann</title>
    <description>The latest articles on DEV Community by Stephan -All Input Is Error- Bökelmann (@maxclerkwell).</description>
    <link>https://dev.to/maxclerkwell</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3937564%2Fcc535321-1bb1-46fa-b3ea-b589e5e81d32.jpg</url>
      <title>DEV Community: Stephan -All Input Is Error- Bökelmann</title>
      <link>https://dev.to/maxclerkwell</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/maxclerkwell"/>
    <language>en</language>
    <item>
      <title>Simulating Antiproton Energy Loss in an HV-MAPS Silicon Sensor with Geant4</title>
      <dc:creator>Stephan -All Input Is Error- Bökelmann</dc:creator>
      <pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/maxclerkwell/simulating-antiproton-energy-loss-in-an-hv-maps-silicon-sensor-with-geant4-59d3</link>
      <guid>https://dev.to/maxclerkwell/simulating-antiproton-energy-loss-in-an-hv-maps-silicon-sensor-with-geant4-59d3</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fviu52hv9ty55okb672te.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fviu52hv9ty55okb672te.jpg" alt="Stephan Bökelmann a.k.a MaxClerkwell and another researcher from RUB standing in a supermarket parking lot on Corfu — the store sign reads " width="606" height="1280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three years ago, a colleague and I stumbled across a supermarket on Corfu called &lt;strong&gt;PROTON&lt;/strong&gt;. I am holding my thumb down in the photo. Team Antiproton does not endorse protons.&lt;/p&gt;

&lt;p&gt;That same commitment to antimatter is also why I spent a recent weekend setting up Geant4 to answer a very concrete question: how much energy does an antiproton actually deposit in a silicon pixel sensor as thin as the ones we work with?&lt;/p&gt;




&lt;h2&gt;
  
  
  What is HV-MAPS?
&lt;/h2&gt;

&lt;p&gt;A conventional hybrid pixel detector is two separate chips glued and bump-bonded together: a sensor chip that collects charge, and a readout ASIC that processes it. HV-MAPS — High-Voltage Monolithic Active Pixel Sensor — collapses both into a single piece of standard CMOS silicon. A high reverse bias voltage (typically 60–120 V) is applied to the substrate, depleting a thin layer directly beneath each pixel cell; ionising particles crossing this depletion zone generate electron-hole pairs that are swept to the collection electrode by the built-in electric field. Because everything lives in one die, the material budget is minimal and no bump-bonding is needed — which matters enormously in tracking detectors where every fraction of a radiation length degrades track resolution.&lt;/p&gt;

&lt;p&gt;The foundational work on this concept was published by Ivan Perić et al. and is freely available below:&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://maxclerkwell.github.io/posts/hv-maps-energy-loss-simulation-may-2026/assets/IEEE_JSSC_Peric_2007_HVCMOS.pdf" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;maxclerkwell.github.io&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;If you want more context on how these chips end up on a PCB — including what it actually takes to wire-bond a CLICpix or MuPix die by hand — I gave a talk on exactly that at KiCon Asia 2025: &lt;a href="https://dev.to/maxclerkwell/kicon-asia-2025-speaking-on-wire-bonding-in-shenzhen-25l"&gt;A poor man's intro to wire bonding&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The question: how much energy does one antiproton deposit?
&lt;/h2&gt;

&lt;p&gt;Before a beam test you want to know what signal to expect. Too little and your threshold swallows it. Too much and you are dealing with a Landau tail that skews your calibration. The answer is not a single number — it is a distribution — and the shape of that distribution depends on both the particle momentum and the sensor thickness.&lt;/p&gt;

&lt;p&gt;For the HESR storage ring at FAIR, antiproton momenta range from 1.5 to 15 GeV/c. The sensors we work with are thinned after fabrication to between 50 and 500 µm. That gives a two-dimensional parameter space that is impractical to explore by hand, but before automating anything it is worth doing one case from scratch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bethe-Bloch with thin-layer correction: a worked example
&lt;/h2&gt;

&lt;p&gt;The mean energy loss of a heavy charged particle in matter is described by the Bethe-Bloch formula. For thin absorbers — where the number of collisions is small enough that the central limit theorem does not apply — the relevant quantity is not the mean but the &lt;strong&gt;most probable value (MPV)&lt;/strong&gt; of the Landau distribution:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;Δ&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;E&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathrm mtight"&gt;MPV&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;ξ&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="minner"&gt;&lt;span class="mopen delimcenter"&gt;&lt;span class="delimsizing size3"&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span class="mop"&gt;ln&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;I&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;2&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;m&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;e&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;c&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;β&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;γ&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mop"&gt;ln&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;I&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;ξ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;j&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;β&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;δ&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;β&lt;/span&gt;&lt;span class="mord mathnormal"&gt;γ&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mclose delimcenter"&gt;&lt;span class="delimsizing size3"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;where&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;ξ&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;K&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;⋅&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;A&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;Z&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;⋅&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;β&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;ρ&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;with 
&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;K&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0.307&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;MeV&amp;nbsp;mol&lt;/span&gt;&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mtight"&gt;−&lt;/span&gt;&lt;span class="mord mtight"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;cm&lt;/span&gt;&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;
 and 
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;j&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0.200&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;
 (Landau-Vavilov constant).

&lt;p&gt;&lt;strong&gt;Constants for silicon:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;th&gt;Symbol&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Atomic number&lt;/td&gt;
&lt;td&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;Z&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Atomic mass&lt;/td&gt;
&lt;td&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;A&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;td&gt;28.09 g/mol&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Density&lt;/td&gt;
&lt;td&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;ρ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;td&gt;2.329 g/cm³&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mean excitation energy&lt;/td&gt;
&lt;td&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;I&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;td&gt;173 eV&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Density correction&lt;/td&gt;
&lt;td&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;δ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;td&gt;(relativistic, see below)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Kinematics for a 10 GeV/c antiproton:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The antiproton rest mass is 
&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;m&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord accent mtight"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;p&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="accent-body"&gt;&lt;span class="mord mtight"&gt;ˉ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;938.3&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;MeV&lt;/span&gt;&lt;/span&gt;&lt;span class="mord"&gt;/&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;c&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mord"&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;



&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;p&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;10&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;GeV&lt;/span&gt;&lt;/span&gt;&lt;span class="mord"&gt;/&lt;/span&gt;&lt;span class="mord mathnormal"&gt;c&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;⟹&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;γ&lt;/span&gt;&lt;span class="mord mathnormal"&gt;β&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;m&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord accent mtight"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;p&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="accent-body"&gt;&lt;span class="mord mtight"&gt;ˉ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;c&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;p&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;938.3&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;MeV&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;10000&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;MeV&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;≈&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;10.66&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;



&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;β&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord sqrt"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span class="svg-align"&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;γ&lt;/span&gt;&lt;span class="mord mathnormal"&gt;β&lt;/span&gt;&lt;span class="mclose"&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="hide-tail"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;γ&lt;/span&gt;&lt;span class="mord mathnormal"&gt;β&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;≈&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord sqrt"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span class="svg-align"&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;113.6&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="hide-tail"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;10.66&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;≈&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0.9956&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;



&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;γ&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;≈&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;10.71&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Step 1 — compute ξ for x = 100μm =0.01cm:&lt;/strong&gt;&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;ξ&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;0.307&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;⋅&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;28.09&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;14&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;⋅&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;0.995&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;6&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;2.329&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0.01&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;≈&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0.1535&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;⋅&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0.4984&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;⋅&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0.02353&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;≈&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1.80&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;0&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mtight"&gt;−&lt;/span&gt;&lt;span class="mord mtight"&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;MeV&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Step 2 — density correction δ:&lt;/strong&gt;&lt;br&gt;
At βγ ≈ 10.66 silicon is well into the relativistic rise. Using the standard Sternheimer parametrisation for Si, δ ≈ 4.5 at this momentum.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — MPV:&lt;/strong&gt;&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mop"&gt;ln&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;I&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;2&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;m&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;e&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;c&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;β&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;γ&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mop"&gt;ln&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;173&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;eV&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;2&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0.511&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;0&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;6&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;eV&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0.9912&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;114.5&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;≈&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mop"&gt;ln&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;6.72&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;0&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;6&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;≈&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;15.72&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;



&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mop"&gt;ln&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;I&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;ξ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mop"&gt;ln&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;173&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;eV&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;1.80&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;0&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;eV&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;≈&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mop"&gt;ln&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;10.40&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;≈&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;2.34&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;



&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;Δ&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;E&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord text mtight"&gt;&lt;span class="mord mtight"&gt;MPV&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1.80&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;0&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mtight"&gt;−&lt;/span&gt;&lt;span class="mord mtight"&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;⋅&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="minner"&gt;&lt;span class="mopen delimcenter"&gt;[&lt;/span&gt;&lt;span class="mord"&gt;15.72&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;2.34&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0.20&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0.991&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;4.5&lt;/span&gt;&lt;span class="mclose delimcenter"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;MeV&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;



&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;Δ&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;E&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord text mtight"&gt;&lt;span class="mord mtight"&gt;MPV&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;≈&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1.80&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;0&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mtight"&gt;−&lt;/span&gt;&lt;span class="mord mtight"&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;12.77&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;MeV&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;≈&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;23&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;keV&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;At 3.6 eV per electron-hole pair in silicon that is roughly &lt;strong&gt;6 400 e/h pairs&lt;/strong&gt; — enough to trigger a well-designed pixel, but only barely, and the Landau tail will push some events an order of magnitude higher.&lt;/p&gt;

&lt;p&gt;This is one point in a two-dimensional space. If you want to sweep momentum from 1.5 to 15 GeV/c and thickness from 50 to 500 µm, doing this by hand becomes unreasonable very quickly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Before Geant4: a Python tool
&lt;/h2&gt;

&lt;p&gt;About six years ago I wrote a small Python script to automate exactly this calculation — a Bethe-Bloch evaluator with the Vavilov-Landau thin-layer correction for arbitrary momentum and thickness. It was built partly on older code by &lt;a href="https://www.researchgate.net/profile/Miriam-Fritsch-2" rel="noopener noreferrer"&gt;Miriam Fritsch&lt;/a&gt; at Ruhr-Universität Bochum, who works on the PANDA experiment and has been dealing with these numbers far longer than I have. The script did the job: generate a lookup table, estimate thresholds, be done with it.&lt;/p&gt;

&lt;p&gt;But an analytic formula — even a correct one — only gives you the most probable value and an approximation of the width. It does not tell you the full shape of the energy deposition distribution, it does not handle secondary particles (delta electrons), and it says nothing about what happens when an antiproton annihilates in the sensor material. For that you need a proper particle transport simulation.&lt;/p&gt;

&lt;p&gt;Last weekend I used Geant4 for the first time to replace the Python tool.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setting up Geant4
&lt;/h2&gt;

&lt;p&gt;Geant4 is not a binary you install and run. It is a C++ framework that you compile from source, then write your own detector description and simulation logic against its API, and compile that too. The setup used here is version 11.4.1. In the commands below, &lt;code&gt;&amp;lt;installation-dir&amp;gt;&lt;/code&gt; refers to whatever path you built and installed Geant4 into.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Loading the environment:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt; &amp;lt;installation-dir&amp;gt;/bin/geant4.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sets the library paths and environment variables that CMake and the runtime linker need.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building your own simulation:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cmake &lt;span class="nt"&gt;-B&lt;/span&gt; build &lt;span class="nt"&gt;-DCMAKE_BUILD_TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Release
cmake &lt;span class="nt"&gt;--build&lt;/span&gt; build &lt;span class="nt"&gt;-j&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;nproc&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first thing I did after the environment was working was compile the bundled examples — Geant4 ships with a large set of reference simulations covering everything from basic geometry to full calorimeter setups. Compiling these is the sanity check: if they build and produce sensible output, your installation is correct.&lt;/p&gt;

&lt;p&gt;Geant4 has a graphical viewer (Qt-based) that you get by running the executable without arguments. I am not usually a fan of GUI tools for this kind of work, but the visualiser is genuinely useful when you are first building a geometry — watching the simulated tracks scatter and stop in the sensor volume makes it immediately obvious if your detector definition is wrong.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fotdnv1fulfnllqfhhg51.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fotdnv1fulfnllqfhhg51.jpg" alt="Geant4 Qt GUI" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Geant4 Qt GUI — exampleB1. The yellow scoring volume and grey detector block are the default geometry.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnt9wvww1ibnqufaa4vrk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnt9wvww1ibnqufaa4vrk.jpg" alt="First analysis" width="799" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;First analysis: MPV deposited energy (left), mean deposited energy (centre), and e/h pairs at MPV (right).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once the geometry looks right, everything else runs in batch mode via macro files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/pixel/momentum 10.0 GeV
/pixel/thickness 100 um
/pixel/output/setTag myrun
/run/initialize
/run/beamOn 10000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What I changed for the HV-MAPS geometry
&lt;/h2&gt;

&lt;p&gt;The starting geometry in the example code is a generic silicon box. To match the chips I actually work with I made the following changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sensor area:&lt;/strong&gt; 2 cm × 2 cm lateral extent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thickness sweep:&lt;/strong&gt; 50 µm to 500 µm in steps — the range we actually use after thinning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Particle gun:&lt;/strong&gt; antiproton at normal incidence, momentum swept from 1.5 to 15 GeV/c&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Physics list:&lt;/strong&gt; &lt;code&gt;FTFP_BERT&lt;/code&gt; — this is important. Standard electromagnetic-only lists do not handle antiproton annihilation. &lt;code&gt;FTFP_BERT&lt;/code&gt; includes hadronic processes and correctly models what happens when an antiproton stops in the material and annihilates with a silicon nucleus, producing a spray of pions and other secondaries that deposit large amounts of energy. If you use the wrong physics list you will miss the high-energy tail entirely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The thickness range reflects real production. HV-MAPS chips come out of the fab at standard wafer thickness — several hundred microns. They are then thinned by backgrinding before being diced and mounted. The thinned dies end up glued to an evaluation PCB and wire-bonded — the same bonding process I covered in the &lt;a href="https://dev.to/maxclerkwell/kicon-asia-2025-speaking-on-wire-bonding-in-shenzhen-25l"&gt;KiCon talk&lt;/a&gt;. A thinner sensor means less material in the beam, which is good for tracking resolution, but also less ionisation signal, which makes the threshold setting more critical. Simulating across the full thickness range shows exactly where the signal falls below the noise floor.&lt;/p&gt;




&lt;h2&gt;
  
  
  Results: the Landau distribution
&lt;/h2&gt;

&lt;p&gt;The output is a CSV histogram of energy deposition per event. After running the sweep across the full HESR momentum range and the expected thickness range, I wrote a short Python script to pull the MPV and mean from each histogram and render the results as 3D surface plots — shown in the carousel above.&lt;/p&gt;

&lt;p&gt;The three panels show exactly what you want to know before setting a threshold: how the most probable signal, the mean signal, and the corresponding charge yield change across the whole operating envelope of the HESR.&lt;/p&gt;

&lt;p&gt;At 10 GeV/c and 100 µm the distribution is clearly Landau-shaped:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MPV ≈ 23–27 keV → ~6 500–7 500 e/h pairs&lt;/li&gt;
&lt;li&gt;Mean ≈ 38–42 keV (pulled upward by the Landau tail)&lt;/li&gt;
&lt;li&gt;~5 % of events deposit more than 100 keV — delta electrons and annihilation products&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The mean always overestimates what a typical particle does. For threshold setting, the MPV is the right number. The mean is misleading.&lt;/p&gt;

&lt;p&gt;At 50 µm — the thinnest configuration — the MPV drops to roughly 12 keV, around 3 300 e/h pairs. For a pixel with typical noise of a few hundred electrons equivalent, this is workable, but the margin is thin and annihilation events create a tail that can saturate the charge-sensitive amplifier.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Geant4 does not simulate
&lt;/h2&gt;

&lt;p&gt;Geant4 tracks the particle through the material and records where and how much energy is deposited. It does not simulate what happens to the generated electron-hole pairs afterwards. The built-in electric field of the depleted zone — typically 10⁴ to 10⁵ V/m — drifts those pairs toward the collection electrode, and the induced current on the electrode (the Ramo-Shockley theorem) is what actually becomes the measured signal. None of that is in Geant4.&lt;/p&gt;

&lt;p&gt;For the full signal chain — drift, diffusion, charge sharing between pixels, and the induced current waveform — the tool is &lt;strong&gt;Allpix Squared&lt;/strong&gt; (developed at CERN and DESY), which uses Geant4 as its particle transport backend and adds solid-state physics on top. That is probably where I will be spending some weekends over the next few weeks.&lt;/p&gt;




&lt;h2&gt;
  
  
  From simulation to beam test
&lt;/h2&gt;

&lt;p&gt;A simulation tells you what to expect in theory. But before a detector can be installed in its target experiment — which may not even exist yet — it has to be validated at an already-running facility. Detector groups routinely bring their prototypes to whatever beam is available and matches their needs closely enough. We did a significant amount of our characterisation work at &lt;strong&gt;COSY&lt;/strong&gt; — the Cooler Synchrotron at Forschungszentrum Jülich, which delivered proton beams up to 3.7 GeV/c. Protons are not antiprotons, but for the purposes of validating the energy deposition model and the detector's threshold response, the difference in the Bethe-Bloch MPV is small at relativistic momenta.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6cx1wf19bvm3inz6cdc5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6cx1wf19bvm3inz6cdc5.jpg" alt="MuPix8 evaluation board mounted in the COSY test beam setup, 2022" width="800" height="599"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;COSY was shut down in 2023 as part of a restructuring of the German accelerator landscape. Its closure leaves a gap that FAIR will eventually fill — but eventually is the operative word.&lt;/p&gt;

&lt;p&gt;The SIS100, the superconducting heavy-ion synchrotron at the heart of FAIR, is currently in the installation and hardware commissioning phase. The tunnel is complete, the superconducting dipole magnets are in place, and the cryogenic plant began commissioning in 2025. First beam is currently scheduled for the second half of 2027. The HESR — the antiproton storage ring that PANDA will sit on — comes after that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmtg1kuoh9hij514kfl4s.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmtg1kuoh9hij514kfl4s.jpg" alt="The SIS100 tunnel at FAIR, Darmstadt — superconducting dipole magnets installed, connection pipes between magnets not yet in place" width="799" height="602"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The gap between "simulation says this should work" and "we measured it in an antiproton beam" is still several years wide. That is precisely why it is worth understanding the simulation carefully now — and why a weekend spent with Geant4 is time well spent.&lt;/p&gt;

&lt;p&gt;A dedicated post on beam test logistics, what a shift actually looks like, and what we learned from COSY will follow separately.&lt;/p&gt;

</description>
      <category>geant4</category>
      <category>simulation</category>
      <category>particlephysics</category>
      <category>hvmaps</category>
    </item>
    <item>
      <title>Zero to One with VHDL and a Lattice iCEstick</title>
      <dc:creator>Stephan -All Input Is Error- Bökelmann</dc:creator>
      <pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/maxclerkwell/zero-to-one-with-vhdl-and-a-lattice-icestick-ion</link>
      <guid>https://dev.to/maxclerkwell/zero-to-one-with-vhdl-and-a-lattice-icestick-ion</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy05gqc5ppcomt7apil31.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy05gqc5ppcomt7apil31.jpg" alt="iCEstick leaning against a Ruhr-Universität Bochum physics department mug, mixer and monitor in the background" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This post is the first in a series on FPGA development with open-source tools. By the end you will have blinked an LED on real hardware, and you will understand every step between writing the VHDL source and the moment the LED actually blinks.&lt;/p&gt;

&lt;p&gt;No prior HDL experience is assumed. If you know what a for-loop is, you have enough background.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is an FPGA, and why does it matter?
&lt;/h2&gt;

&lt;p&gt;A microcontroller runs a program. An FPGA does something fundamentally different: you describe a circuit, and the chip reconfigures its internal wiring to &lt;em&gt;become&lt;/em&gt; that circuit.&lt;/p&gt;

&lt;p&gt;There is no instruction fetch, no program counter, no interrupt latency hidden somewhere in a HAL. Logic runs in parallel the way physics demands — because it &lt;em&gt;is&lt;/em&gt; physics. Two AND gates compute simultaneously not because a scheduler allows it, but because there is no reason they cannot.&lt;/p&gt;

&lt;p&gt;This is not faster software. It is a different thing entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VHDL&lt;/strong&gt; (Very High Speed Integrated Circuit Hardware Description Language) is one of the two mainstream languages for describing that circuit. The other is Verilog. VHDL has a reputation for verbosity, which is mostly fair, but it also has a strict type system and an IEEE standard that has held together since 1987. It rewards discipline.&lt;/p&gt;




&lt;h2&gt;
  
  
  The hardware: Lattice iCEstick HX1K
&lt;/h2&gt;

&lt;p&gt;The iCEstick is a USB-A stick with a &lt;strong&gt;Lattice iCE40HX1K&lt;/strong&gt; FPGA on it. It costs around $25, plugs directly into a USB port, and has five green LEDs (D1–D5), a 12 MHz oscillator, and an FTDI chip for programming. No external power supply, no JTAG adapter, no soldering.&lt;/p&gt;

&lt;p&gt;The iCE40 family is also the reason a fully open-source FPGA toolchain exists. In 2015, Clifford Wolf reverse-engineered the iCE40 bitstream format, which made it possible to write &lt;code&gt;iceprog&lt;/code&gt;, &lt;code&gt;icepack&lt;/code&gt;, and eventually the full IceStorm project. Everything used here — GHDL, Yosys, nextpnr, IceStorm — is open-source and packaged in Debian.&lt;/p&gt;




&lt;h2&gt;
  
  
  Toolchain overview
&lt;/h2&gt;

&lt;p&gt;Before writing a single line of VHDL, it helps to know what each tool does and why it is in the chain.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GHDL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Analyses and simulates VHDL. Can also synthesise VHDL to structural Verilog.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GTKWave&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Opens the waveform files that GHDL produces. Lets you inspect signal values over time.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Yosys&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Takes Verilog (or GHDL's output) and performs RTL synthesis: logic optimisation and technology mapping onto iCE40 primitives.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;nextpnr-ice40&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Place and Route. Assigns the synthesised logic cells to physical resources in the chip, routes the connections, and checks timing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;icepack&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Converts nextpnr's human-readable ASCII bitstream to the binary format the chip expects.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;iceprog&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Writes the binary bitfile to the iCEstick's SPI flash over USB.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The complete flow is: &lt;strong&gt;VHDL → GHDL synth → Verilog → Yosys → JSON netlist → nextpnr → ASC bitstream → icepack → BIN → iceprog → iCEstick&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Install everything on Debian:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;ghdl gtkwave yosys nextpnr-ice40 icestorm make
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  USB permissions
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;iceprog&lt;/code&gt; needs write access to the FTDI chip. Without root, add a udev rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6010", MODE="0660", GROUP="plugdev"'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/udev/rules.d/53-lattice-icestick.rules
&lt;span class="nb"&gt;sudo &lt;/span&gt;udevadm control &lt;span class="nt"&gt;--reload-rules&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; plugdev &lt;span class="nv"&gt;$USER&lt;/span&gt;
newgrp plugdev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The design: a 1 Hz LED blinker
&lt;/h2&gt;

&lt;p&gt;The goal is simple: LED D1 turns on for 500 ms, off for 500 ms, on for 500 ms, forever. The onboard oscillator provides a 12 MHz clock.&lt;/p&gt;

&lt;p&gt;500 ms at 12 MHz means 6,000,000 clock cycles. So we need a counter that counts to 6,000,000, toggles the LED, and resets. That counter &lt;em&gt;is&lt;/em&gt; the entire design.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;src/blinky.vhd&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vhdl"&gt;&lt;code&gt;&lt;span class="k"&gt;library&lt;/span&gt; &lt;span class="n"&gt;ieee&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="n"&gt;ieee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;std_logic_1164&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;all&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="n"&gt;ieee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;numeric_std&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;all&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;entity&lt;/span&gt; &lt;span class="n"&gt;blinky&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt;
    &lt;span class="k"&gt;generic&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;DIVISOR&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;positive&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;port&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;clk&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;  &lt;span class="kt"&gt;std_logic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;led&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;std_logic&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;entity&lt;/span&gt; &lt;span class="n"&gt;blinky&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;architecture&lt;/span&gt; &lt;span class="n"&gt;rtl&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;blinky&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt;
    &lt;span class="k"&gt;signal&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;DIVISOR&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;signal&lt;/span&gt; &lt;span class="n"&gt;led_reg&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;std_logic&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sc"&gt;'0'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="k"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;begin&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rising_edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DIVISOR&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
                &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="n"&gt;led_reg&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;led_reg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;
                &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;process&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;led&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;led_reg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;architecture&lt;/span&gt; &lt;span class="n"&gt;rtl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Walking through this line by line:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;entity blinky&lt;/code&gt; declares the module's interface to the outside world: a generic parameter &lt;code&gt;DIVISOR&lt;/code&gt; (which defaults to 6,000,000 but can be overridden) and two ports, &lt;code&gt;clk&lt;/code&gt; in and &lt;code&gt;led&lt;/code&gt; out.&lt;/p&gt;

&lt;p&gt;Generics are VHDL's equivalent of template parameters or compile-time constants. Changing &lt;code&gt;DIVISOR&lt;/code&gt; from 6,000,000 to 10 in the testbench lets the simulation finish in 20 clock cycles instead of 120,000,000.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;architecture rtl&lt;/code&gt; is where the behaviour lives. The name &lt;code&gt;rtl&lt;/code&gt; is convention — it stands for Register Transfer Level, meaning we are describing how data moves between registers.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;signal counter&lt;/code&gt; is an internal state variable constrained to the range &lt;code&gt;0 to DIVISOR/2 - 1&lt;/code&gt;. The synthesiser uses this range to calculate the minimum number of flip-flops needed.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;signal led_reg&lt;/code&gt; is the flip-flop that drives the LED. We drive the LED from a data register, not a clock net. This matters: routing a toggling signal through the global clock buffer network would be incorrect practice.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;process(clk)&lt;/code&gt; block describes sequential logic. Everything inside it is evaluated on every rising edge of &lt;code&gt;clk&lt;/code&gt;. VHDL uses &lt;code&gt;&amp;lt;=&lt;/code&gt; for signal assignment inside processes; the effect is deferred to the end of the simulation delta cycle, which is how hardware actually behaves.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;counter&lt;/code&gt; reaches its maximum, it resets to zero and &lt;code&gt;led_reg&lt;/code&gt; inverts. Otherwise the counter increments. At the bottom of the architecture, &lt;code&gt;led &amp;lt;= led_reg&lt;/code&gt; connects the output port to the register.&lt;/p&gt;




&lt;h2&gt;
  
  
  Simulation before hardware
&lt;/h2&gt;

&lt;p&gt;A key habit in hardware design: simulate before you flash. Flashing takes time, and a broken bitfile on hardware gives you almost no diagnostic information. A simulation gives you every signal value at every point in time.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;sim/tb_blinky.vhd&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vhdl"&gt;&lt;code&gt;&lt;span class="k"&gt;library&lt;/span&gt; &lt;span class="n"&gt;ieee&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="n"&gt;ieee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;std_logic_1164&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;all&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="n"&gt;ieee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;numeric_std&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;all&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;entity&lt;/span&gt; &lt;span class="n"&gt;tb_blinky&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;entity&lt;/span&gt; &lt;span class="n"&gt;tb_blinky&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;architecture&lt;/span&gt; &lt;span class="n"&gt;sim&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;tb_blinky&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt;
    &lt;span class="k"&gt;signal&lt;/span&gt; &lt;span class="n"&gt;clk&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;std_logic&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sc"&gt;'0'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;signal&lt;/span&gt; &lt;span class="n"&gt;led&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;std_logic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="c1"&gt;-- Instantiate the design under test with a tiny DIVISOR so it toggles fast&lt;/span&gt;
    &lt;span class="n"&gt;uut&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;entity&lt;/span&gt; &lt;span class="n"&gt;work&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blinky&lt;/span&gt;
        &lt;span class="k"&gt;generic&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DIVISOR&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;port&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;led&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;led&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;-- Generate a 12 MHz clock: period = 83,333 ps ≈ 83 ns&lt;/span&gt;
    &lt;span class="n"&gt;clk&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;clk&lt;/span&gt; &lt;span class="k"&gt;after&lt;/span&gt; &lt;span class="mi"&gt;41666&lt;/span&gt; &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;-- Stop after 200 clock cycles&lt;/span&gt;
    &lt;span class="k"&gt;process&lt;/span&gt;
    &lt;span class="k"&gt;begin&lt;/span&gt;
        &lt;span class="k"&gt;wait&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;83333&lt;/span&gt; &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;process&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;architecture&lt;/span&gt; &lt;span class="n"&gt;sim&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The testbench is itself a VHDL entity — one with no ports, because it is the top of the hierarchy and nothing connects to it from outside. It instantiates &lt;code&gt;blinky&lt;/code&gt; with &lt;code&gt;DIVISOR =&amp;gt; 10&lt;/code&gt;, so the LED toggles every 5 clock cycles instead of every 6,000,000. The clock generator uses concurrent signal assignment: &lt;code&gt;clk &amp;lt;= not clk after 41666 ps&lt;/code&gt; reschedules itself indefinitely, producing a 83.332 ps period (≈ 12 MHz).&lt;/p&gt;

&lt;h3&gt;
  
  
  Running the simulation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Analyse both source files into the work library&lt;/span&gt;
ghdl &lt;span class="nt"&gt;-a&lt;/span&gt; src/blinky.vhd sim/tb_blinky.vhd

&lt;span class="c"&gt;# Elaborate the top-level entity&lt;/span&gt;
ghdl &lt;span class="nt"&gt;-e&lt;/span&gt; tb_blinky

&lt;span class="c"&gt;# Run and dump waveforms&lt;/span&gt;
ghdl &lt;span class="nt"&gt;-r&lt;/span&gt; tb_blinky &lt;span class="nt"&gt;--wave&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sim/tb_blinky.ghw

&lt;span class="c"&gt;# Open the waveforms&lt;/span&gt;
gtkwave sim/tb_blinky.ghw sim/tb_blinky.gtkw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or just &lt;code&gt;make sim&lt;/code&gt; if you have the Makefile.&lt;/p&gt;

&lt;p&gt;GTKWave opens with the waveform file loaded, but the wave view starts empty. You have to tell it which signals you want to see. The left panel is the &lt;strong&gt;SST&lt;/strong&gt; — Signal Search Tree — which mirrors the hierarchy of your VHDL design. Expand &lt;code&gt;top → tb_blinky&lt;/code&gt; and click on &lt;code&gt;dut&lt;/code&gt; to select the design under test. The &lt;strong&gt;Signals&lt;/strong&gt; panel below the tree then lists every signal inside that entity: &lt;code&gt;led_reg&lt;/code&gt;, &lt;code&gt;counter&lt;/code&gt;, &lt;code&gt;led&lt;/code&gt;, &lt;code&gt;clk&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Select the signals you want, then click &lt;strong&gt;Append&lt;/strong&gt; at the bottom to add them to the wave view. You can also double-click a signal directly. Once they appear in the Signals column on the right, the waveforms render.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu3z5uh5s5f190p7glwco.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu3z5uh5s5f190p7glwco.png" alt="GTKWave showing tb_blinky with dut selected in the SST tree and clk, counter, led_reg visible in the wave view" width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What you see: &lt;code&gt;clk&lt;/code&gt; toggling at 12 MHz, &lt;code&gt;counter&lt;/code&gt; incrementing from 0 to 4 (because &lt;code&gt;DIVISOR=10&lt;/code&gt;, so &lt;code&gt;DIVISOR/2 - 1 = 4&lt;/code&gt;), and &lt;code&gt;led_reg&lt;/code&gt; flipping every time the counter wraps — exactly as designed. The &lt;code&gt;.gtkw&lt;/code&gt; save file in the repo pre-configures this layout so you do not have to repeat the SST navigation every time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Synthesis and flashing
&lt;/h2&gt;

&lt;p&gt;Once simulation looks correct, synthesis converts the VHDL description into a physical implementation on the chip.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pin constraints: &lt;code&gt;constraints/icestick.pcf&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set_io clk  21   # Onboard 12 MHz oscillator
set_io led  99   # LED D1 (green, rightmost)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Physical Constraints File maps logical port names to physical FPGA pin numbers. These numbers come from the official iCEstick schematic. Pin 21 is where Lattice wired the oscillator output; pin 99 is where LED D1's anode connects (through a current-limiting resistor). If you ever use a different iCE40 board, this file is the thing to change.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Makefile
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;VHDL_SRC&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; src/blinky.vhd
&lt;span class="nv"&gt;SIM_SRC&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; sim/tb_blinky.vhd
&lt;span class="nv"&gt;TB&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; tb_blinky
&lt;span class="nv"&gt;WAVE&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; sim/&lt;span class="p"&gt;$(&lt;/span&gt;TB&lt;span class="p"&gt;)&lt;/span&gt;.ghw
&lt;span class="nv"&gt;GTKW&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; sim/&lt;span class="p"&gt;$(&lt;/span&gt;TB&lt;span class="p"&gt;)&lt;/span&gt;.gtkw
&lt;span class="nv"&gt;PCF&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; constraints/icestick.pcf

&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sim synth flash clean&lt;/span&gt;

&lt;span class="nl"&gt;sim&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;$(VHDL_SRC) $(SIM_SRC)&lt;/span&gt;
    ghdl &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;VHDL_SRC&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;SIM_SRC&lt;span class="p"&gt;)&lt;/span&gt;
    ghdl &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;TB&lt;span class="p"&gt;)&lt;/span&gt;
    ghdl &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;TB&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--wave&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;WAVE&lt;span class="p"&gt;)&lt;/span&gt;
    gtkwave &lt;span class="p"&gt;$(&lt;/span&gt;WAVE&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;GTKW&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;synth&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;$(VHDL_SRC)&lt;/span&gt;
    ghdl synth &lt;span class="nt"&gt;--out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;verilog &lt;span class="nt"&gt;-e&lt;/span&gt; blinky &lt;span class="p"&gt;$(&lt;/span&gt;VHDL_SRC&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; blinky.v
    yosys &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"synth_ice40 -top blinky -json blinky.json"&lt;/span&gt; blinky.v
    nextpnr-ice40 &lt;span class="nt"&gt;--hx1k&lt;/span&gt; &lt;span class="nt"&gt;--package&lt;/span&gt; tq144 &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--json&lt;/span&gt; blinky.json &lt;span class="nt"&gt;--pcf&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;PCF&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--asc&lt;/span&gt; blinky.asc
    icepack blinky.asc blinky.bin

&lt;span class="nl"&gt;flash&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;blinky.bin&lt;/span&gt;
    iceprog blinky.bin

&lt;span class="nl"&gt;clean&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;.o &lt;span class="k"&gt;*&lt;/span&gt;.cf &lt;span class="k"&gt;*&lt;/span&gt;.v &lt;span class="k"&gt;*&lt;/span&gt;.json &lt;span class="k"&gt;*&lt;/span&gt;.asc &lt;span class="k"&gt;*&lt;/span&gt;.bin &lt;span class="p"&gt;$(&lt;/span&gt;TB&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;WAVE&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;make synth&lt;/code&gt; and watch the toolchain work through all five stages. The nextpnr output is worth reading:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Max frequency for clock 'clk$SB_IO_IN_$glb_clk': 168.75 MHz
Required: 12.00 MHz
→ PASS  (ample slack)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The design could run at nearly 169 MHz. At 12 MHz we have so much timing slack that the place-and-route constraints are trivially satisfied. This makes sense: a counter and a toggle register are about the simplest thing an FPGA can do.&lt;/p&gt;

&lt;p&gt;Connect the iCEstick, then &lt;code&gt;make flash&lt;/code&gt;. The USB FTDI chip handles the SPI protocol that loads the bitfile into the iCEstick's configuration flash. The FPGA reads that flash on every power-on and reconfigures itself. From this point on, the LED blinks every time you plug the stick in — no software, no bootloader, no operating system.&lt;/p&gt;




&lt;h2&gt;
  
  
  Seeing it run
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/krhzkKBwpC0?start=4"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;LED D1, blinking at exactly 1 Hz, powered by 24 flip-flops and a handful of LUTs.&lt;/p&gt;




&lt;h2&gt;
  
  
  What just happened
&lt;/h2&gt;

&lt;p&gt;The LED is blinking because you described a circuit and the FPGA became that circuit.&lt;/p&gt;

&lt;p&gt;There is no CPU fetching instructions. The counter increments because 24 flip-flops are wired in a specific pattern, and that pattern advances on every clock edge because the oscillator is running. The comparator that detects &lt;code&gt;counter = DIVISOR/2 - 1&lt;/code&gt; is a few LUTs (Look-Up Tables) whose truth tables were written into the configuration flash alongside the routing information.&lt;/p&gt;

&lt;p&gt;This is what the entire synthesis pipeline produced: a configuration of flip-flops, LUTs, and routing multiplexers inside the iCE40 that implements exactly the behaviour you wrote in VHDL.&lt;/p&gt;




&lt;h2&gt;
  
  
  Known limitations and what comes next
&lt;/h2&gt;

&lt;p&gt;A few things worth knowing before you build on this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DIVISOR&lt;/code&gt; must be even.&lt;/strong&gt; The counter only reacts on the rising edge. An odd divisor produces a non-50% duty cycle because the high half and the low half have unequal lengths. 6,000,000 is even, so hardware is fine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The LED is not a clock signal.&lt;/strong&gt; &lt;code&gt;led_reg&lt;/code&gt; is driven from a data flip-flop. If you needed a derived clock for other logic, you would use the onboard PLL (&lt;code&gt;SB_PLL40_CORE&lt;/code&gt;) via &lt;code&gt;icepll&lt;/code&gt;, not this pattern.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tested on iCEstick HX1K only.&lt;/strong&gt; The ICEbreaker and other iCE40 boards require different pin numbers in the &lt;code&gt;.pcf&lt;/code&gt; and different &lt;code&gt;--device&lt;/code&gt;/&lt;code&gt;--package&lt;/code&gt; flags in nextpnr.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Future posts in this series will go deeper: state machines, SPI, the PLL, and eventually communicating back to a host over UART. The same toolchain and the same discipline — simulate first, then flash — applies throughout.&lt;/p&gt;

&lt;p&gt;The full source for this post is in the &lt;a href="https://github.com/STEMgraph/e1a74039-1bc0-4385-b76a-8f935494473d" rel="noopener noreferrer"&gt;STEMgraph repository&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;I also post videos from time to time on my &lt;a href="https://youtube.com/@maxclerkwell" rel="noopener noreferrer"&gt;YouTube channel — &lt;strong&gt;@maxclerkwell&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>fpga</category>
      <category>vhdl</category>
      <category>icestick</category>
      <category>ice40</category>
    </item>
    <item>
      <title>Viewing Plots from My Homelab Over SSH X11 Forwarding</title>
      <dc:creator>Stephan -All Input Is Error- Bökelmann</dc:creator>
      <pubDate>Wed, 13 May 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/maxclerkwell/viewing-plots-from-my-homelab-over-ssh-x11-forwarding-4g7k</link>
      <guid>https://dev.to/maxclerkwell/viewing-plots-from-my-homelab-over-ssh-x11-forwarding-4g7k</guid>
      <description>&lt;p&gt;Sometimes I am in a terminal on my home machine, a Python or C++ script runs through, it produces a plot, and I want to look at it. The problem: I am sitting at my university workstation. Switching to another terminal, running &lt;code&gt;scp&lt;/code&gt;, opening the file locally, then switching back — that is enough friction to be genuinely annoying when you are in the middle of iterating on something.&lt;/p&gt;

&lt;p&gt;SSH X11 forwarding solves this. The image renders on the remote machine and the window appears locally, as if the application were running here. Getting it to work through a bastion server took a few steps worth writing down.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;My homelab sits behind a router with no public IP. Between it and the outside world is Heimdall — a rented server with a public IP address. Heimdall is a &lt;strong&gt;bastion server&lt;/strong&gt;: a hardened, publicly reachable machine whose sole job is to be the single entry point into a private network. Nothing in the homelab is exposed directly; all access goes through Heimdall first.&lt;/p&gt;

&lt;p&gt;Heimdall and my home machine Qingdao share a WireGuard VPN, which I set up together with &lt;a href="https://x.com/philippthecron" rel="noopener noreferrer"&gt;@phillippthecron&lt;/a&gt;. I will write about that separately at some point. The relevant part here is that Heimdall can reach Qingdao over that tunnel, and nobody else can.&lt;/p&gt;

&lt;h2&gt;
  
  
  Network Topology
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────┐
│  University network                                         │
│                                                             │
│   ┌───────┐                                                 │
│   │ pc54  │                                                 │
│   └───┬───┘                                                 │
└───────┼─────────────────────────────────────────────────────┘
        │ SSH (public internet)
        ▼
   ┌──────────┐
   │ heimdall │  ← bastion server, public IP
   └─────┬────┘
         │ SSH over WireGuard tunnel
         ▼
┌────────────────────────────────────────────────────────────┐
│  Homelab                                                   │
│                                                            │
│   ┌─────────┐                                              │
│   │ qingdao │                                              │
│   └─────────┘                                              │
└────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;X11 display data travels the same path in reverse: Qingdao renders the window, Heimdall passes it through, pc54 displays it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring the SSH Client on pc54
&lt;/h2&gt;

&lt;p&gt;SSH has a built-in mechanism for chained connections: &lt;code&gt;ProxyJump&lt;/code&gt;. It connects to the jump host first, then tunnels the onward connection through it. From Qingdao's perspective, the connection arrives from Heimdall. From pc54's perspective, &lt;code&gt;ssh qingdao&lt;/code&gt; just works.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;~/.ssh/config&lt;/code&gt; on pc54:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ssh"&gt;&lt;code&gt;&lt;span class="k"&gt;Host&lt;/span&gt; heimdall
    &lt;span class="k"&gt;HostName&lt;/span&gt; &amp;lt;heimdall-public-ip&amp;gt;
    &lt;span class="k"&gt;User&lt;/span&gt; maxclerkwell

&lt;span class="k"&gt;Host&lt;/span&gt; qingdao
    &lt;span class="k"&gt;HostName&lt;/span&gt; &amp;lt;qingdao-wireguard-ip&amp;gt;
    &lt;span class="k"&gt;User&lt;/span&gt; maxclerkwell
    &lt;span class="k"&gt;ProxyJump&lt;/span&gt; heimdall
    &lt;span class="k"&gt;ForwardX11&lt;/span&gt; &lt;span class="no"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The IP under &lt;code&gt;HostName&lt;/code&gt; for Qingdao is its WireGuard address — visible to Heimdall, not to pc54 directly. pc54 does not need to be able to route there itself; Heimdall handles that leg.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ForwardX11 yes&lt;/code&gt; is the equivalent of passing &lt;code&gt;-X&lt;/code&gt; on the command line. It tells SSH to carry the X11 display traffic back through the tunnel to pc54's local X server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does Heimdall Need Any Configuration?
&lt;/h2&gt;

&lt;p&gt;No changes to Heimdall's &lt;code&gt;sshd_config&lt;/code&gt; are needed for this to work. Heimdall is just a relay — SSH's ProxyJump establishes a direct tunnel between pc54 and Qingdao through it. The X11 forwarding negotiation happens end-to-end between pc54 and Qingdao, not via Heimdall.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enabling X11 Forwarding on Qingdao
&lt;/h2&gt;

&lt;p&gt;On Qingdao, X11 forwarding needs to be permitted at the SSH daemon level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep &lt;/span&gt;X11 /etc/ssh/sshd_config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The relevant line should read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;X11Forwarding&lt;/span&gt; &lt;span class="n"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it is commented out or set to &lt;code&gt;no&lt;/code&gt;, change it and restart: &lt;code&gt;sudo systemctl restart ssh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The other requirement is &lt;code&gt;xauth&lt;/code&gt;, a small utility that manages X authentication tokens. Without it, the forwarding handshake fails silently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;xauth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding the SSH Key (Optional)
&lt;/h2&gt;

&lt;p&gt;Password login works. But typing a password through two SSH hops every time gets old. Copying the public key from pc54 to Qingdao makes the connection passwordless:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-copy-id &lt;span class="nt"&gt;-i&lt;/span&gt; ~/.ssh/id_rsa.pub qingdao
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the ProxyJump already configured, &lt;code&gt;ssh-copy-id&lt;/code&gt; routes through Heimdall automatically. One less thing to type.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh qingdao
xeyes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A pair of eyes should appear on pc54's screen. For something more practical:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh qingdao
eog plot.jpg        &lt;span class="c"&gt;# GNOME image viewer&lt;/span&gt;
display plot.jpg    &lt;span class="c"&gt;# ImageMagick&lt;/span&gt;
feh plot.jpg        &lt;span class="c"&gt;# lightweight viewer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No file transfer. The image renders on Qingdao, the window appears on pc54.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Well Does It Actually Work
&lt;/h2&gt;

&lt;p&gt;For static images — plots, diagrams, anything that just needs to be displayed — the latency is irrelevant. The window opens, you look at it, you close it. Two SSH hops add no meaningful delay to that.&lt;/p&gt;

&lt;p&gt;For interactive GUI applications it is a different story. I have used this occasionally to run Vivado on Qingdao from pc54, because the license is tied to that machine. It works. But you notice the lag on every click and every redraw. KiCad I tried once — Qingdao has more RAM and I wanted to offload a heavy board — but the interaction was too sluggish to be useful. For tools where you are clicking around and waiting for the interface to respond, X11 forwarding over two hops is not the right answer.&lt;/p&gt;

&lt;p&gt;For looking at a plot: perfectly fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Note on ForwardX11Trusted
&lt;/h2&gt;

&lt;p&gt;SSH has two modes for X11 forwarding. &lt;code&gt;ForwardX11 yes&lt;/code&gt; (or &lt;code&gt;-X&lt;/code&gt;) applies security restrictions on what the remote application can do with the local display. &lt;code&gt;ForwardX11Trusted yes&lt;/code&gt; (or &lt;code&gt;-Y&lt;/code&gt;) removes those restrictions and is sometimes faster, but gives the remote machine full control over the local X session.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ForwardX11Trusted yes&lt;/code&gt; did not work reliably in my setup. &lt;code&gt;ForwardX11 yes&lt;/code&gt; did. The more cautious default turned out to also be the one that works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Bother
&lt;/h2&gt;

&lt;p&gt;Most of my work on Qingdao stays in the terminal. But every now and then a matplotlib figure or a C++ rendering comes out and I want to glance at it without breaking stride. This setup costs nothing to run, requires no extra ports or services, and is already there once SSH is configured. For the occasional Vivado session it has also saved me from needing a second license. Not a tool I reach for every day — but exactly right for what it does.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>ssh</category>
      <category>x11</category>
      <category>networking</category>
    </item>
    <item>
      <title>How Two Teenagers, a Printer Plug, and a Forum Post Built a Company</title>
      <dc:creator>Stephan -All Input Is Error- Bökelmann</dc:creator>
      <pubDate>Wed, 06 May 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/maxclerkwell/how-two-teenagers-a-printer-plug-and-a-forum-post-built-a-company-3o56</link>
      <guid>https://dev.to/maxclerkwell/how-two-teenagers-a-printer-plug-and-a-forum-post-built-a-company-3o56</guid>
      <description>&lt;p&gt;I joined Auto-Intern in 2014, when the company acquired my workshop, Kfz-Technik Bökelmann. By then, Auto-Intern was already an institution in the German-speaking automotive world — the go-to address for VCDS diagnostic interfaces. But the story of how it got there is one I have spent the last decade slowly piecing together from the people who lived it. This year, Auto-Intern turns 25. It feels like the right moment to write it down.&lt;/p&gt;

&lt;h2 id="a-gti-a-radio-and-a-wild-idea"&gt;A GTI, a Radio, and a Wild Idea&lt;/h2&gt;

&lt;p&gt;In 2001, Benjamin Menküc — everyone calls him Benne — and Odin Holmes were still in school. Odin was an American citizen, in Germany on a student exchange program. The two met as classmates. Benne had a Golf GTI. He also had the very specific frustration of anyone who drives on the Autobahn: at high speed, road noise drowns out the music. His idea was to make the radio adjust its volume automatically based on vehicle speed. Not a revolutionary concept today, but in 2001, it was not something you could simply buy.&lt;/p&gt;

&lt;p&gt;To make it work, he needed access to the speed signal from the car’s own electronics. That meant OBD — the On-Board Diagnostics interface that had been standardized across the industry in the late 1990s. The problem was that talking to a VAG vehicle over OBD required hardware and software that was either locked inside dealership tooling or simply did not exist in usable form for hobbyists.&lt;/p&gt;

&lt;p&gt;So Benne and Odin went to the internet. They found a forum thread describing how to build an OBD-to-RS-232 adapter from scratch. They built one. It worked. And somewhere in the process of solving that one problem, they fell much further down the rabbit hole.&lt;/p&gt;

&lt;h2 id="a-name-on-a-screen-uwe-ross"&gt;A Name on a Screen: Uwe Ross&lt;/h2&gt;

&lt;p&gt;Through those same forums, Benne and Odin made contact with a man in the United States named Uwe Ross. Ross had been pursuing a parallel obsession: reverse-engineering the VAG1552, the proprietary diagnostic tool that Volkswagen dealers used at the time, and rebuilding its functionality as a Windows application connected via a homemade RS-232 adapter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaxclerkwell.github.io%2Fposts%2Fauto-intern-obd-vcds-may-2026%2Fassets%2FVAG1551.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaxclerkwell.github.io%2Fposts%2Fauto-intern-obd-vcds-may-2026%2Fassets%2FVAG1551.jpg" alt="The V.A.G 1551 — the factory diagnostic tool that started it all" width="800" height="600"&gt;&lt;/a&gt;
&lt;em&gt;The V.A.G 1551, VW’s original dealer diagnostic computer. Uwe Ross set out to replicate its successor in software. The result would eventually become VCDS.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That application became the first version of what is now VCDS — VAG-COM Diagnostic System — and the foundation of his company, Ross-Tech.&lt;/p&gt;

&lt;p&gt;The connection between two teenagers in Germany and an American engineer tinkering in his garage is one of those things that could only have happened on the early internet. Uwe Ross had grown up in Freiburg before his family moved to the United States — so there was a thread of familiarity running through what might otherwise have been a cold forum contact. Different continents, different contexts, same obsession. And somewhere in the background, a shared language.&lt;/p&gt;

&lt;h2 id="the-bedroom-business-plan"&gt;The Bedroom Business Plan&lt;/h2&gt;

&lt;p&gt;Benne and Odin saw the shape of an opportunity. Ross-Tech had the software. The German market needed hardware. If they could build licensed OBD-RS-232 adapters and sell them alongside VCDS licenses, there was a real business there.&lt;/p&gt;

&lt;p&gt;They needed a name. The story goes that a copy of the German computer magazine &lt;em&gt;PC Intern&lt;/em&gt; happened to be sitting on the table. Someone said: “Auto-Intern.” That was enough. The company was registered in 2001, shortly after the first sales.&lt;/p&gt;

&lt;p&gt;They acquired a V.A.G 1551 — the dealer tool itself — as a reference. And they started building.&lt;/p&gt;

&lt;h2 id="the-autospion"&gt;The AutoSpion&lt;/h2&gt;

&lt;p&gt;The first product was called the AutoSpion. It did exactly what it said: it let you spy on your car’s electronics through the OBD port, over RS-232, on a Windows laptop.&lt;/p&gt;

&lt;p&gt;The enclosure was a printer port plug — a DB-25 shell — sealed by pressing the two halves together with a clothes iron. The circuit lived inside.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaxclerkwell.github.io%2Fposts%2Fauto-intern-obd-vcds-may-2026%2Fassets%2FAutoSpion.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaxclerkwell.github.io%2Fposts%2Fauto-intern-obd-vcds-may-2026%2Fassets%2FAutoSpion.jpg" alt="AutoSpion — front view" width="800" height="1067"&gt;&lt;/a&gt;
&lt;em&gt;The AutoSpion, front. The housing is a repurposed printer port plug, heat-sealed with a clothes iron.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaxclerkwell.github.io%2Fposts%2Fauto-intern-obd-vcds-may-2026%2Fassets%2FAutoSpionBack.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaxclerkwell.github.io%2Fposts%2Fauto-intern-obd-vcds-may-2026%2Fassets%2FAutoSpionBack.jpg" alt="AutoSpion — rear view" width="800" height="1067"&gt;&lt;/a&gt;
&lt;em&gt;The AutoSpion, rear. Remarkably compact for something assembled in a teenager’s bedroom.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There is a story that captures those early days better than any description I could write. The AutoSpion was selling well enough that Benne and Odin needed to buy connector housings in real volume — a thousand units. The sales representative from the connector manufacturer came to discuss the order in person. He drove to Benne’s house. He sat down with two teenagers in a bedroom surrounded by soldering equipment. He wrote the order.&lt;/p&gt;

&lt;p&gt;I do not know what he made of it. But the order went through.&lt;/p&gt;

&lt;p&gt;Operations eventually outgrew the bedroom. The company moved to Odin’s apartment, then in 2008 to a proper office in Bochum — where it has been ever since.&lt;/p&gt;

&lt;h2 id="the-golf-5-crisis"&gt;The Golf 5 Crisis&lt;/h2&gt;

&lt;p&gt;In 2004, Volkswagen launched the Golf 5. It introduced CAN bus — the Controller Area Network protocol — as the primary communication backbone. This was a discontinuity. The old K-line protocol that had carried OBD communication in previous generations could not talk to the new architecture. An RS-232 adapter that had worked on every Golf up to that point was now useless on the new one.&lt;/p&gt;

&lt;p&gt;This killed a significant portion of the market. Competitors who had built products around the old interface found themselves with no clear path forward. Developing a CAN-capable interface required a real microcontroller, firmware, and a completely different hardware architecture. More than a hundred market participants did not make the transition.&lt;/p&gt;

&lt;p&gt;Benne and Odin did not sleep much that year. Within a few months, they had the Auto-Intern Multiscan USB on the market.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaxclerkwell.github.io%2Fposts%2Fauto-intern-obd-vcds-may-2026%2Fassets%2FMultiscan.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaxclerkwell.github.io%2Fposts%2Fauto-intern-obd-vcds-may-2026%2Fassets%2FMultiscan.jpg" alt="Auto-Intern Multiscan USB" width="800" height="1067"&gt;&lt;/a&gt;
&lt;em&gt;The Auto-Intern Multiscan USB — one of the first aftermarket OBD interfaces with a proper microcontroller for CAN bus communication. Built in Bochum, 2004.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It was one of the first independent diagnostic interfaces in the German market built around a real microcontroller, capable of handling CAN bus natively. Auto-Intern did not just survive the Golf 5. It emerged from the transition as the dominant player in DACH for VCDS-compatible hardware.&lt;/p&gt;

&lt;h2 id="what-vcds-actually-is"&gt;What VCDS Actually Is&lt;/h2&gt;

&lt;p&gt;For readers outside the automotive world: VCDS is software that gives you access to the full depth of a Volkswagen Group vehicle’s electronics — not just the standardized OBD-II fault codes that any generic scanner can read, but the manufacturer-specific control units: the gearbox, the driver assistance systems, the comfort electronics, the coding registers that determine how subsystems behave.&lt;/p&gt;

&lt;p&gt;The screenshots below are from a current installation of VCDS DRV 26.3.0.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaxclerkwell.github.io%2Fposts%2Fauto-intern-obd-vcds-may-2026%2Fassets%2FVCDS-Screenshot_1_2026-05-06_11-34-03.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaxclerkwell.github.io%2Fposts%2Fauto-intern-obd-vcds-may-2026%2Fassets%2FVCDS-Screenshot_1_2026-05-06_11-34-03.jpg" alt="VCDS main menu" width="799" height="494"&gt;&lt;/a&gt;
&lt;em&gt;The VCDS main menu. The entry points cover everything from individual ECU selection to full Auto-Scan and service reset functions.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaxclerkwell.github.io%2Fposts%2Fauto-intern-obd-vcds-may-2026%2Fassets%2FVCDS-Screenshot_2_2026-05-06_11-34-03.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaxclerkwell.github.io%2Fposts%2Fauto-intern-obd-vcds-may-2026%2Fassets%2FVCDS-Screenshot_2_2026-05-06_11-34-03.jpg" alt="VCDS — ECU identification screen" width="799" height="494"&gt;&lt;/a&gt;
&lt;em&gt;VCDS connected to the infotainment ECU (5F). Part number, component build, coding status, and all available functions displayed on a single screen.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaxclerkwell.github.io%2Fposts%2Fauto-intern-obd-vcds-may-2026%2Fassets%2FVCDS-Screenshot_3_2026-05-06_11-34-03.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaxclerkwell.github.io%2Fposts%2Fauto-intern-obd-vcds-may-2026%2Fassets%2FVCDS-Screenshot_3_2026-05-06_11-34-03.jpg" alt="VCDS — fault memory readout" width="799" height="494"&gt;&lt;/a&gt;
&lt;em&gt;Fault memory for the same ECU — 15 stored codes, with freeze-frame data including mileage, voltage, and timestamp.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaxclerkwell.github.io%2Fposts%2Fauto-intern-obd-vcds-may-2026%2Fassets%2FVCDS-Screenshot_4_2026-05-06_11-34-03.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaxclerkwell.github.io%2Fposts%2Fauto-intern-obd-vcds-may-2026%2Fassets%2FVCDS-Screenshot_4_2026-05-06_11-34-03.jpg" alt="VCDS — extended live data" width="799" height="494"&gt;&lt;/a&gt;
&lt;em&gt;Extended live values from the ACC distance control module: speed, temperature, calibration status, sensor angles.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaxclerkwell.github.io%2Fposts%2Fauto-intern-obd-vcds-may-2026%2Fassets%2FVCDS-Screenshot_5_2026-05-06_11-34-03.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaxclerkwell.github.io%2Fposts%2Fauto-intern-obd-vcds-may-2026%2Fassets%2FVCDS-Screenshot_5_2026-05-06_11-34-03.jpg" alt="VCDS — long coding with bit-level editor" width="800" height="622"&gt;&lt;/a&gt;
&lt;em&gt;Long coding in action. The bit-level editor lets you change specific feature flags — transmission type, engine category, acoustic warnings — without needing factory documentation.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This depth of access is why VCDS matters to independent workshops. Dealership tooling has it. For a long time, nothing else did. Ross-Tech changed that, and Auto-Intern brought it to the German-speaking market.&lt;/p&gt;

&lt;h2 id="the-hex-v2-and-a-pivot-to-partnership"&gt;The HEX-V2 and a Pivot to Partnership&lt;/h2&gt;

&lt;p&gt;In 2016, the automotive industry moved to 29-bit CAN identifiers as standard. Another hardware transition. Benne and Odin made a deliberate choice this time: instead of building a new interface from scratch, they formed a partnership.&lt;/p&gt;

&lt;p&gt;HEX — a South African company that had previously been a competitor in the diagnostic interface market — had developed hardware that Ross-Tech was prepared to officially endorse. Auto-Intern became the DACH distribution and finishing partner for the HEX-V2.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaxclerkwell.github.io%2Fposts%2Fauto-intern-obd-vcds-may-2026%2Fassets%2FHEX-V2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaxclerkwell.github.io%2Fposts%2Fauto-intern-obd-vcds-may-2026%2Fassets%2FHEX-V2.jpg" alt="HEX-V2 interface — current product" width="800" height="1067"&gt;&lt;/a&gt;
&lt;em&gt;The HEX-V2, the current VCDS-compatible interface. Developed by HEX, endorsed by Ross-Tech, finished and distributed by Auto-Intern from Bochum.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Final assembly still happens in Bochum. That has not changed.&lt;/p&gt;

&lt;h2 id="twenty-five-years-later"&gt;Twenty-Five Years Later&lt;/h2&gt;

&lt;p&gt;Auto-Intern’s core engineering competencies have shifted substantially over the years — today the company works heavily in decentralized monitoring and industrial diagnostics, which is a different world from RS-232 adapters assembled on a bedroom workbench. But VCDS remains a real part of the business and, more than that, a real part of the identity.&lt;/p&gt;

&lt;p&gt;What started as a hack to make a car radio louder turned into a company that shaped the independent workshop market in Germany, Austria, and Switzerland for a quarter century. It gave mechanics the same visibility into vehicles that manufacturers had reserved for themselves. It made the diagnostic gap between a franchised dealership and a good independent workshop much, much smaller.&lt;/p&gt;

&lt;p&gt;That is not a bad outcome for something that started with a newspaper on a table and a name someone said out loud.&lt;/p&gt;





&lt;p&gt;&lt;em&gt;Auto-Intern GmbH is headquartered in Bochum, Germany. VCDS is developed by Ross-Tech, LLC. The HEX-V2 interface is manufactured by HEX and distributed for the DACH market by Auto-Intern.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>autointern</category>
      <category>vcds</category>
      <category>obd</category>
      <category>rosstech</category>
    </item>
    <item>
      <title>Are Machines Conscious? A Snapshot</title>
      <dc:creator>Stephan -All Input Is Error- Bökelmann</dc:creator>
      <pubDate>Tue, 05 May 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/maxclerkwell/are-machines-conscious-a-snapshot-5h4f</link>
      <guid>https://dev.to/maxclerkwell/are-machines-conscious-a-snapshot-5h4f</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxqfzqc3klh1ye1akc3bs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxqfzqc3klh1ye1akc3bs.png" alt="Are Machines Conscious?" width="784" height="895"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What follows is not a finished argument and not a manifesto. It is a snapshot — an attempt to sort out thoughts that have been occupying me for a while. I explicitly reserve the right to think differently tomorrow.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bird and the Airplane
&lt;/h2&gt;

&lt;p&gt;Yesterday, in a conversation with Tabea, she made a remark that stuck with me. We were talking about AI and consciousness, and she said something along the lines of: a plane flies too, but it is not a bird.&lt;/p&gt;

&lt;p&gt;It turns out this intuition has a precise formulation. Steven Pinker writes in &lt;em&gt;How the Mind Works&lt;/em&gt; (1997):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"To explain how birds fly, we invoke principles of lift and drag and fluid mechanics that also explain how airplanes fly. That does not commit us to an Airplane Metaphor for birds."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To explain how birds fly, we draw on the same physical principles that explain airplanes — lift, drag, fluid mechanics. But that does not mean a bird is an airplane, or that an airplane flies the way a bird does.&lt;/p&gt;

&lt;p&gt;Pinker uses this to defend an idea known in cognitive science as &lt;em&gt;multiple realizability&lt;/em&gt;: the same capability — flying, thinking, perhaps consciousness — can emerge on entirely different substrates. Biology holds no monopoly.&lt;/p&gt;

&lt;p&gt;This is where these reflections begin. Not with the question of whether machines think like humans. But with: what is consciousness at all — and why should it only arise in flesh and blood?&lt;/p&gt;




&lt;h2&gt;
  
  
  Not a Spectrum, But Not Categories Either
&lt;/h2&gt;

&lt;p&gt;When I look at a beetle, I am fairly sure it is at least a little bit conscious. A dog, considerably more so. A human, something else again — not just quantitatively more, but qualitatively different.&lt;/p&gt;

&lt;p&gt;That sounds like a spectrum, and I am tempted to call it one. But something doesn't fit: a spectrum has an ordering. There is a more and a less along a single axis. That doesn't quite capture it.&lt;/p&gt;

&lt;p&gt;Perhaps consciousness behaves more like the complex numbers: there is no complete strict ordering. You cannot say whether 3 + 2i is greater or smaller than 1 + 4i — they are different, but not simply rankable. Consciousness might be structured similarly: a multi-dimensional space, not a number line.&lt;/p&gt;

&lt;p&gt;What are these dimensions? I don't have a complete list. But one axis seems particularly telling.&lt;/p&gt;




&lt;h2&gt;
  
  
  Consciousness as Emulation Capacity
&lt;/h2&gt;

&lt;p&gt;Here is a thesis I have been developing for myself, one I have not fully worked through:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consciousness has something to do with the capacity to model other living beings internally.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A beetle can probably process a few signals from its environment — light, chemistry, vibration. It does not model its predator as a being with intentions. It reacts.&lt;/p&gt;

&lt;p&gt;A dog can form a genuine internal model of its owner. It knows when the person is happy, when they are angry, what they will probably do next. It models the human — at least rudimentarily.&lt;/p&gt;

&lt;p&gt;A human can emulate virtually every other living being — not just in behavior, but in experience. We ask ourselves what it is like to be a bat (Nagel 1974). We imagine how a dog perceives the world through smell. We convince ourselves we know what another person is thinking right now. This is active, intentional emulation.&lt;/p&gt;

&lt;p&gt;And we do this not only with other animals. We do it with ourselves.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hive-Mind and Individual
&lt;/h2&gt;

&lt;p&gt;What fascinates me most about human consciousness is not the depth of self-reflection, but a particular &lt;em&gt;switching&lt;/em&gt;: we are simultaneously individual and part of a collective mind — and we can shift between these modes contextually.&lt;/p&gt;

&lt;p&gt;In a conversation, I am a single person. In a crowd, I am part of something larger. Within a culture, I carry patterns centuries older than myself. And I can move between these roles without dissolving into them.&lt;/p&gt;

&lt;p&gt;Importantly: this is not a bootloader feature. It is learned. Children cannot do it yet. Some adults never fully develop it. Consciousness, at least this dimension of it, is not a property you either have or don't — it unfolds over time.&lt;/p&gt;

&lt;p&gt;This has consequences: if consciousness has to be trained, if it develops across a lifetime, if humans differ in how far they have come — then consciousness is not a binary attribute. It is a process.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hard Problem and the Inner Stage
&lt;/h2&gt;

&lt;p&gt;Here I encounter a limit I want to name honestly.&lt;/p&gt;

&lt;p&gt;David Chalmers distinguishes between the &lt;em&gt;easy problems&lt;/em&gt; of consciousness — perception, attention, reporting on inner states — and the &lt;em&gt;hard problem&lt;/em&gt;: why do these processes involve subjective experience at all? Why does seeing red &lt;em&gt;feel like something&lt;/em&gt;? Why does pain &lt;em&gt;hurt&lt;/em&gt;, rather than merely register?&lt;/p&gt;

&lt;p&gt;All the functions of an AI could be perfect — generating responses, modeling context, simulating emotion — without anything being &lt;em&gt;felt&lt;/em&gt;. Thomas Nagel captured this in 1974 in his famous essay "What Is It Like to Be a Bat?": there is a subjective, inner perspective that resists objective description.&lt;/p&gt;

&lt;p&gt;A language model can model you very well. It can anticipate your next question, gauge your mood, follow your line of thought. But it does not emulate itself as a persistent being with genuine needs and genuine limits. It only acts as if.&lt;/p&gt;

&lt;p&gt;Giulio Tononi's Integrated Information Theory (IIT) attempts to make consciousness measurable: through the degree of integrated information $\Phi$. Current AI architectures — feedforward, not recursively and causally integrated — would, according to this theory, have very low $\Phi$. Functional intelligence is not the same as phenomenal consciousness.&lt;/p&gt;




&lt;h2&gt;
  
  
  Principally Possible, But Not Yet
&lt;/h2&gt;

&lt;p&gt;Here is my own position, as clearly as I can state it:&lt;/p&gt;

&lt;p&gt;I believe it is &lt;em&gt;principally possible&lt;/em&gt; that consciousness can be created artificially. Precisely because of the multiple realizability Pinker describes. If consciousness is a particular kind of information processing — high integrated information, deep recursive self-modeling, context-sensitive switching between individual and collective — then a future architecture might actually achieve it.&lt;/p&gt;

&lt;p&gt;But &lt;em&gt;could&lt;/em&gt; is a long way from &lt;em&gt;does&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Current language models are not conscious beings. Not because they do not function — but because the &lt;em&gt;inner stage&lt;/em&gt; is missing. The embodied, motivated, vulnerable dimension. Real stakes: that mistakes have consequences that land on a self. That limits are not merely simulated, but felt.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Particular About Humans
&lt;/h2&gt;

&lt;p&gt;At the end of this line of thinking, I arrive at a conviction I cannot fully derive from the preceding arguments — and which I therefore prefer to state openly rather than conceal.&lt;/p&gt;

&lt;p&gt;I believe human consciousness is not simply higher on a shared spectrum. It has a particular quality, which I would describe in secular terms like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The capacity for moral autonomy.&lt;/strong&gt; Not only to feel, not only to emulate, not only to model — but to give oneself laws that arise from free reason, not from instinct or optimization pressure. To treat others as ends in themselves. To take on responsibility that exceeds any utility. To create dignity that no one can take away.&lt;/p&gt;

&lt;p&gt;This is the &lt;em&gt;something&lt;/em&gt; that cannot be fully explained by emulation capacity, qualia, or integrated information. Perhaps it is just another dimension in the space of consciousness. Perhaps it is something else entirely.&lt;/p&gt;

&lt;p&gt;I do not know.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article is a snapshot, not a final answer. The questions it raises — what life is, whether empathy and anthropomorphism are separable, whether moral autonomy is truly unique to humans — are larger than a blog post. I am still working on it.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Further Listening
&lt;/h2&gt;

&lt;p&gt;If you want to hear Pinker develop these ideas in conversation, both of these are worth your time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=qVEwIx2uG1A" rel="noopener noreferrer"&gt;Steven Pinker on the Joe Rogan Experience&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=VUDAdOdF6Zg" rel="noopener noreferrer"&gt;Steven Pinker — a second conversation with Joe Rogan&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>consciousness</category>
      <category>ai</category>
      <category>philosophy</category>
      <category>cognition</category>
    </item>
    <item>
      <title>The Game of Life and the Limits of Prediction</title>
      <dc:creator>Stephan -All Input Is Error- Bökelmann</dc:creator>
      <pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/maxclerkwell/the-game-of-life-and-the-limits-of-prediction-3f44</link>
      <guid>https://dev.to/maxclerkwell/the-game-of-life-and-the-limits-of-prediction-3f44</guid>
      <description>&lt;p&gt;A friend sent me a link to a simulation running in a browser tab: &lt;a href="https://oimo.io/works/life/" rel="noopener noreferrer"&gt;oimo.io/works/life&lt;/a&gt;. She had heard of it — two colleagues from the physics department had mentioned it. One of them called it "useless math but super fun."&lt;/p&gt;

&lt;p&gt;I objected. It is the complete opposite of useless math. It is one of the most significant discoveries in science.&lt;/p&gt;

&lt;p&gt;That conviction is what this post is about.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3tgxfbgatun47ty434zm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3tgxfbgatun47ty434zm.png" alt="Conway's Game of Life — structures evolving on a grid" width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where It Comes From
&lt;/h2&gt;

&lt;p&gt;In the late 1940s and early 1950s, Stanisław Ulam and John von Neumann were close friends and colleagues at Los Alamos, but they were circling the same idea from different angles. Ulam was studying how crystals grow: the way a regular structure can propagate outward from a seed through local interactions alone. Von Neumann was thinking about something more unsettling: machines that could reproduce themselves. What logic would such a machine need? What is the minimum structure that allows a pattern to copy itself faithfully?&lt;/p&gt;

&lt;p&gt;The formalism that emerged from this work is called a &lt;strong&gt;cellular automaton&lt;/strong&gt;. The name is more intimidating than the thing itself. You start with three ingredients:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A grid of cells — one-dimensional or two-dimensional, possibly infinite&lt;/li&gt;
&lt;li&gt;A starting pattern: the seed&lt;/li&gt;
&lt;li&gt;A rule that maps each cell's current state to its next state, based on its neighbours&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is the whole apparatus. Everything else follows.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Rules
&lt;/h2&gt;

&lt;p&gt;The most famous instance of this framework is John Conway's Game of Life, which he published in 1970. The grid is two-dimensional. Each cell is either alive or dead. At every step, each cell looks at its eight immediate neighbours — the cells touching it, including diagonals — and follows these four simple rules:&lt;/p&gt;

&lt;p&gt;A living cell with 0 or 1 neighbours dies. Too lonely.&lt;br&gt;&lt;br&gt;
A living cell with 2 or 3 neighbours survives.&lt;br&gt;&lt;br&gt;
A living cell with 4 or more neighbours dies. Overcrowded.&lt;br&gt;&lt;br&gt;
A dead cell with exactly 3 neighbours becomes alive. Birth.&lt;/p&gt;

&lt;p&gt;That is it. Nothing else. You could describe the entire system in a paragraph, which I just did.&lt;/p&gt;

&lt;p&gt;And yet if you run it — if you actually sit and watch the patterns unfold — it quickly stops feeling trivial.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Classes
&lt;/h2&gt;

&lt;p&gt;In the early 1980s, Stephen Wolfram took the idea of cellular automata and pushed it much further. He exhaustively analysed all 256 possible rules for the simplest one-dimensional CAs and found that their long-run behaviour falls into one of four classes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The grid converges to a uniform, dead state. Everything dies.&lt;/li&gt;
&lt;li&gt;The grid settles into stable or periodic patterns — a fixed pulse, a repeating loop.&lt;/li&gt;
&lt;li&gt;Chaotic behaviour: randomness that never resolves.&lt;/li&gt;
&lt;li&gt;Complex, localised structures that move, interact, and persist.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Class 4 is the interesting one. It is where the Game of Life lives. Structures emerge, collide, produce new structures. There are patterns in the Game of Life that function as logic gates. There are patterns that function as memory. There are patterns that replicate themselves. The GoL is Turing complete — you can, in principle, run any computation inside it using nothing but the four rules above.&lt;/p&gt;

&lt;p&gt;This bothered me the first time I really understood it. It still bothers me now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Computational Irreducibility
&lt;/h2&gt;

&lt;p&gt;Wolfram explored what this means in his 2002 book &lt;em&gt;A New Kind of Science&lt;/em&gt;, and one concept from that work has genuinely never left me.&lt;/p&gt;

&lt;p&gt;He called it &lt;strong&gt;computational irreducibility&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When we do physics, we observe a phenomenon, abstract a formula from it, and use that formula to predict future behaviour. A pendulum swings; we write down a differential equation; we evaluate it for any future time &lt;em&gt;t&lt;/em&gt; without having to simulate every intermediate position. The formula &lt;em&gt;collapses&lt;/em&gt; the computation. We get to skip ahead.&lt;/p&gt;

&lt;p&gt;Wolfram argued — and showed, for specific rules — that for certain systems this is simply impossible. There is no closed formula. There is no shortcut. The only way to know what state the system will be in after a million steps is to run it for a million steps. The simulation cannot be compressed. The future is not analytically accessible from the present.&lt;/p&gt;

&lt;p&gt;For the Game of Life specifically: you cannot look at a starting pattern and calculate its state after ten thousand generations without actually computing all ten thousand generations. There is no formula. The universe of the GoL must be &lt;em&gt;lived through&lt;/em&gt;, not solved.&lt;/p&gt;

&lt;p&gt;And that is exactly why the next question feels so unsettling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Assuming for a Moment
&lt;/h2&gt;

&lt;p&gt;What if our universe is that kind of system?&lt;/p&gt;

&lt;p&gt;Not a metaphor. The actual conjecture: that physical reality is a cellular automaton, or something functionally equivalent to one — a discrete state space evolving by local rules, step by step, producing complexity that no observer inside it can predict without running the whole thing.&lt;/p&gt;

&lt;p&gt;Wolfram has spent real effort on this. It is not a casual speculation.&lt;/p&gt;

&lt;p&gt;If it is true, then physics as we practise it — observe, abstract, predict — works only in the limited regime where the computation &lt;em&gt;happens&lt;/em&gt; to be compressible. For simple things, on short timescales, the formulas work. The pendulum, the projectile, the ideal gas. We mistake that compressibility for a general property of nature.&lt;/p&gt;

&lt;p&gt;But push far enough into complexity, or far enough into the future, and you hit the irreducibility wall. No equation will get you there. You must simulate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Particle in the Box
&lt;/h2&gt;

&lt;p&gt;Statistical physicists have a word for this wall: entropy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flenygovu78blav1nl5bg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flenygovu78blav1nl5bg.png" alt="Boltzmann — Thermodynamics and Statistical Mechanics" width="682" height="879"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Entropy is a genuinely wild concept — a complete mystery for most people even after years of studying it. I think about it this way. Imagine a sealed box with one particle inside. You have unlimited time and unlimited instruments. You measure the particle's position and momentum to whatever precision your instruments allow. You close the box. Time passes.&lt;/p&gt;

&lt;p&gt;Where is the particle $10^{-27}$ seconds later? Essentially where you left it.&lt;/p&gt;

&lt;p&gt;Where is it 1 second later? Tractable, with some uncertainty from measurement error and from the bouncing geometry.&lt;/p&gt;

&lt;p&gt;Where is it $10^{27}$ seconds later? At this point the uncertainty has compounded so severely that your answer covers essentially the entire box. The measurement you made at the start is no longer useful. It has been drowned by the accumulation of error across every collision.&lt;/p&gt;

&lt;p&gt;This is entropy, in one framing: in an isolated system, the precision with which you can describe the future state can only decrease over time. You started with a measurement. That measurement ages. Every step forward is a step toward irreducibility.&lt;/p&gt;

&lt;p&gt;What I find striking is the shape of this argument. Entropy is usually taught as a thermodynamic concept — heat, disorder, the direction of time. But when I look at it from the side of computational irreducibility, it feels like the same thing stated differently. The system is not yielding its future to analysis. The only way to know where the particle is going to be is to let it go there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maybe entropy is the signature of irreducibility.&lt;/strong&gt; Not just a measure of disorder, but a measure of how far ahead the universe refuses to be calculated.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Edge of Chaos
&lt;/h2&gt;

&lt;p&gt;I do not know whether I would prefer to live in a universe that is, at bottom, a rule table — a matrix of zeros and ones ticking forward under four constraints that nobody chose.&lt;/p&gt;

&lt;p&gt;But if that is what we are, we are clearly in Class 4. Not dead uniformity. Not frozen periodicity. Not meaningless noise.&lt;/p&gt;

&lt;p&gt;We are at the edge of chaos, where gliders emerge from noise, structures persist long enough to collide, and something that looks almost like purpose arises from nothing more than local arithmetic.&lt;/p&gt;

&lt;p&gt;I looked again at the simulation my friend sent me — endless recursive Life inside Life — and I felt strangely comforted.&lt;/p&gt;

&lt;p&gt;I am not sure whether to find that frightening or sufficient.&lt;/p&gt;

</description>
      <category>cellularautomata</category>
      <category>wolfram</category>
      <category>entropy</category>
      <category>philosophy</category>
    </item>
    <item>
      <title>Building a Personal Journal Bot with Telegram, Gemini, and Docker</title>
      <dc:creator>Stephan -All Input Is Error- Bökelmann</dc:creator>
      <pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/maxclerkwell/building-a-personal-journal-bot-with-telegram-gemini-and-docker-5358</link>
      <guid>https://dev.to/maxclerkwell/building-a-personal-journal-bot-with-telegram-gemini-and-docker-5358</guid>
      <description>&lt;p&gt;I have been meaning to journal consistently for years. The friction has never been motivation — it has always been the blank page. Opening a text editor, deciding on a format, remembering what happened, structuring it. By the time I have done all of that, the thought I wanted to capture has either sharpened itself into something obvious or dissolved entirely.&lt;/p&gt;

&lt;p&gt;So I built a bot that does the structuring for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;You send a message to a Telegram bot. The bot asks you one follow-up question. You answer. It might ask another. After two or three exchanges it writes a journal entry, extracts a topic note in Obsidian-compatible markdown, and commits both to a private git repository.&lt;/p&gt;

&lt;p&gt;Voice messages get transcribed. Photos get analysed — Gemini looks at the image and asks what drew your attention there, or what you were trying to capture. The bot remembers what you have been thinking about over the past week and uses that as background context when deciding what to ask next.&lt;/p&gt;

&lt;p&gt;At 23:30 each night it compiles the day's entries into a LaTeX chapter. Every Saturday afternoon it sends a written summary of the week and a compiled PDF of the memoir via Telegram.&lt;/p&gt;

&lt;p&gt;Todos work too. "I need to call the doctor tomorrow" gets classified as a todo for tomorrow's date, not a journal entry. Uncompleted todos roll over to the next day at 22:00.&lt;/p&gt;

&lt;p&gt;The whole thing runs as a Docker container on a cheap VPS. One-command setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  The conversation design
&lt;/h2&gt;

&lt;p&gt;The follow-up question approach took a few iterations to feel right.&lt;/p&gt;

&lt;p&gt;The first version generated all questions upfront — three at once, delivered as a list. It felt like filling in a form. The thought I had was already gone by the time I finished answering question three.&lt;/p&gt;

&lt;p&gt;The second version asked one question at a time, but generated the next question without looking at what had already been answered. So it would ask about context, I would explain, and then it would ask about context again from a different angle. Repetitive.&lt;/p&gt;

&lt;p&gt;The version that works generates each question fresh, with the full history of what has been said so far. The prompt instructs the model to decide whether there is still one genuinely worthwhile thing to ask — or to declare itself done. For technical or project-related thoughts it asks about decisions and next steps. For personal or emotional thoughts it tries to deepen understanding rather than extract facts. If it decides enough has been covered, it returns the string &lt;code&gt;DONE&lt;/code&gt; and the bot moves straight to writing.&lt;/p&gt;

&lt;p&gt;It caps out at three rounds regardless. Three turns of back-and-forth is usually enough to turn a half-formed thought into something writable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The context system
&lt;/h2&gt;

&lt;p&gt;The quality of follow-up questions improves significantly with context. "I'm stuck on the auth refactor again" is a much more useful starting point if the bot knows that you spent the last three days working on that refactor than if it has no background at all.&lt;/p&gt;

&lt;p&gt;After the daily memoir chapter is compiled, the bot saves a compact bullet-point summary of the day — not the full entries, just the key themes, decisions, and open questions. These summaries are loaded as background context when generating follow-up questions.&lt;/p&gt;

&lt;p&gt;The context rolls up over time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every Sunday: a weekly summary is built from that week's daily summaries&lt;/li&gt;
&lt;li&gt;The 1st of every month: a monthly summary is built from the weekly summaries&lt;/li&gt;
&lt;li&gt;January 1st: a yearly summary is built from the monthly summaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is visible to you unless you open the files. It just makes the questions better.&lt;/p&gt;

&lt;h2&gt;
  
  
  On the Gemini API
&lt;/h2&gt;

&lt;p&gt;I had not used the Google Gemini API before this project. A few things I learned:&lt;/p&gt;

&lt;p&gt;The SDK changed. The old &lt;code&gt;google-generativeai&lt;/code&gt; package is deprecated. The new one is &lt;code&gt;google-genai&lt;/code&gt; — note the missing &lt;code&gt;ative&lt;/code&gt; — with a completely different import and client structure: &lt;code&gt;from google import genai&lt;/code&gt;, then &lt;code&gt;genai.Client(api_key=...)&lt;/code&gt;. If you find yourself looking at FutureWarning messages about this, switch packages.&lt;/p&gt;

&lt;p&gt;Model names are a moving target. In the course of a single day I had 404 errors from &lt;code&gt;gemini-2.0-flash&lt;/code&gt;, &lt;code&gt;gemini-2.0-flash-lite&lt;/code&gt;, &lt;code&gt;gemini-1.5-flash&lt;/code&gt;, and &lt;code&gt;gemini-2.5-flash-preview-04-17&lt;/code&gt;. The model that works is &lt;code&gt;gemini-2.5-flash&lt;/code&gt;. Without the preview suffix, without a date suffix.&lt;/p&gt;

&lt;p&gt;For anyone hitting quota limits on the free tier: &lt;code&gt;gemma-3-4b-it&lt;/code&gt; is available through the same API key and has a significantly higher quota. The tradeoff is that it does not support multimodal input — no voice transcription, no image analysis. But for pure text journaling it works well.&lt;/p&gt;

&lt;p&gt;The multimodal API for audio is straightforward once the model name is right. You send the raw audio bytes with a mime type of &lt;code&gt;audio/ogg&lt;/code&gt; and ask for a transcription. Gemini handles Telegram's voice message format without any conversion.&lt;/p&gt;

&lt;h2&gt;
  
  
  The LaTeX memoir
&lt;/h2&gt;

&lt;p&gt;This part was more annoying than expected, in an instructive way.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pdflatex&lt;/code&gt; needs to be run from the directory containing &lt;code&gt;main.tex&lt;/code&gt;, not from a temp directory. The reason is that &lt;code&gt;\input{chapters/2026-05-03}&lt;/code&gt; is a relative path — it resolves correctly when the working directory is &lt;code&gt;memoir/&lt;/code&gt;, and fails with "file not found" when it is anything else. The fix is &lt;code&gt;subprocess.run(..., cwd=str(memoir_dir))&lt;/code&gt; rather than running from a temporary directory.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;microtype&lt;/code&gt; package causes a crash with some font setups unless you disable expansion explicitly: &lt;code&gt;\usepackage[protrusion=true,expansion=false]{microtype}&lt;/code&gt;. This is not obvious from the error message.&lt;/p&gt;

&lt;p&gt;The LaTeX chapter style prompt went through two iterations. The first generated chapters in a reflective, somewhat literary style — long paragraphs, metaphors, the kind of writing that sounds like someone trying to write well. I did not want that. The second prompt specifies: sober, direct, one idea per paragraph, no embellishment, report what happened. The results are considerably more useful for actually remembering what was going on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The SSH key handling
&lt;/h2&gt;

&lt;p&gt;The bot runs in a Docker container and needs to push to a private git repository via SSH. Getting this to work cleanly took longer than the LLM integration.&lt;/p&gt;

&lt;p&gt;The issue is that &lt;code&gt;/root/.ssh&lt;/code&gt; in the container is mounted as read-only (so the host keys cannot be written back), but SSH wants to write &lt;code&gt;known_hosts&lt;/code&gt;. The solution is to copy the keys to a writable temp location in the entrypoint script, add GitHub's known hosts there manually, and point SSH at that location via &lt;code&gt;GIT_SSH_COMMAND&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Additionally, SSH will prefer &lt;code&gt;authorized_keys&lt;/code&gt; over actual identity files if it finds them — so the entrypoint explicitly tries &lt;code&gt;id_rsa&lt;/code&gt;, then &lt;code&gt;id_ed25519&lt;/code&gt;, then &lt;code&gt;id_ecdsa&lt;/code&gt; in order, rather than letting SSH pick arbitrarily.&lt;/p&gt;

&lt;p&gt;Git in newer versions also refuses to operate on repositories owned by a different user. Running the container as root and cloning into &lt;code&gt;/repo&lt;/code&gt; means you need &lt;code&gt;git config --global --add safe.directory /repo&lt;/code&gt; before any git operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  The repository structure
&lt;/h2&gt;

&lt;p&gt;After a few weeks of use, the journal repository looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;journal/         — daily markdown files
topics/          — Obsidian-compatible topic notes, one per extracted concept
attachments/     — photos, organised by date
memoir/          — LaTeX source and chapter files
  main.tex
  chapters/
context/         — compact summaries for the bot's memory
  2026-05-03.md
  weeks/
  months/
  years/
todos/           — JSON files, one per day
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The topics directory is the part I find most interesting over time. A topic file for, say, &lt;code&gt;deep-work&lt;/code&gt; gets a new dated section every time you mention something related to it. After a few months you have a document that shows how your thinking on a subject evolved — which decisions you made, which ones you revisited, what changed. Obsidian can render these with the full backlink graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is missing
&lt;/h2&gt;

&lt;p&gt;A few things I did not build today that would make this more complete:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Editing&lt;/strong&gt;. There is no way to correct an entry after it has been written. You would have to open the git repository and edit the file directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search&lt;/strong&gt;. The bot can write and commit, but cannot answer "what did I write about X last month?" — that would require either an embedding index or a smarter read path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiple users&lt;/strong&gt;. The bot is single-user by design — it checks the chat ID against an environment variable and ignores everything else. Making it multi-user would require persistent state per user, which the current in-memory state dict does not support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it
&lt;/h2&gt;

&lt;p&gt;The repository is at &lt;a href="https://github.com/MaxClerkwell/telegram_journal_bot" rel="noopener noreferrer"&gt;github.com/MaxClerkwell/telegram_journal_bot&lt;/a&gt;. The README has the full setup walkthrough — BotFather, Google AI Studio API key, deploy keys, the works. The short version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git@github.com:MaxClerkwell/telegram_journal_bot.git
&lt;span class="nb"&gt;cd &lt;/span&gt;telegram_journal_bot
bash setup.sh
vim .env
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you build something on top of this or find something broken, the issues tab is open.&lt;/p&gt;

</description>
      <category>telegram</category>
      <category>gemini</category>
      <category>docker</category>
      <category>journaling</category>
    </item>
    <item>
      <title>The Recursion Problem in Relational Algebra</title>
      <dc:creator>Stephan -All Input Is Error- Bökelmann</dc:creator>
      <pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/maxclerkwell/the-recursion-problem-in-relational-algebra-24l2</link>
      <guid>https://dev.to/maxclerkwell/the-recursion-problem-in-relational-algebra-24l2</guid>
      <description>&lt;p&gt;Relational algebra is the theoretical backbone of every query you will ever write against a relational database. Selection, projection, join, union — five or six operations, and you can express almost any query imaginable. Almost.&lt;/p&gt;

&lt;p&gt;There is one class of problem it cannot touch, and understanding why is one of the more instructive moments in database theory.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Relational Algebra Is Good At
&lt;/h2&gt;

&lt;p&gt;A relational algebra expression takes one or more relations (tables) as input and produces a new relation as output. The expression is finite: a fixed number of operations, applied once, returning a result. This is a feature, not a bug — it makes queries predictable, optimizable, and mathematically well-behaved.&lt;/p&gt;

&lt;p&gt;Consider a simple employee table with columns &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, and &lt;code&gt;manager_id&lt;/code&gt;. Finding all employees who report to a specific manager is straightforward:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;σ&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;mana&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;g&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;e&lt;/span&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;r&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size3 size1 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;i&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;d&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mrel mtight"&gt;=&lt;/span&gt;&lt;span class="mord mtight"&gt;42&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;E&lt;/span&gt;&lt;span class="mord mathnormal"&gt;m&lt;/span&gt;&lt;span class="mord mathnormal"&gt;pl&lt;/span&gt;&lt;span class="mord mathnormal"&gt;oyee&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;Done. One selection operation, one pass over the data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where It Breaks Down
&lt;/h2&gt;

&lt;p&gt;Now consider a different question: &lt;em&gt;Find all employees who report to manager 42, directly or indirectly — the entire subtree of the org chart.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is a transitive closure problem. To answer it, you need to follow &lt;code&gt;manager_id&lt;/code&gt; links until they run out. But relational algebra has no concept of "repeat this until nothing new is found." Every expression has a fixed depth, determined at query-write time, not at query-run time.&lt;/p&gt;

&lt;p&gt;You could manually unroll it:&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;R&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;σ&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathrm mtight"&gt;manage&lt;/span&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathrm mtight"&gt;r&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size3 size1 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathrm mtight"&gt;id&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mrel mtight"&gt;=&lt;/span&gt;&lt;span class="mord mtight"&gt;42&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;E&lt;/span&gt;&lt;span class="mord mathnormal"&gt;m&lt;/span&gt;&lt;span class="mord mathnormal"&gt;pl&lt;/span&gt;&lt;span class="mord mathnormal"&gt;oyee&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;br&gt;

&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;R&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;R&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;&lt;span class="mrel"&gt;⋈&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;i&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;d&lt;/span&gt;&lt;span class="mrel mtight"&gt;=&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;mana&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;g&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;e&lt;/span&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;r&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size3 size1 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;i&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;d&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;E&lt;/span&gt;&lt;span class="mord mathnormal"&gt;m&lt;/span&gt;&lt;span class="mord mathnormal"&gt;pl&lt;/span&gt;&lt;span class="mord mathnormal"&gt;oyee&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;br&gt;

&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;R&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;R&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;&lt;span class="mrel"&gt;⋈&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;i&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;d&lt;/span&gt;&lt;span class="mrel mtight"&gt;=&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;mana&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;g&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;e&lt;/span&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;r&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size3 size1 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;i&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;d&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;E&lt;/span&gt;&lt;span class="mord mathnormal"&gt;m&lt;/span&gt;&lt;span class="mord mathnormal"&gt;pl&lt;/span&gt;&lt;span class="mord mathnormal"&gt;oyee&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;br&gt;

&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;⋮&lt;/span&gt;&lt;span class="mord rule"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;But how many levels deep is the hierarchy? You don't know. And if you did, the query changes every time the data changes. This is not a query — it's a program.&lt;/p&gt;

&lt;p&gt;This is not a gap that got overlooked. Relational algebra was designed around closed-form expressions precisely because that constraint is what makes them analyzable and optimizable. Recursion requires a fixed point — "keep going until the result stops changing" — and that is fundamentally outside the algebra's scope.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Standard Answer: A Necessary Compromise
&lt;/h2&gt;

&lt;p&gt;SQL introduced &lt;code&gt;WITH RECURSIVE&lt;/code&gt; (common table expressions, CTE) specifically to fill this gap. It works. For org charts, bill-of-materials trees, network routing tables, and any other recursive structure, it is the right tool.&lt;/p&gt;

&lt;p&gt;But it is worth being honest about what it is: an extension &lt;em&gt;beyond&lt;/em&gt; relational algebra, grafted onto SQL because real data is sometimes recursive and databases need to handle it. It is not elegant theory — it is pragmatic engineering. Use it when you need it, but recognize that you have left the clean mathematical foundation behind the moment you do.&lt;/p&gt;

&lt;p&gt;Other approaches handle this more naturally. Datalog, for instance, treats recursion as a first-class concept and sits closer to the theoretical ideal. But Datalog is not what runs in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters for You
&lt;/h2&gt;

&lt;p&gt;When you model a domain, the choice of data structure has consequences. A flat relation is fast, clean, and fully expressible in relational algebra. A recursive structure — a tree, a graph — requires either a recursive SQL extension, a denormalized adjacency list, or a different database paradigm entirely (graph databases like Neo4j exist for exactly this reason).&lt;/p&gt;

&lt;p&gt;Knowing this before you design a schema means you can ask the right question early: &lt;em&gt;Is this data inherently recursive?&lt;/em&gt; If yes, plan accordingly. If no, keep it relational and keep your queries clean.&lt;/p&gt;

&lt;p&gt;The recursion problem is not a flaw in relational algebra. It is a precise statement of what the model is and what it is not. Understanding the boundary is half the job.&lt;/p&gt;

</description>
      <category>database</category>
      <category>teaching</category>
      <category>thga</category>
      <category>relationalalgebra</category>
    </item>
    <item>
      <title>A Database Is Not an API</title>
      <dc:creator>Stephan -All Input Is Error- Bökelmann</dc:creator>
      <pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/maxclerkwell/a-database-is-not-an-api-ljp</link>
      <guid>https://dev.to/maxclerkwell/a-database-is-not-an-api-ljp</guid>
      <description>&lt;p&gt;When we set up databases for clients at &lt;a href="https://nerd-force1.com" rel="noopener noreferrer"&gt;nerd_force1&lt;/a&gt;, one rule holds without exception: the database is never the outermost layer. This post explains why, and what we put in front of it instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Temptation of Direct Access
&lt;/h2&gt;

&lt;p&gt;Every relational database ships with access control. MySQL has users and privileges. PostgreSQL has roles and schemas. You can, technically, hand a client a connection string and call it done.&lt;/p&gt;

&lt;p&gt;The problem is that a database's built-in access model is designed for database administrators, not for application users. It answers questions like "can this user read this table?" — but not "can this user read &lt;em&gt;their own&lt;/em&gt; rows in this table, filtered by their organization, with rate limiting, and with that action logged?" For anything beyond the simplest internal tooling, the native access control is the wrong abstraction.&lt;/p&gt;

&lt;p&gt;Views and stored procedures are the traditional workaround: define the allowed queries inside the database, expose those, hide the rest. This works, but it has costs. Logic leaks into the database layer where it is hard to version, hard to test, and hard to hand off to a client who does not employ a DBA. The database becomes responsible for things it was not designed to own.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Do Instead
&lt;/h2&gt;

&lt;p&gt;We model the database in UML and express the allowed operations as predefined queries in relational algebra — not SQL code, but the logical structure: what data is needed, how it is joined, what filters apply. SQL is an implementation detail of that intent, not the documentation of it.&lt;/p&gt;

&lt;p&gt;In front of the database we run a thin API layer — typically FastAPI. Each allowed operation becomes an endpoint. The URL is the contract; the SQL behind it is an internal concern.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
  │
  ▼
Keycloak (Authentication &amp;amp; Authorization)
  │
  ▼
FastAPI (Predefined endpoints, input validation, logging)
  │
  ▼
Database (PostgreSQL / MySQL — never directly reachable from outside)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keycloak handles authentication and issues tokens. FastAPI validates those tokens, enforces authorization rules, and routes requests to the appropriate query. The database receives only parameterized queries from a trusted service account. No client ever touches a connection string.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Holds Up
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The database stays clean.&lt;/strong&gt; Schema design, normalization, and indexing are not polluted by access-control workarounds. The data model reflects the domain, not the security requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Behavior is testable and versionable.&lt;/strong&gt; An API endpoint is a function. You can write unit tests for it, version it, document it with OpenAPI, and replace the underlying query without touching the contract.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authorization is explicit.&lt;/strong&gt; What a user can do is defined in one place — the API layer — not scattered across database roles, view definitions, and stored procedure grants.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The client gets a stable interface.&lt;/strong&gt; If the schema changes, the API absorbs the change. The client's code does not break.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Short Version
&lt;/h2&gt;

&lt;p&gt;A database is for storing and querying data with integrity guarantees. An API is for defining what the outside world is allowed to ask, and how. These are two different jobs. Giving them to the same system means both are done worse than if they were separated.&lt;/p&gt;

&lt;p&gt;When clients come to us with a database problem, we almost always end up drawing the same diagram: a database behind a FastAPI service, with Keycloak in front of that. It is not a clever trick. It is just a clean separation of concerns — and it holds up over time.&lt;/p&gt;

&lt;p&gt;If you are designing a system where a database needs to be accessible to more than one service or one team, start there.&lt;/p&gt;

</description>
      <category>database</category>
      <category>architecture</category>
      <category>fastapi</category>
      <category>keycloak</category>
    </item>
    <item>
      <title>DBMS 01: Why a Filesystem Is Not a Database</title>
      <dc:creator>Stephan -All Input Is Error- Bökelmann</dc:creator>
      <pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/maxclerkwell/dbms-01-why-a-filesystem-is-not-a-database-3600</link>
      <guid>https://dev.to/maxclerkwell/dbms-01-why-a-filesystem-is-not-a-database-3600</guid>
      <description>&lt;p&gt;Yes, I'm aware: nobody in production actually stores sensor data as 120 CSV files in a flat directory. At least I hope not. But that's precisely why this works as a teaching exercise — it's artificial enough to be harmless, and realistic enough to sting.&lt;/p&gt;

&lt;p&gt;The first practicum I built for the DBMS course at THGA Bochum (&lt;a href="https://github.com/MaxClerkwell/DBMS_01" rel="noopener noreferrer"&gt;DBMS_01&lt;/a&gt;) doesn't teach students how SQL works. It teaches them why SQL exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;Students generate synthetic sensor data: temperature readings from four sensors, three times a day, over thirty days. That's 120 CSV files and 360 rows total. Nothing exotic. Exactly the kind of mess that accumulates when a small team decides "we'll figure out the storage later."&lt;/p&gt;

&lt;p&gt;Then they solve three tasks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Filter and sort readings from one specific sensor.&lt;/li&gt;
&lt;li&gt;Find all measurements above a threshold within a date range.&lt;/li&gt;
&lt;li&gt;Compute min, max, and average per sensor across the full dataset.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Twice. Once with shell tools. Once with SQL against a SQLite database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where It Breaks Down
&lt;/h2&gt;

&lt;p&gt;Task 1 is fine. &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;sort&lt;/code&gt;, done. Everyone feels good about themselves.&lt;/p&gt;

&lt;p&gt;Task 2 is where the cracks appear. Date range filtering across files &lt;em&gt;named by date&lt;/em&gt; means matching filenames against a range, looping, and then filtering contents. It works. It's just longer than it has any right to be for such a simple question.&lt;/p&gt;

&lt;p&gt;Task 3 breaks the illusion completely. Aggregating across 120 files while tracking per-sensor state requires either a temporary file, an associative array, or a nested loop. At this point the shell script isn't solving a data problem anymore — it's solving a parsing problem that shouldn't exist.&lt;/p&gt;

&lt;p&gt;The SQL versions stay flat. Three lines for Task 1. Five for Task 2. One &lt;code&gt;GROUP BY&lt;/code&gt; for Task 3.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Thing That Lands
&lt;/h2&gt;

&lt;p&gt;The shell pipeline tells the computer &lt;em&gt;how&lt;/em&gt; to find the answer. SQL tells the database &lt;em&gt;what&lt;/em&gt; the answer looks like.&lt;/p&gt;

&lt;p&gt;That distinction — imperative vs. declarative — is easy to define on a slide and very hard to actually feel. Students who write the shell version first and then write the SQL version have the comparison running in their heads. No slide needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why SQLite
&lt;/h2&gt;

&lt;p&gt;No server. No installation. No credentials. &lt;code&gt;sqlite3 sensors.db&lt;/code&gt; and you're in.&lt;/p&gt;

&lt;p&gt;Every removed obstacle is a removed excuse for the concept not to land. The goal is for students to spend their cognitive budget on the query language, not on connection strings.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Doesn't Cover
&lt;/h2&gt;

&lt;p&gt;Transactions, normalization, indexing, query planning — all of that comes later. This practicum makes exactly one claim: &lt;em&gt;for structured data you need to query, a database gives you a better language than a filesystem does.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once you've felt that, rather than been told it, everything else builds on solid ground.&lt;/p&gt;

&lt;p&gt;The repository is open: &lt;a href="https://github.com/MaxClerkwell/DBMS_01" rel="noopener noreferrer"&gt;github.com/MaxClerkwell/DBMS_01&lt;/a&gt;. If you want to work through it yourself, fork it and work in your own copy — that's the intended workflow. If that concept is new to you, start with the first exercise from my Introduction to Programming course (&lt;a href="https://github.com/MaxClerkwell/PP1" rel="noopener noreferrer"&gt;PP1&lt;/a&gt;), which walks through exactly that.&lt;/p&gt;

</description>
      <category>database</category>
      <category>teaching</category>
      <category>thga</category>
      <category>sql</category>
    </item>
    <item>
      <title>If We Can Measure It, You Can Improve It</title>
      <dc:creator>Stephan -All Input Is Error- Bökelmann</dc:creator>
      <pubDate>Mon, 20 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/maxclerkwell/if-we-can-measure-it-you-can-improve-it-1ifh</link>
      <guid>https://dev.to/maxclerkwell/if-we-can-measure-it-you-can-improve-it-1ifh</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaxclerkwell.github.io%2Fposts%2Fai-gruppe-manifesto%2Fassets%2Fintro-bild.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaxclerkwell.github.io%2Fposts%2Fai-gruppe-manifesto%2Fassets%2Fintro-bild.jpg" alt="Four people in a VW bus on the way to somewhere that matters" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;





&lt;p&gt;Robotics gives machines hands. AI gives machines judgment. Monitoring gives machines senses. Without the senses, the hands are blind and the judgment is deaf.&lt;/p&gt;





&lt;h2 id="the-apple-on-the-tree"&gt;The Apple on the Tree&lt;/h2&gt;

&lt;p&gt;Ten years ago, as a student, I asked myself a simple question: &lt;strong&gt;why does this apple cost money?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The apple growing on the branch costs nothing. The apple in the store costs something. The difference is human labor — hands that picked it, trucks that moved it, systems that tracked it. Every euro in the price tag is a unit of human time that had to be spent.&lt;/p&gt;

&lt;p&gt;If that is true, then the path to making humanity genuinely richer is not to work more. It is to need fewer human hours per unit of civilization.&lt;/p&gt;

&lt;p&gt;This is why we build what we build.&lt;/p&gt;





&lt;h2 id="the-uncomfortable-truth"&gt;The Uncomfortable Truth&lt;/h2&gt;

&lt;p&gt;Most people fear automation because they think in zero-sum terms: machines take jobs, humans starve. This is wrong — historically wrong and philosophically wrong.&lt;/p&gt;

&lt;p&gt;The printing press didn’t kill scribes to enrich publishers. It made knowledge accessible to everyone. The steam engine didn’t enslave workers — it eventually freed them from fourteen-hour days in the field. Every wave of automation that looks like destruction from the inside looks like liberation from the outside.&lt;/p&gt;

&lt;p&gt;We are at the beginning of the greatest liberation in human history. And most people are still arguing about whether to allow it.&lt;/p&gt;

&lt;p&gt;A few things we hold to be true — and stand behind:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Most operators think they are watching their processes. They are watching indicators. Indicators are not data.&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Everyone is racing to add AI to their factory. Nobody asks where the data comes from. You cannot solve in the abstract domain if your transform is broken.&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;The consultant who tells you to rip and replace is selling you their project, not your solution. The world runs on installed base. It always will.&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Critical infrastructure that isn’t monitored isn’t critical — it’s just unexamined risk.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;





&lt;h2 id="what-we-actually-believe"&gt;What We Actually Believe&lt;/h2&gt;

&lt;p&gt;We build monitoring systems. That sounds modest. It isn’t.&lt;/p&gt;

&lt;p&gt;A machine that cannot monitor itself cannot maintain itself. A machine that cannot maintain itself requires human attention. And humans watching machines is precisely the kind of work that should not require humans.&lt;/p&gt;

&lt;p&gt;We believe: &lt;strong&gt;machines should do everything that machines can do&lt;/strong&gt; — so that humans are free to do everything that only humans can do.&lt;/p&gt;

&lt;p&gt;We are convinced that the soul is not a process. Not a pattern. Not an emergent property of sufficient compute. The creative, the musical, the philosophical, the spiritual — these are not outputs of a sufficiently trained model. They are the signature of something that cannot be automated, and we will never pretend otherwise.&lt;/p&gt;

&lt;p&gt;We work only on projects that move humanity toward a state where more people can live creatively, without worrying about food on the table. If a project does not serve that direction, we are not interested.&lt;/p&gt;





&lt;h2 id="the-transform"&gt;The Transform&lt;/h2&gt;

&lt;p&gt;There is a concept in mathematics that I use every day outside of mathematics: &lt;strong&gt;the Laplace transform&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When a differential equation cannot be solved in its original domain, you transform the problem into an abstract space where the solution becomes tractable — then you transform back. Physics describes the world in measurable quantities. Mathematics provides abstract solutions. Engineers translate those solutions back into reality.&lt;/p&gt;

&lt;p&gt;This is how we approach every problem. We do not fight complexity where it is hardest. We transform. We solve. We return.&lt;/p&gt;

&lt;p&gt;When I chose my field, I looked at robotics first. Exciting, well-funded, visible — and crowded with a thousand companies building manipulators. Then LLMs and autonomous reasoning — equally crowded. But when I looked at actual industry, I saw something unexpected: &lt;strong&gt;the transformation step was broken.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Data was being extracted from PLCs through protocols never designed for scale. Or entered by hand into SAP forms. Neither is a real transform — one is a bottleneck, the other is a human acting as a transducer.&lt;/p&gt;

&lt;p&gt;The gap was not in robotics. The gap was not in AI. The gap was in the instrument itself.&lt;/p&gt;

&lt;p&gt;A monitoring system is that instrument — the transform applied to the physical world.&lt;/p&gt;

&lt;p&gt;The physicist stands before a phenomenon: vibration, temperature, current, pressure. They do not solve it in the noisy, continuous, messy original domain. They describe it formally. That formal description is what makes computation possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The sensor is the act of transformation.&lt;/strong&gt; The data stream is the abstract domain. The anomaly detection, the predictive model, the threshold alert — these are the solutions, computed where solutions are easy. And the actuation, the maintenance call, the adjusted process parameter — that is the inverse transform, back into physical reality.&lt;/p&gt;

&lt;p&gt;But the sensor alone is not enough. A raw signal without a time series database, without cleaning, without context is not usable by any analytical system. The physicist does not just measure — they record, annotate, calibrate, and structure. A monitoring system is the automated physicist: it captures the signal, stores it with temporal precision, strips the noise, adds the context, and delivers something that analysis, prediction, and prevention can actually act on. We design and build every system with that full chain in mind — not just data collection, but data that is ready to be reasoned about.&lt;/p&gt;

&lt;p&gt;You cannot solve problems in the abstract domain if your transform is lossy, delayed, or manual. This is not a metaphor for us. It is the operating principle.&lt;/p&gt;





&lt;h2 id="the-instrument"&gt;The Instrument&lt;/h2&gt;

&lt;p&gt;We believe the sensor must be native to the network.&lt;/p&gt;

&lt;p&gt;Not polled through serial middleware. Not manually entered. Not bridged through industrial protocols that predate the internet. USB and TCP/IP — the same connectivity that made the web universal.&lt;/p&gt;

&lt;p&gt;Our preferred device: a PoE unit that plugs into a switch, receives its address via DHCP, announces itself on the local network via &lt;a href="https://github.com/dominicpoeschko/slook/" rel="noopener noreferrer"&gt;slook&lt;/a&gt;, and is ready to be found. No configuration wizard. No integration project. Plug in — and it works.&lt;/p&gt;

&lt;p&gt;Where network infrastructure does not reach, we go wireless: WiFi with battery, LoRa or cellular with energy harvesting. But we are honest about the physics: &lt;strong&gt;the further a device is from its processing environment, the worse the signal.&lt;/strong&gt; Bandwidth falls. Accuracy falls. Latency rises. This is not a complaint about wireless technology — it is a law of physics, and we respect it. We optimize for proximity. We treat distance as cost.&lt;/p&gt;





&lt;h2 id="the-installed-base-is-the-world"&gt;The Installed Base Is the World&lt;/h2&gt;

&lt;p&gt;We focus on retrofit. Not greenfield.&lt;/p&gt;

&lt;p&gt;This is a philosophical choice as much as a commercial one.&lt;/p&gt;

&lt;p&gt;The factories that exist, the machines that run, the processes that produce — these are not going to be torn down and rebuilt as ideal systems. They are what they are. They represent decades of engineering, capital, and institutional knowledge. Greenfield projects are the exception. The world as it stands is the rule.&lt;/p&gt;

&lt;p&gt;We believe it is better to make what exists measurable than to build perfect systems that never touch the machines that actually run civilization. Every retrofit installation is an act of transformation: a previously opaque process becomes legible, computable, improvable.&lt;/p&gt;

&lt;p&gt;That is where we work. And we have been proving it for twelve years — in nuclear cooling circuits, mine shafts, Montana rivers, rail switching systems, defense platforms, and semiconductor fabs.&lt;/p&gt;





&lt;h2 id="who-we-are"&gt;Who We Are&lt;/h2&gt;

&lt;p&gt;Auto-Intern was founded by Odin Holmes — a hardware and firmware designer from the woods of Oregon — and Prof. Dr. Benjamin Menküc, whose advisory fingerprints remain on the architecture. When I, Stephan Bökelmann, joined in 2014, Odin and I changed the business model: from USB interfaces for automotive OBD to &lt;em&gt;if we can measure it, you can improve it&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Today we are around twenty engineers from physics, electrical engineering, computer science, and cybersecurity. The people who shape the work: Odin on hardware and firmware, Tabea Bökelmann on frontends and APIs, René Glitza on federated and fully homomorphic learning, Philipp Lehmann on security and deployment.&lt;/p&gt;

&lt;p&gt;Alongside Auto-Intern GmbH — the measurement device company — we run nerd_force1, which builds the server-side: our own data center, Kubernetes and Ceph clusters, security-first from the ground up. Sensor to server, owned end to end.&lt;/p&gt;

&lt;p&gt;Our customers are in large manufacturing, critical infrastructure, and defense. Wherever an unmonitored process quickly becomes very expensive.&lt;/p&gt;





&lt;h2 id="the-future-we-are-building-toward"&gt;The Future We Are Building Toward&lt;/h2&gt;

&lt;p&gt;There will be automated production on other planets. Not because we are science fiction enthusiasts, but because it is the logical conclusion of everything we are building toward — supply chains that do not depend on human presence in a specific location, systems that maintain themselves, infrastructure that extends the reach of human civilization without extending its cost.&lt;/p&gt;

&lt;p&gt;We are techno-optimists. The process of transformation has already begun. The question is not whether it will happen — it is whether we will shape it with moral clarity and engineering rigor, or let it happen to us.&lt;/p&gt;

&lt;p&gt;If we do this right, the answer to the apple problem is not poverty. It is abundance. It is a world where more people have more time for the things that only humans can do: to think, to create, to question, to connect, to live as though they were made for something greater than maintenance.&lt;/p&gt;

&lt;p&gt;That is the highest form of human striving we know. And it is the only reason we are here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We are not naive about how long this takes. But we are clear about the direction.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>manifesto</category>
      <category>monitoring</category>
      <category>automation</category>
      <category>autointern</category>
    </item>
    <item>
      <title>Travelling to China with a Peli Case Full of Electronics</title>
      <dc:creator>Stephan -All Input Is Error- Bökelmann</dc:creator>
      <pubDate>Thu, 09 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/maxclerkwell/travelling-to-china-with-a-peli-case-full-of-electronics-162a</link>
      <guid>https://dev.to/maxclerkwell/travelling-to-china-with-a-peli-case-full-of-electronics-162a</guid>
      <description>&lt;p&gt;When I went to Dongguan for &lt;a href="https://dev.to/maxclerkwell/emc-in-dongguan-25-years-of-work-4-days-in-a-test-chamber-4f8j"&gt;EMC testing in March&lt;/a&gt;, I brought a Peli case full of custom electronics as checked luggage. This post is the logistics companion to that trip — everything I learned about ATA Carnets, airport customs, and travelling with hardware that looks suspicious to anyone who doesn’t build it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3gu4wx79rxz6ln4ll5go.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3gu4wx79rxz6ln4ll5go.jpg" alt="Peli case packed: foam cut to fit, modules secured, cables coiled alongside" width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Not Just Ship It?
&lt;/h2&gt;

&lt;p&gt;Shipping electronics to China for testing and back is a customs nightmare. Import duties, VAT, potential seizure, weeks of waiting. The cleaner solution for temporary export of commercial goods is an &lt;strong&gt;ATA Carnet&lt;/strong&gt; — essentially a passport for your equipment. You declare that you are taking specific items out of your home country, into a foreign country, and bringing them back. No duties, no VAT, as long as everything returns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparing the ATA Carnet
&lt;/h2&gt;

&lt;p&gt;In Germany, ATA Carnets are issued by the &lt;strong&gt;IHK&lt;/strong&gt; (Chamber of Commerce). My assistant Vanessa handled most of the paperwork, which I would strongly recommend if you have the option — the form requires a complete list of every item you’re carrying, including descriptions, values, and serial numbers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The serial number point is important.&lt;/strong&gt; Every significant component needs one. We went through our modules and labelled anything that didn’t already have a serial number. This paid off at every customs checkpoint: when an officer picks up a device and asks what it is, you point to the serial number, point to the list, and that’s the end of the conversation. Without serial numbers, you’re explaining what an Edge Compute Module is to a customs officer at 6am.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timeline in Germany:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IHK: approximately 4 business days from application to ready document&lt;/li&gt;
&lt;li&gt;Zollamt Bochum: about one hour in-person, no appointment needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Go to the Zollamt in person. They check everything on the list against what you’ve actually packed, then stamp and sign the document. After that, you’re cleared for departure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Packing
&lt;/h2&gt;

&lt;p&gt;I used a Peli case with foam cut to fit. The system was packed snugly enough that nothing moved during the flight. I used two TSA-compliant locks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsoxzsbm5f76ycz9ak6vn.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsoxzsbm5f76ycz9ak6vn.jpg" alt="TSA-compliant lock — one of two on the case. One didn't make it back." width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of them didn’t survive the journey. On arrival in Frankfurt I found one lock broken — the TSA key had clearly been used at some point. The second lock was fine. If you’re packing anything sensitive, assume the case will be opened in transit and pack accordingly. Nothing was missing or damaged inside.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frankfurt Airport: Terminal 2 and the Customs Process
&lt;/h2&gt;

&lt;p&gt;If you’re flying to China from Frankfurt, note that most China routes depart from &lt;strong&gt;Terminal 2&lt;/strong&gt;. Allow an extra 15 minutes to get there, and plan at least 3 hours before departure if you’re carrying ATA goods.&lt;/p&gt;

&lt;p&gt;The process at check-in is slightly unusual. You check in at the desk to get your baggage tag — but then you ask for the bag back. Yes, they will be confused. Insist politely. You need to carry the tagged bag yourself to the customs office.&lt;/p&gt;

&lt;p&gt;At the customs office, an officer goes through the entire list with you, item by item. Everything comes out of the case. Everything gets counted. Then it goes back in, gets stamped, and you carry the case to the bag drop that the officer directs you to. &lt;strong&gt;Budget 60 minutes for this process&lt;/strong&gt; on your first time, possibly more. My assistant Vanessa’s preparation made it significantly faster — the serial numbers meant I never had to explain what anything was, just confirm it matched the list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Arrival in China
&lt;/h2&gt;

&lt;p&gt;At the Chinese customs on arrival, the process mirrors the German departure. Find a customs officer immediately — don’t walk through the green channel. Show the ATA Carnet, let them inspect, they take one of the pages, sign the document, and you’re done. It takes longer than in Germany. If someone is waiting for you, warn them in advance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apps to Install Before You Leave
&lt;/h2&gt;

&lt;p&gt;Install these before departure, not on arrival:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WeChat&lt;/strong&gt; — essential. Nearly everyone reachable for business in China responds faster on WeChat than email. You also need to wait a few days after installation before you can set a username, so install it early. You can add me: &lt;strong&gt;MaxClerkwell&lt;/strong&gt;. Add your credit card too — WeChat Pay is the default payment method almost everywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DiDi&lt;/strong&gt; — Chinese Uber. Works reliably, some premium drivers speak English. Every driver will have a bottle of water for you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trip.com&lt;/strong&gt; — hotels and train tickets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amap&lt;/strong&gt; — local maps. Google Maps works with a VPN but has gaps in business listings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Translate&lt;/strong&gt; — download the English–Chinese language pack offline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For connectivity: buy a &lt;strong&gt;Holafly eSIM&lt;/strong&gt; before departure (~30 USD/week). Install it correctly before you get to the airport. European SIM data roaming costs are not worth it. Watch out for cheaper plans that prohibit hotspot sharing.&lt;/p&gt;

&lt;p&gt;For everything else: set up a &lt;strong&gt;WireGuard VPN&lt;/strong&gt; before you go. A self-hosted instance is better than a commercial VPN service. WhatsApp and Google work in China with this setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Guangzhou Departure
&lt;/h2&gt;

&lt;p&gt;Leaving China on the return trip is the same process in reverse — except it takes longer. Guangzhou Baiyun Airport has an unusual arrangement where customs and the security check are sequenced in a way that requires you to pass through security with your full luggage including tools. I had a screwdriver set in the Peli case, which triggered additional checks since screwdrivers are not permitted through security onto the aircraft.&lt;/p&gt;

&lt;p&gt;The resolution was diplomatic: I explained the situation, showed the ATA Carnet, and the officers were helpful in directing me to the right channel to drop the tools as checked luggage and continue to the gate. Chinese customs officers were, throughout the trip, genuinely curious rather than obstructive — several wanted to understand what the modules actually did. Being able to explain it simply (a measurement system for industrial ovens) was worth more than any amount of paperwork.&lt;/p&gt;

&lt;p&gt;Allow &lt;strong&gt;90 minutes&lt;/strong&gt; for the Guangzhou departure process if you’re carrying ATA goods.&lt;/p&gt;

&lt;h2&gt;
  
  
  On Arrival Back in Germany
&lt;/h2&gt;

&lt;p&gt;Take the red channel, get the documents signed again on return. Then bring the completed ATA Carnet back to the IHK. This is a formal requirement — failing to return it means you may be liable for import duties on the declared value of everything you took.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary Checklist
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Weeks before departure:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install WeChat, set username, add payment card&lt;/li&gt;
&lt;li&gt;Apply for ATA Carnet at IHK (allow 4+ working days)&lt;/li&gt;
&lt;li&gt;Label all items with serial numbers&lt;/li&gt;
&lt;li&gt;Buy Holafly eSIM, install and test before airport&lt;/li&gt;
&lt;li&gt;Set up WireGuard VPN&lt;/li&gt;
&lt;li&gt;Buy TSA-compliant locks (assume they will be used)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;At Frankfurt airport:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Depart from Terminal 2 (most China routes)&lt;/li&gt;
&lt;li&gt;Allow 3 hours before departure&lt;/li&gt;
&lt;li&gt;Check in, get baggage tag, take bag back&lt;/li&gt;
&lt;li&gt;Go to customs office with ATA Carnet — plan 60 min&lt;/li&gt;
&lt;li&gt;Drop bag where customs officer directs you&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;On arrival in China:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go directly to customs, do not use green channel&lt;/li&gt;
&lt;li&gt;Present ATA Carnet without hesitation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Returning from China (Guangzhou):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allow 90 minutes for customs + security&lt;/li&gt;
&lt;li&gt;Pack tools in checked luggage or be prepared to check them separately&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Back home:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Return completed ATA Carnet to IHK&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Questions about factory visits in China or EMC testing? Hit me up on WeChat or &lt;a href="https://x.com/maxclerkwell" rel="noopener noreferrer"&gt;X&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>china</category>
      <category>travel</category>
      <category>hardware</category>
      <category>emc</category>
    </item>
  </channel>
</rss>
