<?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>Forem: Ruslan Manov</title>
    <description>The latest articles on Forem by Ruslan Manov (@john_smith_9ff0ff4cfcffdc).</description>
    <link>https://forem.com/john_smith_9ff0ff4cfcffdc</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3744446%2F5e11b41b-461c-41ed-b878-f3d20438b419.jpg</url>
      <title>Forem: Ruslan Manov</title>
      <link>https://forem.com/john_smith_9ff0ff4cfcffdc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/john_smith_9ff0ff4cfcffdc"/>
    <language>en</language>
    <item>
      <title>The Amnesiac That Learned to Remember — Building a Brain for Claude Code</title>
      <dc:creator>Ruslan Manov</dc:creator>
      <pubDate>Mon, 06 Apr 2026 17:03:20 +0000</pubDate>
      <link>https://forem.com/john_smith_9ff0ff4cfcffdc/the-amnesiac-that-learned-to-remember-building-a-brain-for-claude-code-1ok6</link>
      <guid>https://forem.com/john_smith_9ff0ff4cfcffdc/the-amnesiac-that-learned-to-remember-building-a-brain-for-claude-code-1ok6</guid>
      <description>&lt;p&gt;**&lt;u&gt;&lt;/u&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What happens when you give an AI coding assistant persistent memory across sessions, machines, and projects? sqlite-memory-mcp turns Claude Code from a brilliant amnesiac into something that remembers&lt;br&gt;
**&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;It was 2:47 AM when Claude said something it shouldn't have known.&lt;/p&gt;

&lt;p&gt;"The bridge sync conflict you hit last Tuesday — the one where two sessions overwrote the same entity — I fixed the root cause in db_utils.py. Want me to show you the diff?"&lt;/p&gt;

&lt;p&gt;I hadn't mentioned that bug. Not in this session. Not in this conversation. The last time I'd touched that code was six days ago, in a different terminal, on a different machine.&lt;/p&gt;

&lt;p&gt;Claude remembered.&lt;/p&gt;

&lt;p&gt;Not because it has long-term memory by default. It doesn't. Every Claude Code session starts blank — a brilliant amnesiac. Ask it about yesterday's work and it gives you a polite, empty stare.&lt;/p&gt;

&lt;p&gt;Unless you give it a brain.&lt;/p&gt;

&lt;p&gt;--&lt;/p&gt;

&lt;p&gt;sqlite-memory-mcp started because I broke something.&lt;/p&gt;

&lt;p&gt;Three Claude Code sessions running in parallel. All writing to the same JSONL memory file. The file corrupted silently — half-written JSON lines, truncated observations, entities that existed in one session and vanished in another.&lt;/p&gt;

&lt;p&gt;JSONL doesn't do concurrent writes. It doesn't do transactions. It doesn't do recovery. It's a format designed for append-only logs, pressed into service as a database by developers who needed something simple.&lt;/p&gt;

&lt;p&gt;I needed something real.&lt;/p&gt;

&lt;p&gt;v0.1.0 was twelve MCP tools and a SQLite database with WAL mode. Write-ahead logging meant multiple sessions could read and write simultaneously without corruption. FTS5 gave full-text search with BM25 ranking. The foundation was boring on purpose — SQLite has been running in production on every smartphone on Earth for two decades. It doesn't break.&lt;/p&gt;

&lt;p&gt;That was supposed to be it. A fix. Ship it, move on.&lt;/p&gt;

&lt;p&gt;It wasn't.&lt;/p&gt;

&lt;p&gt;--&lt;/p&gt;

&lt;p&gt;The first thing that happened was sessions.&lt;/p&gt;

&lt;p&gt;Claude doesn't know it's Claude. It doesn't know this is session #47 on project "trading-bot" and that session #46 ended with a failing test in portfolio_manager.py. Every session is a fresh start, a new mind, a newborn with a PhD.&lt;/p&gt;

&lt;p&gt;Session recall changed that. sqlite-memory-mcp now tracks which session created which entities, what tools were used, what the conversation context looked like. When Claude starts a new session, it can query: "What was I working on last time in this project?"&lt;/p&gt;

&lt;p&gt;The answer comes back in milliseconds. Full context. The amnesiac remembers.&lt;/p&gt;

&lt;p&gt;--&lt;/p&gt;

&lt;p&gt;Then came tasks.&lt;/p&gt;

&lt;p&gt;Not tasks for humans — tasks for Claude. A structured task system where one session can leave work for the next. "The FTS5 injection fix is half-done. The sanitization function works but the tests aren't written yet. Priority: high."&lt;/p&gt;

&lt;p&gt;Next session picks it up. No human has to re-explain. No context is lost. The AI hands off to its future self like a relay runner passing a baton — except the runner dissolves after every lap and a new one materializes at the starting line.&lt;/p&gt;

&lt;p&gt;Task tray UI in PyQt6 sits on your desktop. Kanban board renders as HTML. You can see what Claude is thinking about, what it left unfinished, what it flagged as blocked.&lt;/p&gt;

&lt;p&gt;--&lt;/p&gt;

&lt;p&gt;Bridge sync was the inflection point.&lt;/p&gt;

&lt;p&gt;Two machines. Home desktop running Fedora, laptop on the train. Same memory, synchronized through a git repository. Entity changes push to the bridge, pull on the other side. Lamport clocks for causal ordering. Machine IDs for conflict detection.&lt;/p&gt;

&lt;p&gt;Claude on the laptop continues where Claude on the desktop stopped. Same memory. Same task queue. Same knowledge graph. Different hardware, different continent, same mind.&lt;/p&gt;

&lt;p&gt;The developer on the train opens Claude Code and says: "What did I do this morning?"&lt;/p&gt;

&lt;p&gt;Claude answers. Accurately. With file paths, function names, and the exact commit hash where the work stopped.&lt;/p&gt;

&lt;p&gt;--&lt;/p&gt;

&lt;p&gt;At v3.4.0, the numbers look like this:&lt;/p&gt;

&lt;p&gt;54 MCP tools across 7 focused servers. SQLite WAL for concurrency. FTS5 BM25 search with optional semantic fusion through sqlite-vec. Session tracking, structured tasks, bridge sync, collaboration workflows, entity linking, intelligence layer, causal event ledger with Lamport clocks.&lt;/p&gt;

&lt;p&gt;One local SQLite file. No cloud service. No API key. No monthly bill. No data leaving your machine unless you explicitly push to the bridge.&lt;/p&gt;

&lt;p&gt;The design constraint never changed: local-first, private by default.&lt;/p&gt;

&lt;p&gt;--&lt;/p&gt;

&lt;p&gt;3 AM. Claude finishes the refactor I asked for. It creates a task for tomorrow: "Run the full test suite after the schema migration. Check bridge compatibility."&lt;/p&gt;

&lt;p&gt;I close the terminal. The session dies. Claude's mind evaporates.&lt;/p&gt;

&lt;p&gt;But the memory persists. In a WAL-mode SQLite database on my local disk. Indexed. Searchable. Synchronized. Waiting for the next session to wake up, query the graph, and pick up exactly where the last one left off.&lt;/p&gt;

&lt;p&gt;The amnesiac doesn't forget anymore.&lt;/p&gt;

&lt;p&gt;Repo: github.com/RMANOV/sqlite-memory-mcp&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>sqlite</category>
      <category>python</category>
      <category>claude</category>
    </item>
    <item>
      <title>How a SQLite WAL Fix Grew into a 54-Tool MCP Memory Stack</title>
      <dc:creator>Ruslan Manov</dc:creator>
      <pubDate>Mon, 06 Apr 2026 16:56:22 +0000</pubDate>
      <link>https://forem.com/john_smith_9ff0ff4cfcffdc/how-a-sqlite-wal-fix-grew-into-a-54-tool-mcp-memory-stack-4nkl</link>
      <guid>https://forem.com/john_smith_9ff0ff4cfcffdc/how-a-sqlite-wal-fix-grew-into-a-54-tool-mcp-memory-stack-4nkl</guid>
      <description>&lt;p&gt;**&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;u&gt;How a SQLite WAL Fix Grew into a 54-Tool MCP Memory Stack&lt;/u&gt;
&lt;/h3&gt;

&lt;p&gt;**&lt;/p&gt;

&lt;h2&gt;
  
  
  sqlite-memory-mcp started as a safer replacement for JSONL-based MCP memory. At v3.4.0 it is a 54-tool SQLite stack with tasks, bridge sync, collaboration, public-knowledge workflows, and optional hybrid search
&lt;/h2&gt;

&lt;p&gt;**&lt;/p&gt;




&lt;h1&gt;
  
  
  How a SQLite WAL Fix Grew into a 54-Tool MCP Memory Stack
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;sqlite-memory-mcp&lt;/code&gt; started with a narrow goal: stop local memory corruption when&lt;br&gt;
multiple Claude Code sessions touch the same store.&lt;/p&gt;

&lt;p&gt;The official memory-server pattern is simple, but a flat file becomes fragile as&lt;br&gt;
soon as more than one process writes to it. I wanted the same local-first feel,&lt;br&gt;
but with transactional storage, better search, and room to grow.&lt;/p&gt;

&lt;p&gt;SQLite was the obvious starting point.&lt;/p&gt;

&lt;p&gt;By v3.4.0, that starting point has grown into a broader stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;54 MCP tools&lt;/li&gt;
&lt;li&gt;7 focused servers plus an optional unified server&lt;/li&gt;
&lt;li&gt;SQLite WAL for concurrent local access&lt;/li&gt;
&lt;li&gt;FTS5 BM25 search, with optional semantic fusion through &lt;code&gt;sqlite-vec&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;session recall and project search&lt;/li&gt;
&lt;li&gt;structured task management&lt;/li&gt;
&lt;li&gt;git-based bridge sync&lt;/li&gt;
&lt;li&gt;collaborator and public-knowledge workflows&lt;/li&gt;
&lt;li&gt;entity linking and context/intelligence tools&lt;/li&gt;
&lt;li&gt;an optional PyQt6 task tray app&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why SQLite was the right base layer
&lt;/h2&gt;

&lt;p&gt;For this kind of workflow, SQLite buys a lot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one local database file&lt;/li&gt;
&lt;li&gt;no daemon to run&lt;/li&gt;
&lt;li&gt;no cloud dependency&lt;/li&gt;
&lt;li&gt;ACID transactions&lt;/li&gt;
&lt;li&gt;WAL mode for concurrent readers and writers&lt;/li&gt;
&lt;li&gt;FTS5 in standard SQLite&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point was never "SQLite beats every database".&lt;/p&gt;

&lt;p&gt;The point was: for a local MCP memory stack that lives next to Claude Code,&lt;br&gt;
SQLite gives you reliability, search, and portability without introducing more&lt;br&gt;
infrastructure than the problem needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The release progression in plain English
&lt;/h2&gt;

&lt;p&gt;Here is the shortest accurate summary of how the project evolved.&lt;/p&gt;

&lt;h3&gt;
  
  
  v0.1.0: replace JSONL with SQLite WAL
&lt;/h3&gt;

&lt;p&gt;The first release shipped 12 tools in one server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the 9 core memory tools from the official MCP server&lt;/li&gt;
&lt;li&gt;&lt;code&gt;session_save&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;session_recall&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;search_by_project&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This made the project useful immediately: same core memory workflow, but backed&lt;br&gt;
by SQLite with WAL and FTS5.&lt;/p&gt;

&lt;h3&gt;
  
  
  v0.2.0: move memory between machines with git
&lt;/h3&gt;

&lt;p&gt;The next release added bridge sync:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bridge_push&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bridge_pull&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bridge_status&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was the first step from "single-machine memory" toward "local-first memory&lt;br&gt;
that can travel".&lt;/p&gt;

&lt;h3&gt;
  
  
  v0.3.0 and v0.4.0: tasks and desktop workflow
&lt;/h3&gt;

&lt;p&gt;v0.3.0 added task management and HTML kanban reporting.&lt;/p&gt;

&lt;p&gt;v0.4.0 added the PyQt6 task tray app and utility scripts around the same SQLite&lt;br&gt;
database.&lt;/p&gt;

&lt;p&gt;At that point the project was no longer just a memory backend. It became a&lt;br&gt;
practical daily workflow tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  v0.6.0 through v0.9.0: collaboration and public knowledge
&lt;/h3&gt;

&lt;p&gt;The next wave added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;collaborator management&lt;/li&gt;
&lt;li&gt;queued knowledge sharing&lt;/li&gt;
&lt;li&gt;review flows for imported knowledge&lt;/li&gt;
&lt;li&gt;public-knowledge publishing requests&lt;/li&gt;
&lt;li&gt;ratings and verification metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One detail worth stating carefully: these workflows are review-oriented. The&lt;br&gt;
useful part is not "viral sharing". The useful part is that shared knowledge can&lt;br&gt;
be staged, inspected, and accepted deliberately.&lt;/p&gt;

&lt;h3&gt;
  
  
  v3.0.0: intelligence-layer expansion
&lt;/h3&gt;

&lt;p&gt;v3.0.0 was the large historical expansion point.&lt;/p&gt;

&lt;p&gt;It introduced the context/intelligence layer on top of the existing memory,&lt;br&gt;
tasks, bridge, and collaboration features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;task/entity linking&lt;/li&gt;
&lt;li&gt;context assessment and resume flows&lt;/li&gt;
&lt;li&gt;candidate-claim extraction and promotion&lt;/li&gt;
&lt;li&gt;context-pack building&lt;/li&gt;
&lt;li&gt;impact explanation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Important footnote: v3.0.0 shipped 49 tools in one monolithic server. The later&lt;br&gt;
54-tool split-server layout came after that.&lt;/p&gt;

&lt;h3&gt;
  
  
  v3.1.x to v3.4.0: split architecture, hybrid search, and hardening
&lt;/h3&gt;

&lt;p&gt;The current line added or stabilized several things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;split into focused MCP servers to make tool exposure more manageable&lt;/li&gt;
&lt;li&gt;optional unified server for people who want one process&lt;/li&gt;
&lt;li&gt;optional hybrid search with &lt;code&gt;sqlite-vec&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;recurring task support and more task/context integration&lt;/li&gt;
&lt;li&gt;security and hardening fixes across bridge, collaboration, schema, and search&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What the current architecture actually looks like
&lt;/h2&gt;

&lt;p&gt;At v3.4.0 the project exposes 50 tools across these servers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sqlite_memory&lt;/code&gt; — core 9 memory tools&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sqlite_tasks&lt;/code&gt; — task CRUD and task workflow tools&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sqlite_session&lt;/code&gt; — session recall and context-health tools&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sqlite_bridge&lt;/code&gt; — bridge sync and shared-task flows&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sqlite_collab&lt;/code&gt; — collaborator and public-knowledge tools&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sqlite_entity&lt;/code&gt; — task/entity linking and entity-maintenance helpers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sqlite_intel&lt;/code&gt; — context and intelligence tools&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sqlite_unified&lt;/code&gt; — optional all-in-one server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That split matters because the project outgrew the original one-file server.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed in the latest hardening cycle
&lt;/h2&gt;

&lt;p&gt;The recent v3.3.x line is not about flashy new marketing bullets. It is about&lt;br&gt;
making the stack safer and more predictable.&lt;/p&gt;

&lt;p&gt;Those tags include fixes for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an FTS5 injection issue&lt;/li&gt;
&lt;li&gt;path traversal risks in bridge/runtime paths&lt;/li&gt;
&lt;li&gt;collaborator trust-boundary hardening&lt;/li&gt;
&lt;li&gt;additional schema indexes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;read_graph&lt;/code&gt; performance issues&lt;/li&gt;
&lt;li&gt;bridge logging and TaskDB SQL cleanup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the right kind of work for a project in this stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I think is actually interesting here
&lt;/h2&gt;

&lt;p&gt;The interesting part is not the raw tool count.&lt;/p&gt;

&lt;p&gt;The interesting part is that a local-first SQLite database can sit underneath a&lt;br&gt;
surprisingly broad MCP workflow without giving up the properties that made it&lt;br&gt;
useful in the first place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;easy backup&lt;/li&gt;
&lt;li&gt;easy inspection&lt;/li&gt;
&lt;li&gt;no service orchestration&lt;/li&gt;
&lt;li&gt;no mandatory cloud hop&lt;/li&gt;
&lt;li&gt;direct ownership of the data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project is bigger now, but the center of gravity is the same:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;a local SQLite file that Claude Code can use safely across repeated sessions.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want to try it
&lt;/h2&gt;

&lt;p&gt;Current repo: &lt;a href="https://github.com/RMANOV/sqlite-memory-mcp" rel="noopener noreferrer"&gt;https://github.com/RMANOV/sqlite-memory-mcp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Latest stable tag in the repo right now: &lt;code&gt;v3.4.0&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you only want drop-in memory compatibility, start with the core server.&lt;/p&gt;

&lt;p&gt;If you want the full stack, add the companion servers or use the unified server.&lt;/p&gt;

&lt;p&gt;That was the original goal and it is still the point of the project: keep memory&lt;br&gt;
local, durable, searchable, and useful enough to support real daily work.&lt;/p&gt;

</description>
      <category>sqlite</category>
      <category>python</category>
      <category>claudeai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The Day the Swarm Got Scared -- And Saved Everyone</title>
      <dc:creator>Ruslan Manov</dc:creator>
      <pubDate>Mon, 06 Apr 2026 16:43:15 +0000</pubDate>
      <link>https://forem.com/john_smith_9ff0ff4cfcffdc/the-day-the-swarm-got-scared-and-saved-everyone-3gl2</link>
      <guid>https://forem.com/john_smith_9ff0ff4cfcffdc/the-day-the-swarm-got-scared-and-saved-everyone-3gl2</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;&lt;u&gt;The Day the Swarm Got Scared -- And Saved Everyone&lt;/u&gt;&lt;/strong&gt;
&lt;/h2&gt;




&lt;h2&gt;
  
  
  T+0.000s: The Valley
&lt;/h2&gt;

&lt;p&gt;Two hundred drones crossed the ridgeline at 0347 local time, flying a Vee formation at fifteen-meter spacing. They had no GPS. They had not had GPS for eleven minutes, ever since the electronic warfare blanket rolled across the valley like an invisible fog. The SAM corridor below them was a dark geometry of overlapping kill envelopes, and every drone in the swarm knew this because every drone in the swarm had been talking to every other drone, constantly, through a protocol borrowed from epidemiology.&lt;/p&gt;

&lt;p&gt;The swarm was not afraid. Not yet.&lt;/p&gt;

&lt;p&gt;Fear, in the STRIX system, is not a metaphor. It is a 64-bit floating-point number between zero and one, computed forty times per second by a subsystem adapted from behavioral economics. At T+0, the fleet-wide fear parameter sat at F=0.08. Background noise. The algorithmic equivalent of a steady hand.&lt;/p&gt;

&lt;p&gt;What happened next pushed it to 0.73.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Particle Filter: Navigating Blind
&lt;/h2&gt;

&lt;p&gt;Eleven minutes without GPS is a long time for an inertial measurement unit. IMUs drift. Accelerometers accumulate bias. Gyroscopes precess. Without correction, a drone flying on dead reckoning will be hundreds of meters off-position within minutes.&lt;/p&gt;

&lt;p&gt;STRIX does not use dead reckoning. It uses a dual particle filter -- 200 particles per drone, each particle a hypothesis about where the drone actually is in six-dimensional space: &lt;code&gt;[x, y, z, vx, vy, vz]&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// strix-core/src/particle_nav.rs&lt;/span&gt;
&lt;span class="c1"&gt;// Each particle is a 6D state hypothesis weighted by likelihood.&lt;/span&gt;
&lt;span class="c1"&gt;// When GPS is denied, the filter relies on IMU prediction alone,&lt;/span&gt;
&lt;span class="c1"&gt;// but cross-validates against gossip-relayed neighbor positions.&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ParticleNavFilter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;particles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;n_particles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the critical insight: even without GPS, the drones are not navigating alone. Each drone broadcasts its best position estimate through the gossip protocol. When Drone 47 hears from Drone 48 that it is approximately 15 meters to its left, and Drone 47's particle filter has a cluster of hypotheses that agree with this, those particles gain weight. The particles that disagree quietly die.&lt;/p&gt;

&lt;p&gt;The swarm navigates by consensus. Two hundred particle filters, each with 200 particles, form a distributed estimation engine of 40,000 simultaneous hypotheses about the state of the world. GPS denial does not blind this system. It degrades it. There is a difference.&lt;/p&gt;

&lt;p&gt;When the EW engine detects GPS denial, it triggers an automated response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// strix-core/src/ew_response.rs&lt;/span&gt;
&lt;span class="c1"&gt;// GPS denial triggers noise expansion in the particle filter,&lt;/span&gt;
&lt;span class="c1"&gt;// widening the hypothesis cloud to account for increased uncertainty.&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;EwResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ExpandNavigationNoise&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;noise_multiplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;GossipFallback&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;reduced_fanout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;priority_only&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;ForceRegime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Regime&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The process noise multiplier expands. The particle cloud widens. Uncertainty increases, but honestly -- the system knows what it does not know, and acts accordingly.&lt;/p&gt;




&lt;h2&gt;
  
  
  T+12.400s: First Blood
&lt;/h2&gt;

&lt;p&gt;Drone 7 ceased transmitting at T+12.4 seconds.&lt;/p&gt;

&lt;p&gt;There was no warning. No gradual degradation of telemetry. One tick it was there, broadcasting its state through the gossip protocol at three-peer fanout. The next tick it was not. The heartbeat counter incremented past the timeout threshold, and the swarm's loss analyzer activated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// strix-auction/src/antifragile.rs&lt;/span&gt;
&lt;span class="c1"&gt;// The loss analyzer classifies the kill and creates an exclusion zone.&lt;/span&gt;
&lt;span class="c1"&gt;// This is where the swarm starts learning.&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;record_loss&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;LossRecord&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;orphans&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="py"&gt;.orphaned_tasks&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.loss_records&lt;/span&gt;&lt;span class="nf"&gt;.push_back&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.adapt_from_loss&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;orphans&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things happened simultaneously within 2 milliseconds of detecting the loss:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First&lt;/strong&gt;, the loss was classified. Drone 7 was in ENGAGE regime at 500 meters altitude with a known threat bearing. Classification: SAM. Kill zone radius: 2,000 meters. Penalty weight: 0.8.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;classify_loss&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;regime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Regime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;threat_bearing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;altitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;LossClassification&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;regime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threat_bearing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Regime&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Engage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;altitude&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;200.0&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;LossClassification&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Sam&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Regime&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Engage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;LossClassification&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SmallArms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Regime&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Patrol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;LossClassification&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Collision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;LossClassification&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ElectronicWarfare&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;LossClassification&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Unknown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&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;Second&lt;/strong&gt;, Drone 7's orphaned tasks were identified and flagged for immediate re-auction. The auctioneer's &lt;code&gt;needs_reauction&lt;/code&gt; flag flipped to &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third&lt;/strong&gt;, and this is the part that matters: a kill zone materialized in the swarm's shared spatial memory. Not a GPS coordinate. Not a waypoint. A pheromone. A digital scent of death, deposited at Drone 7's last known position, repelling every drone that came near.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// strix-mesh/src/stigmergy.rs&lt;/span&gt;
&lt;span class="c1"&gt;// Threat pheromone: "Danger here" -- repels drones from hazardous areas.&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;PheromoneType&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Explored&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// "I've been here"&lt;/span&gt;
    &lt;span class="n"&gt;Threat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// "Danger here"&lt;/span&gt;
    &lt;span class="n"&gt;Target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// "Interesting target"&lt;/span&gt;
    &lt;span class="n"&gt;Rally&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// "Regroup here"&lt;/span&gt;
    &lt;span class="n"&gt;Corridor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// "Safe path"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pheromone field is a sparse 3D grid with 10-meter cells. Each deposit is about 20 bytes. The gradient computation that steers drones away from danger is O(1) per cell -- a central-difference calculation across neighboring cells that returns a three-component vector pointing away from concentration.&lt;/p&gt;

&lt;p&gt;The swarm did not need to be told to avoid the area where Drone 7 died. It could &lt;em&gt;smell&lt;/em&gt; the danger.&lt;/p&gt;




&lt;h2&gt;
  
  
  T+12.406s: The Market Reacts
&lt;/h2&gt;

&lt;p&gt;Six milliseconds after the loss, the combinatorial auction repriced everything.&lt;/p&gt;

&lt;p&gt;The STRIX auction is a sealed-bid market. Every drone evaluates every available task independently and submits a composite score based on proximity, capability match, energy reserves, urgency, and risk exposure. The auctioneer collects all bids and solves the assignment problem using a modified Hungarian algorithm.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// strix-auction/src/bidder.rs&lt;/span&gt;
&lt;span class="c1"&gt;// Bid scoring function. Note the risk term: kill-zone proximity&lt;/span&gt;
&lt;span class="c1"&gt;// and fear level directly suppress bids on dangerous tasks.&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// total = urgency*10 + capability*3 + proximity*5 + energy*2 - risk*4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When Drone 7 died, two things changed in the market. First, its tasks became orphans -- supply dropped. Second, the kill zone inflated the risk term for every task near grid 7-Alpha -- demand cratered. The market did not need a commander to say "avoid that area." The prices said it. No drone bid competitively on tasks inside the kill zone because the math would not let them.&lt;/p&gt;

&lt;p&gt;The fear parameter rose from 0.08 to 0.31. This was not panic. This was information. The &lt;code&gt;SwarmFearAdapter&lt;/code&gt; translated the loss event into the language of behavioral economics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// strix-swarm/src/fear_adapter.rs&lt;/span&gt;
&lt;span class="c1"&gt;// STRIX telemetry mapped to PhiSim's behavioral economics model:&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// | PhiSim concept       | STRIX signal                        |&lt;/span&gt;
&lt;span class="c1"&gt;// |----------------------|-------------------------------------|&lt;/span&gt;
&lt;span class="c1"&gt;// | drawdown             | Attrition rate (1 - alive/initial)  |&lt;/span&gt;
&lt;span class="c1"&gt;// | vol_ratio            | Threat intensity (1 + intent score) |&lt;/span&gt;
&lt;span class="c1"&gt;// | anomaly_count        | CUSUM breaks this tick              |&lt;/span&gt;
&lt;span class="c1"&gt;// | consecutive_losses   | Consecutive ticks with drone loss   |&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At F=0.31, the formation spacing widened. The &lt;code&gt;FormationConfig&lt;/code&gt; applies fear-modulated spacing: as fear rises, drones spread apart. Wider formation, harder to hit with a single salvo. Less aerodynamic efficiency, but the auction already repriced for that -- the scoring function factors in the additional transit cost.&lt;/p&gt;




&lt;h2&gt;
  
  
  T+23.800s: The Feint
&lt;/h2&gt;

&lt;p&gt;At T+23.8, the adversarial particle filter detected something interesting.&lt;/p&gt;

&lt;p&gt;The second particle filter -- the one that does not track friendly drones but enemy threats -- had been watching a cluster of radar returns moving south along the valley floor. The threat tracker maintained its own 100-particle hypothesis cloud per target, and the intent detection pipeline had been analyzing the movement pattern through three layers of signal processing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// strix-core/src/intent.rs&lt;/span&gt;
&lt;span class="c1"&gt;// 3-layer pipeline: Hurst persistence -&amp;gt; closing acceleration -&amp;gt; vol compression&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// Layer 1: Hurst persistence     -&amp;gt; purposeful trajectory? [H &amp;gt; 0.55]&lt;/span&gt;
&lt;span class="c1"&gt;// Layer 2: Closing acceleration  -&amp;gt; accelerating toward us?&lt;/span&gt;
&lt;span class="c1"&gt;// Layer 3: Volatility compression -&amp;gt; formation tightening?&lt;/span&gt;
&lt;span class="c1"&gt;//              |&lt;/span&gt;
&lt;span class="c1"&gt;//   Confidence-weighted fusion&lt;/span&gt;
&lt;span class="c1"&gt;//              |&lt;/span&gt;
&lt;span class="c1"&gt;//   IntentScore in [-1, 1] + IntentClass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Hurst exponent for the southern cluster was 0.42. Below the purposeful threshold of 0.55. The movement was mean-reverting -- zigzagging, not advancing. The closing acceleration was near zero. The volatility ratio was high: 1.8, indicating loose, disorganized movement.&lt;/p&gt;

&lt;p&gt;The intent pipeline classified this as &lt;code&gt;IntentClass::Neutral&lt;/code&gt;, bordering on &lt;code&gt;Retreating&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But here is where it gets subtle. The CUSUM anomaly detector noticed something the intent pipeline alone would miss: the southern cluster's radar cross-section kept changing. Large, then small, then large. Inconsistent with real aircraft. Consistent with decoys -- inflatable or electronic emitters designed to draw attention and waste resources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// strix-core/src/anomaly.rs -- CUSUM detects distributional shifts.&lt;/span&gt;
&lt;span class="c1"&gt;// When the signature variance of a target group breaks the cusum threshold,&lt;/span&gt;
&lt;span class="c1"&gt;// the system flags potential deception.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The adversarial particle filter's weight distribution was bimodal: half the particles clustered on "real threat, low intent" and half on "decoy, ignore." The Hurst persistence analysis tipped the balance. Real threat formations show persistent trajectories (H &amp;gt; 0.55). Decoys wander. H=0.42 was the signature of something pretending to be threatening but failing at the physics of it.&lt;/p&gt;

&lt;p&gt;The fear parameter ticked up to 0.38 on the initial detection, then &lt;em&gt;back down&lt;/em&gt; to 0.29 as the system accumulated evidence of deception. This is the dual-process architecture at work -- fear rises fast (System 1), but the analytical pipeline (System 2) can override it with evidence. The swarm did not freeze. It did not divert resources to chase phantoms. It maintained course.&lt;/p&gt;

&lt;p&gt;The XAI narrator logged the reasoning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[t=24.1s] Threat response (prob=31%): Maintaining course — southern cluster
classified as FEINT. Confidence: 78%.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every alternative was recorded. "Divert 30 drones south" scored 0.34, rejected for "Insufficient threat probability, Hurst below purposeful threshold." The glass box held.&lt;/p&gt;




&lt;h2&gt;
  
  
  T+47.200s: The Cascade
&lt;/h2&gt;

&lt;p&gt;This is where the story could have ended badly.&lt;/p&gt;

&lt;p&gt;At T+47.2, the EW blanket intensified. The comms jamming layer that had been degrading mesh connectivity surged to SEVERE. Sixty drones lost their gossip links simultaneously. Not destroyed -- silenced. Their particle filters kept running, their IMUs kept integrating, but they could not hear the swarm and the swarm could not hear them.&lt;/p&gt;

&lt;p&gt;Then the SAM corridor opened up.&lt;/p&gt;

&lt;p&gt;In thirty seconds, between T+47 and T+77, the swarm lost sixty drones. Not lost-connection. Lost. Destroyed. The loss analyzer fired sixty times in thirty seconds. Sixty kill zones materialized across the valley floor. Sixty sets of orphaned tasks flooded the auction queue.&lt;/p&gt;

&lt;p&gt;The fear parameter did this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;T+47.0: F = 0.29
T+50.0: F = 0.51
T+55.0: F = 0.62
T+60.0: F = 0.68
T+65.0: F = 0.71
T+70.0: F = 0.73
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;F=0.73. The swarm was terrified.&lt;/p&gt;

&lt;p&gt;What does terror look like in a combinatorial auction? It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// strix-auction/src/antifragile.rs&lt;/span&gt;
&lt;span class="c1"&gt;// Fear-amplified kill zone penalties. At F=0.73, the multiplier is 2.095.&lt;/span&gt;
&lt;span class="c1"&gt;// SAM kill zones with base penalty 0.8 become 1.676 -- effectively&lt;/span&gt;
&lt;span class="c1"&gt;// making it economically impossible to bid on tasks inside them.&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;kill_zone_penalties_with_fear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fear&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fear&lt;/span&gt;&lt;span class="nf"&gt;.clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;multiplier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1.0 -&amp;gt; 2.5&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.kill_zones&lt;/span&gt;
        &lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;kz&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kz&lt;/span&gt;&lt;span class="py"&gt;.center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kz&lt;/span&gt;&lt;span class="py"&gt;.radius&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kz&lt;/span&gt;&lt;span class="py"&gt;.penalty&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At F=0.73, the fear multiplier hit 2.095. Every SAM kill zone's penalty weight of 0.8 became 1.676. The auction's risk term (&lt;code&gt;-risk*4&lt;/code&gt;) for tasks inside those zones was so massive that no bid could overcome it. The market priced those areas at infinity. No drone went there. No commander needed to draw a red line on a map. The red line drew itself, from the blood of the fallen.&lt;/p&gt;

&lt;p&gt;But here is where anti-fragility kicked in. The kill zones did not just warn. They &lt;em&gt;taught&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Each additional loss in the same zone GROWS the radius.&lt;/span&gt;
&lt;span class="c1"&gt;// After 4 merges with growth factor 1.3: base * 1.3^4 = base * 2.86&lt;/span&gt;
&lt;span class="c1"&gt;// The system overestimates danger on purpose. Better to avoid&lt;/span&gt;
&lt;span class="c1"&gt;// too much than too little.&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;merge_into_existing_zone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;LossRecord&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;kz&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.kill_zones&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kz&lt;/span&gt;&lt;span class="py"&gt;.center&lt;/span&gt;&lt;span class="nf"&gt;.distance_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="py"&gt;.position&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.merge_distance&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;kz&lt;/span&gt;&lt;span class="py"&gt;.loss_count&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="n"&gt;kz&lt;/span&gt;&lt;span class="py"&gt;.radius&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.zone_growth_factor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;kz&lt;/span&gt;&lt;span class="py"&gt;.penalty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kz&lt;/span&gt;&lt;span class="py"&gt;.penalty&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="c1"&gt;// Shift centre towards the new loss (weighted average).&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;kz&lt;/span&gt;&lt;span class="py"&gt;.loss_count&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;kz&lt;/span&gt;&lt;span class="py"&gt;.center&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;kz&lt;/span&gt;&lt;span class="py"&gt;.center.x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="py"&gt;.position.x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;kz&lt;/span&gt;&lt;span class="py"&gt;.center.y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="py"&gt;.position.y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;kz&lt;/span&gt;&lt;span class="py"&gt;.center.z&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="py"&gt;.position.z&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three losses near the same coordinates? The kill zone radius expanded by a factor of 1.3 per loss. The penalty weight climbed toward 1.0. The evade bias at that position -- the probability of entering EVADE regime when nearby -- stacked additively. The swarm was not just avoiding the danger. It was building an increasingly accurate map of it, and the more it suffered, the better the map became.&lt;/p&gt;

&lt;p&gt;The antifragile score -- &lt;code&gt;sum over kill zones of (loss_count * ln(1 + loss_count) * radius_growth)&lt;/code&gt; -- climbed past 50.0. By Taleb's measure, the system was &lt;em&gt;more robust&lt;/em&gt; after losing 60 drones than it had been with 200.&lt;/p&gt;




&lt;h2&gt;
  
  
  T+78.000s: The Reformation
&lt;/h2&gt;

&lt;p&gt;One hundred and forty drones remained. They were scattered, terrified (F=0.73), and navigating on inertial alone in a GPS-denied environment thick with SAM coverage and comms jamming.&lt;/p&gt;

&lt;p&gt;They reformed in four seconds.&lt;/p&gt;

&lt;p&gt;The gossip protocol is designed for exactly this scenario. Each surviving drone selected three random peers from its known-alive list and exchanged state digests. If the digests differed -- and they all differed, because sixty drones had just vanished -- full state exchanges followed. Within two gossip rounds, the surviving 140 drones had converged on a shared picture of who was left and where everyone was.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// strix-mesh/src/gossip.rs&lt;/span&gt;
&lt;span class="c1"&gt;// O(log N) convergence via epidemic gossip.&lt;/span&gt;
&lt;span class="c1"&gt;// Two rounds to synchronize 140 nodes after catastrophic loss.&lt;/span&gt;

&lt;span class="c1"&gt;// Conflict resolution:&lt;/span&gt;
&lt;span class="c1"&gt;// - General data: newer timestamp wins.&lt;/span&gt;
&lt;span class="c1"&gt;// - Threat data: union -- never discard threat information.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The formation engine computed new slot positions for 140 drones in Vee formation. The correction velocity vectors pointed each drone toward its new slot using proportional control with speed clamping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// strix-core/src/formation.rs&lt;/span&gt;
&lt;span class="c1"&gt;// v_corr = (delta / ||delta||) * min(||delta||, v_max)&lt;/span&gt;
&lt;span class="c1"&gt;// If within deadband: v_corr = 0.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is where the CBF -- the Control Barrier Function -- earned its keep. One hundred and forty drones, all simultaneously repositioning in three dimensions, in comms-degraded conditions. The potential for mid-air collision was enormous.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// strix-core/src/cbf.rs&lt;/span&gt;
&lt;span class="c1"&gt;// CBF safety clamp: TTC-aware collision avoidance.&lt;/span&gt;
&lt;span class="c1"&gt;// Runs AFTER formation control, BEFORE velocity commands are sent.&lt;/span&gt;
&lt;span class="c1"&gt;// Every velocity vector that would violate the safety barrier gets&lt;/span&gt;
&lt;span class="c1"&gt;// rotated and scaled to the nearest safe vector.&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;CbfConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;min_separation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// meters&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;altitude_floor_ned&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// NED convention&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;altitude_ceiling_ned&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="c1"&gt;// decay rate -- aggressiveness&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;max_correction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// m/s cap&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CBF is a mathematical guarantee. Not a best-effort collision avoidance. Not a "try to maintain separation." A hard constraint that modifies every velocity command to ensure that the barrier function -- a measure of how close two drones are to colliding -- never decreases below zero. If two drones are on a collision course, the CBF does not ask. It corrects. And it does so with the minimum modification necessary to the desired velocity, preserving mission intent to the maximum extent physics allows.&lt;/p&gt;

&lt;p&gt;Zero collisions during the reformation. At 1.15ms per tick for 20 drones, and scaling to the full 140, the system ran the entire CBF pass in under 10ms. Tight enough that the correction commands arrived before the drones had moved appreciably toward each other.&lt;/p&gt;




&lt;h2&gt;
  
  
  T+82.000s: The Market Finds Equilibrium
&lt;/h2&gt;

&lt;p&gt;The auction re-ran at T+82.0. All surviving drones submitted sealed bids on all remaining tasks, with kill-zone penalties applied, fear-modulated risk terms included, and the intent pipeline's assessment of remaining threats factored into urgency multipliers.&lt;/p&gt;

&lt;p&gt;The market cleared in 4.86ms.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Auction result&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;137 tasks assigned (of 142 remaining)&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;5 tasks unassigned (inside active kill zones, no viable bid)&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Total welfare&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;847.3 (down from 1,204.1 pre-attrition)&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Antifragile score&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;58.4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The five unassigned tasks were inside the densest kill zones. The market's judgment: no drone should go there. The risk-adjusted cost exceeded the task value. This was not cowardice. This was the auction computing, in 4.86 milliseconds, a truth that would take a human commander minutes to reach: those tasks were not worth another drone.&lt;/p&gt;

&lt;p&gt;The fear parameter began to decay. No new losses. The gossip protocol confirmed all 140 surviving drones were in formation and executing their assigned tasks. The CUSUM detectors settled. The Hurst exponent of the fleet's own movement pattern climbed back above 0.6 -- purposeful, directed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;T+82.0: F = 0.71
T+90.0: F = 0.64
T+100.0: F = 0.55
T+120.0: F = 0.42
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The swarm was calming down. Not because someone told it to. Because the math said the danger was receding.&lt;/p&gt;




&lt;h2&gt;
  
  
  T+127.000s: The Glass Box
&lt;/h2&gt;

&lt;p&gt;The XAI narrator had been recording every decision the entire time. Not summarizing. Not approximating. Every single decision trace, with full reasoning chains, alternatives considered, confidence levels, and input states.&lt;/p&gt;

&lt;p&gt;This is the glass box.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// strix-xai/src/trace.rs&lt;/span&gt;
&lt;span class="c1"&gt;// Every decision emits a DecisionTrace with:&lt;/span&gt;
&lt;span class="c1"&gt;// - Timestamp&lt;/span&gt;
&lt;span class="c1"&gt;// - Decision type (TaskAssignment, RegimeChange, FormationChange,&lt;/span&gt;
&lt;span class="c1"&gt;//                  ThreatResponse, ReAuction, LeaderElection)&lt;/span&gt;
&lt;span class="c1"&gt;// - Full inputs (drone IDs, regime, metrics, fear/courage/tension)&lt;/span&gt;
&lt;span class="c1"&gt;// - Reasoning chain (numbered steps with data)&lt;/span&gt;
&lt;span class="c1"&gt;// - All alternatives considered (with scores and rejection reasons)&lt;/span&gt;
&lt;span class="c1"&gt;// - Output action + confidence score&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;DecisionTrace&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;decision_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DecisionType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TraceInputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;reasoning&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ReasoningStep&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;alternatives_considered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Alternative&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TraceOutput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the command center, a human operator -- the one who had been watching the entire engagement unfold -- requested the after-action review. The mission replay system aggregated 4,847 decision traces into a structured timeline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// strix-xai/src/replay.rs&lt;/span&gt;
&lt;span class="c1"&gt;// MissionReplay aggregates all traces into a timeline with&lt;/span&gt;
&lt;span class="c1"&gt;// statistics, key moments, and what-if analysis capability.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The narrator produced the report at &lt;code&gt;DetailLevel::Detailed&lt;/code&gt;. Every decision, every alternative, every rejection reason. But between the lines of structured data, a story emerged.&lt;/p&gt;

&lt;p&gt;Not because anyone programmed it to tell stories. Because when you trace the complete decision history of a system that learned from sixty deaths, the trace reads like one.&lt;/p&gt;




&lt;h2&gt;
  
  
  The After-Action Report
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=== STRIX Mission Replay: Operation Ridgeline ===
Duration: 127.0s | Drones: 200 initial, 140 surviving | Traces: 4,847

KEY MOMENTS:

[t=12.4s] Unit 7 ceased. Classification: SAM. Kill zone established at
(3847.2, 1204.5, 502.1), radius 2000m, penalty 0.80.
The market remembered.

[t=12.4s] Re-auction triggered. 3 orphaned tasks redistributed among 199
remaining drones in 4.2ms. No bid entered for grid 7-Alpha.
At t=12.4s, no drone bid on grid 7-Alpha again.

[t=24.1s] Southern cluster assessed as FEINT.
Hurst=0.42 (below purposeful threshold 0.55).
Closing acceleration: -0.12 m/s^2 (below attack threshold 0.50).
Volatility ratio: 1.80 (expanding, not compressing).
Decision: Maintain course. Confidence: 78%.
  Alternative: Divert 30 drones south (score=0.34) -- rejected:
  "Insufficient threat probability, Hurst below purposeful threshold."
The swarm chose not to chase ghosts.

[t=47.2s-77.0s] CASCADE EVENT. 60 units lost in 30.0 seconds.
Fear: 0.29 -&amp;gt; 0.73.
Kill zones established: 60. Merged zones: 12.
Auction repriced: 60 re-auction cycles, mean latency 3.8ms.
Antifragile score: 12.1 -&amp;gt; 58.4.
The swarm suffered. The swarm learned.

[t=78.0s] Reformation complete. 140 drones, Vee formation.
Gossip convergence: 97.1% in 2 rounds (3.2 seconds).
CBF interventions: 23 (zero collisions).
The swarm reformed while scared, and nothing touched.

[t=82.0s] Market equilibrium. 137/142 tasks assigned.
5 tasks unpriced (inside kill zones, welfare &amp;lt; threshold).
The market found the boundary of acceptable risk.

[t=127.0s] Mission complete. Fear: 0.42 (decaying).
Final antifragile score: 62.7.

DETERMINISTIC REPLAY AVAILABLE: All 4,847 traces stored.
Full tick-by-tick replay at original timing enabled.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The operator stared at the screen for a long time after reading it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;At T+12.4s, Unit 7 ceased. The market remembered. At T+12.4s, no drone bid on grid 7-Alpha again.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It was not poetry. It was a database query formatted as text. But it read like an epitaph, because the math of loss and memory and avoidance, when you trace it honestly, has a cadence that sounds like grief.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Algorithms Are Real
&lt;/h2&gt;

&lt;p&gt;STRIX is an open-source Rust project. Apache 2.0. Every algorithm described in this story is implemented, tested, and benchmarked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Numbers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;34,889 lines of Rust across 9 crates&lt;/li&gt;
&lt;li&gt;7,493 lines of Python (PyO3 bindings + simulation)&lt;/li&gt;
&lt;li&gt;671 tests&lt;/li&gt;
&lt;li&gt;1.15ms per tick (20 drones)&lt;/li&gt;
&lt;li&gt;4.86ms auction clear (100 drones)&lt;/li&gt;
&lt;li&gt;Scaling target: 2,000+ drones&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The nine crates:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;strix-core&lt;/code&gt;: Dual particle filter, CUSUM anomaly detection, regime detection, formation control, CBF safety, EW response, threat intent pipeline, ROE engine&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strix-auction&lt;/code&gt;: Combinatorial auction, sealed-bid market, anti-fragile kill zones, fear-modulated risk pricing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strix-mesh&lt;/code&gt;: Gossip protocol (O(log N) convergence), digital pheromone fields (stigmergy), fractal communication&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strix-xai&lt;/code&gt;: Glass-box trace recording, natural-language narration, deterministic mission replay&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strix-swarm&lt;/code&gt;: Integration orchestrator, tick loop, PhiSim fear adapter&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strix-adapters&lt;/code&gt;: MAVLink, ROS2, simulator interfaces&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strix-python&lt;/code&gt;: PyO3 bindings for the entire stack&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strix-playground&lt;/code&gt;: Scenario engine, threat presets, benchmarking&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strix-optimizer&lt;/code&gt;: SMCO parameter optimization, Pareto analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What makes it different:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;Dual particle filter&lt;/em&gt; -- no competitor has both friendly navigation and adversarial intent prediction running simultaneously&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Anti-fragile kill zones&lt;/em&gt; -- the swarm measurably improves after losses, inspired by Taleb's anti-fragility&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Fear meta-parameter&lt;/em&gt; -- behavioral economics (Kahneman) modulates every subsystem through a single continuous signal&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Combinatorial auction&lt;/em&gt; -- market-based task allocation with kill-zone repricing, not centralized planning&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Digital pheromones + gossip&lt;/em&gt; -- fully decentralized, no single point of failure, bio-inspired coordination&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Glass-box XAI&lt;/em&gt; -- every decision traced, narrated, replayable; zero black-box decisions&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Deterministic replay&lt;/em&gt; -- entire missions can be replayed tick-by-tick for after-action review and what-if analysis&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fear is a math function. The courage is a counter-signal. The memory is pheromones. The market finds the optimal outcome.&lt;/p&gt;

&lt;p&gt;And sometimes, when you read the trace of what the market decided, it sounds like something more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/RMANOV/strix" rel="noopener noreferrer"&gt;github.com/RMANOV/strix&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>robotics</category>
      <category>opensource</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>Building a Drone Swarm Orchestrator That Gets Scared — 20 Subsystems in 35K Lines of Rust</title>
      <dc:creator>Ruslan Manov</dc:creator>
      <pubDate>Mon, 06 Apr 2026 16:37:25 +0000</pubDate>
      <link>https://forem.com/john_smith_9ff0ff4cfcffdc/building-a-drone-swarm-orchestrator-that-gets-scared-20-subsystems-in-35k-lines-of-rust-4bp4</link>
      <guid>https://forem.com/john_smith_9ff0ff4cfcffdc/building-a-drone-swarm-orchestrator-that-gets-scared-20-subsystems-in-35k-lines-of-rust-4bp4</guid>
      <description>&lt;p&gt;&lt;strong&gt;#&lt;u&gt;# Building a Drone Swarm Orchestrator That Gets Scared — 20 Subsystems in 35K Lines of Rust&lt;/u&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tags: rust, robotics, opensource, algorithms&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Target: Dev.to&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The Observation That Started Everything
&lt;/h2&gt;

&lt;p&gt;A few years ago I was working on a particle filter for tracking hidden state in financial time series — the usual quantitative trading toolkit. Particles representing possible market regimes, a Bayesian update step when new price data arrives, a resampling step to prevent weight degeneracy. Standard stuff.&lt;/p&gt;

&lt;p&gt;Then someone asked me to look at a drone swarm coordination problem. I expected something completely different. What I found instead was the same math, wearing different clothes.&lt;/p&gt;

&lt;p&gt;Drones tracking uncertain positions in 3D space? That's a particle filter, same as tracking a hidden volatility regime. Allocating scarce drone resources to competing tasks? That's a combinatorial auction, same as portfolio optimization under constraints. Protecting the swarm against catastrophic attrition? That's drawdown protection, same as risk management in a leveraged portfolio. The math didn't change. Only the domain changed.&lt;/p&gt;

&lt;p&gt;That observation became STRIX: a 34,889-line Rust + 7,493-line Python drone swarm orchestration library (~42,400 LOC total) that treats the battlefield as a market, implements swarm coordination from ant colony research, and uses a "fear meta-parameter" borrowed from behavioral economics to modulate every subsystem in real time. Designed to scale toward 2000+ drones.&lt;/p&gt;

&lt;p&gt;This article is a deep dive into the architecture, the technical decisions, and what 20 subsystems across 9 crates taught me about building complex autonomous systems.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Problem Space
&lt;/h2&gt;

&lt;p&gt;Drone swarm coordination is hard in a specific way: the difficulty is not computational but architectural. You need to simultaneously solve:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;State estimation under uncertainty&lt;/strong&gt; — where are we, where are threats, what's the ground truth when GPS is jammed?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task allocation under contention&lt;/strong&gt; — which drone does which task when you have more tasks than drones and capabilities don't match uniformly?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safety with formal guarantees&lt;/strong&gt; — how do you ensure collision avoidance and no-fly zone compliance without a central controller that becomes a single point of failure?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coordination without centralization&lt;/strong&gt; — how does the swarm share state when you lose nodes, when comms are degraded, when the network topology changes every second?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human-in-the-loop&lt;/strong&gt; — how do you keep a human meaningfully in the decision loop when the swarm acts at millisecond timescales?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explainability&lt;/strong&gt; — if the swarm makes a decision you didn't expect, how do you reconstruct exactly why?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most existing approaches handle one or two of these well. The rest are left as "future work" or "out of scope." STRIX tries to handle all six in a unified architecture.&lt;/p&gt;


&lt;h2&gt;
  
  
  Architecture: The 10-Step Tick Loop
&lt;/h2&gt;

&lt;p&gt;The core of STRIX is a deterministic tick loop that runs every timestep. Each tick executes exactly 10 steps in order. Every subsystem runs on every tick. No exceptions, no optional steps.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────────┐
│                    STRIX TICK LOOP (per drone)                   │
├─────────────────────────────────────────────────────────────────┤
│  Step 1:  EW Threat Scan                                         │
│           Classify: GpsJamming | CommJamming | Spoofing |        │
│                     DirectedEnergy | CyberIntrusion              │
│           Modulate noise params + gossip fanout via fear F∈[0,1] │
├─────────────────────────────────────────────────────────────────┤
│  Step 2:  Dual Particle Filter                                   │
│           Friendly: 200 particles, state [x,y,z,vx,vy,vz]       │
│           Threats:  100 particles, adversarial tracking          │
│           Predict + Measurement update + Resample                │
├─────────────────────────────────────────────────────────────────┤
│  Step 3:  CUSUM Anomaly Detection                               │
│           Per-drone sequential change detection                  │
│           Regime transitions: Patrol → Engage → Evade            │
│           3×3 Markov transition matrix                           │
├─────────────────────────────────────────────────────────────────┤
│  Step 4:  Formation Correction                                   │
│           7 formation types (Vee, Line, Wedge, Column,           │
│           EchelonLeft, EchelonRight, Spread)                     │
│           Proportional control law with deadband                 │
├─────────────────────────────────────────────────────────────────┤
│  Step 5:  Threat Tracker Update                                  │
│           Intent detection: motion pattern → behavior class      │
│           Hysteresis gate prevents classification oscillation    │
│           Adversarial doctrines: PROBING, FEINT, COORDINATED    │
├─────────────────────────────────────────────────────────────────┤
│  Step 6:  ROE Authorization Gate                                 │
│           WeaponsHold | WeaponsTight | WeaponsFree               │
│           Pipeline: classify → IFF confidence → collateral risk  │
│           CVaR risk scoring integration                          │
├─────────────────────────────────────────────────────────────────┤
│  Step 7:  Combinatorial Task Auction  [strix-auction]            │
│           Drones bid on tasks; winner-takes-assignment           │
│           Kill-zone repricing after losses                       │
│           Dark pool compartmentalization for classified tasks     │
├─────────────────────────────────────────────────────────────────┤
│  Step 8:  Gossip State Propagation  [strix-mesh]                 │
│           O(log N) convergence, priority-queued messages         │
│           Pheromone update: Danger/Explored/Rally/Resource       │
├─────────────────────────────────────────────────────────────────┤
│  Step 9:  CBF Safety Clamp  [strix-core]                        │
│           TTC-aware CBF with deadlock detection                  │
│           Collision avoidance + altitude bounds + NFZ exclusion  │
│           APF trajectory planning integration                    │
├─────────────────────────────────────────────────────────────────┤
│  Step 10: XAI Decision Trace  [strix-xai]                       │
│           Record every decision with full causal chain           │
│           Machine traces → human-readable narrative              │
│           Deterministic replay for after-action review           │
└─────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 9 crates correspond roughly to this structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;strix-core&lt;/code&gt;: steps 1–6, 9 (15 modules, the heaviest crate)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strix-auction&lt;/code&gt;: step 7&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strix-mesh&lt;/code&gt;: step 8&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strix-xai&lt;/code&gt;: step 10&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strix-swarm&lt;/code&gt;: swarm-level coordination across drones&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strix-adapters&lt;/code&gt;: MAVLink/ROS2 hardware adapters&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strix-python&lt;/code&gt;: PyO3 bindings&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strix-playground&lt;/code&gt;: scenario DSL for testing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strix-optimizer&lt;/code&gt;: SMCO multi-objective parameter optimization with Pareto front, 62 tunable parameters&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Deep Dive 1: The Battlefield is a Market
&lt;/h2&gt;

&lt;p&gt;The auction system in &lt;code&gt;strix-auction&lt;/code&gt; is the most intellectually loaded module in STRIX. The core insight: &lt;strong&gt;task allocation in a drone swarm is mathematically equivalent to portfolio optimization in a market with constraints&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In a financial portfolio:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have scarce capital to allocate&lt;/li&gt;
&lt;li&gt;You have a set of available assets with different risk/return profiles&lt;/li&gt;
&lt;li&gt;Some assets have correlations you need to track&lt;/li&gt;
&lt;li&gt;Drawdown protection prevents you from loading into catastrophic positions&lt;/li&gt;
&lt;li&gt;Some trades are only visible to certain participants (dark pools)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a drone swarm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have scarce drone-capacity to allocate&lt;/li&gt;
&lt;li&gt;You have a set of tasks with different capability requirements&lt;/li&gt;
&lt;li&gt;Some tasks must be done together (bundles)&lt;/li&gt;
&lt;li&gt;Kill-zone repricing prevents you from sending more drones into a slaughter&lt;/li&gt;
&lt;li&gt;Some tasks are classified and visible only to specific sub-swarms (compartmentalization)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;Task&lt;/code&gt; structure makes this mapping explicit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;required_capabilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Capabilities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// sensor/weapon/EW/relay&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;urgency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;bundle_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// tasks that must go together&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;dark_pool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// compartmentalized visibility&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The auction mechanism is loosely inspired by VCG (Vickrey-Clarke-Groves, 1971) — the same mechanism that underlies modern digital advertising auctions — adapted for multi-unit combinatorial assignment with physical constraints. A drone's bid on a task is a function of its distance to the task location, its capability match score, its current load, and its fear state. High-fear drones bid more conservatively. They're risk-averse, like a trader protecting a drawdown.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kill-zone repricing&lt;/strong&gt; is the anti-fragility mechanism: after a drone is lost in a location, the perceived cost of that location increases for all subsequent bidders. The auction organically routes the swarm around high-attrition zones. The swarm doesn't need a central commander to say "stop flying over that hill" — the auction figures it out through price signals.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dark pool compartmentalization&lt;/strong&gt; solves a harder problem: in real operations, some tasks are classified above the clearance of certain drones. The auction system supports &lt;code&gt;dark_pool&lt;/code&gt; visibility groups, where only drones within the same dark pool can see and bid on compartmentalized tasks. This is architecturally identical to how dark pools work in equity markets — non-public order flow visible only to approved participants.&lt;/p&gt;

&lt;p&gt;Benchmark: &lt;strong&gt;465 µs for a full auction cycle with 50 drones competing on 20 tasks&lt;/strong&gt;. At scale: &lt;strong&gt;4.86 ms for 100 drones competing on 50 tasks&lt;/strong&gt;. Both run inside the tick loop.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deep Dive 2: Fear as a Control Signal
&lt;/h2&gt;

&lt;p&gt;The PhiSim integration is the part of STRIX that gets the strongest reactions from people who encounter it for the first time: "You put &lt;em&gt;fear&lt;/em&gt; into a drone swarm? Why?"&lt;/p&gt;

&lt;p&gt;The answer starts with Kahneman. Prospect theory (Kahneman &amp;amp; Tversky, 1979) shows that human decision-making under uncertainty is not utility-maximizing — it's loss-averse, context-sensitive, and heavily influenced by current emotional state. A trader who just suffered a significant drawdown behaves differently than a trader who is up on the month, even when facing mathematically identical choices. That's not irrational. It's adaptive.&lt;/p&gt;

&lt;p&gt;The same logic applies to autonomous systems. A swarm that has lost 30% of its drones to jamming should not behave identically to a full-strength swarm approaching the same objective. It should be more cautious — wider formations, longer evade distances, stronger avoidance signals, more aggressive information sharing. Not because a human operator told it to be cautious, but because the system's own estimate of its situation warrants it.&lt;/p&gt;

&lt;p&gt;Fear &lt;code&gt;F ∈ [0, 1]&lt;/code&gt; in STRIX is computed from a dual adversarial process: fear and courage as opposing forces, with tension = |fear - courage|. The inputs map financial concepts to combat:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Financial concept&lt;/th&gt;
&lt;th&gt;Swarm analog&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Portfolio drawdown&lt;/td&gt;
&lt;td&gt;Attrition rate (drones lost / total)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Volatility ratio&lt;/td&gt;
&lt;td&gt;Threat intensity ratio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anomaly count&lt;/td&gt;
&lt;td&gt;CUSUM change-point breaks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Consecutive losses&lt;/td&gt;
&lt;td&gt;Loss ticks (sustained attrition)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When fear rises, every subsystem responds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Evade distance: 150m at F=0 → 500m at F=1&lt;/span&gt;
&lt;span class="n"&gt;evade_distance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="py"&gt;.evade_distance&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;2.3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Formation spacing: +50% at maximum fear&lt;/span&gt;
&lt;span class="n"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="py"&gt;.spacing&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;axes&lt;/span&gt;&lt;span class="py"&gt;.bias&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Pheromone persistence: 3.3x longer at F=1&lt;/span&gt;
&lt;span class="n"&gt;modulated_decay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;decay&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;axes&lt;/span&gt;&lt;span class="py"&gt;.threshold&lt;/span&gt;  &lt;span class="c1"&gt;// threshold ≈ 0.3 at F=1&lt;/span&gt;

&lt;span class="c1"&gt;// Gossip fanout: up to 2x at F=1 (capped at 3x)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_fanout&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;base_fanout&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="nf"&gt;.min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_fanout&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The effect is that a high-fear swarm is simultaneously more cautious (wider formations, longer evade distances) and more communicative (higher gossip fanout, stronger pheromones). This matches what military doctrine recommends for degraded units: pull back, increase information sharing, wait for situation clarity before re-engaging.&lt;/p&gt;

&lt;p&gt;Courage is the opposing force. A high-courage swarm can tolerate tighter formations, closer engagement distances, more aggressive auction bids. Tension (|fear - courage|) drives ROE posture suggestions — not automated ROE changes, but recommendations surfaced to human operators through the XAI layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deep Dive 3: Bio-Inspired Coordination
&lt;/h2&gt;

&lt;p&gt;The gossip protocol in &lt;code&gt;strix-mesh&lt;/code&gt; and the pheromone system in &lt;code&gt;strix-core&lt;/code&gt; are the two bio-inspired coordination mechanisms. They solve the same problem from different angles: how does a decentralized swarm maintain coherent collective behavior without a central coordinator?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Digital Pheromones&lt;/strong&gt; are borrowed directly from Dorigo's ant colony optimization (1992). Ants leave chemical traces that guide other ants toward food sources and away from dead ends. STRIX implements four pheromone types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Danger&lt;/code&gt; — repulsive, deposited near threats and loss events&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Explored&lt;/code&gt; — marks already-covered terrain to avoid redundant paths&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Rally&lt;/code&gt; — attractive, marks gathering points for regrouping&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Resource&lt;/code&gt; — marks objectives and high-value areas&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each pheromone has exponential decay, but the decay rate is modulated by fear. At high fear, pheromones persist 3.3x longer — the swarm's collective memory of dangerous areas stays fresh longer when it's actively scared. At low fear, pheromones fade quickly, allowing the swarm to be bolder about re-exploring previously dangerous terrain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gossip Protocol&lt;/strong&gt; implements epidemic information spreading. Each drone periodically selects a random set of neighbors and exchanges state updates. The mathematical guarantee: in a connected graph, gossip reaches all nodes in O(log N) rounds with high probability. STRIX implements bandwidth-aware priority queuing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Priority&lt;/th&gt;
&lt;th&gt;Message Type&lt;/th&gt;
&lt;th&gt;Rationale&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0 (highest)&lt;/td&gt;
&lt;td&gt;ThreatAlert&lt;/td&gt;
&lt;td&gt;Immediate danger, time-critical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;TaskAssignment&lt;/td&gt;
&lt;td&gt;Coordination, semi-urgent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;StateUpdate&lt;/td&gt;
&lt;td&gt;Position/status, routine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;PheromoneDeposit&lt;/td&gt;
&lt;td&gt;Environmental update, low urgency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Heartbeat&lt;/td&gt;
&lt;td&gt;Keepalive, lowest priority&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When fear is high and bandwidth is constrained, low-priority messages are dropped first. The swarm preferentially shares threat information when it most needs to. When fear drops and bandwidth recovers, state updates and pheromone deposits fill in the collective picture.&lt;/p&gt;

&lt;p&gt;The combination of pheromones and gossip produces emergent behavior that no single subsystem explicitly implements: without a central coordinator, the swarm learns the shape of the threat environment, avoids areas that have been costly, concentrates toward objectives, and maintains information coherence across node losses.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deep Dive 4: Safety and Trajectory Planning
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TTC-aware CBF with deadlock detection&lt;/strong&gt; extends the standard CBF formulation by incorporating Time-to-Collision estimates. Instead of only enforcing static separation distances, the CBF considers the closing velocity between drones — two drones approaching each other head-on at high speed trigger the safety clamp earlier than two drones drifting slowly toward each other. Deadlock detection identifies situations where CBF constraints from multiple drones create a gridlock and applies a resolution strategy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;APF trajectory planning&lt;/strong&gt; (Artificial Potential Fields) provides smooth, obstacle-aware paths. Attractive potentials pull drones toward objectives; repulsive potentials push them away from obstacles, NFZs, and other drones. The APF output feeds into the CBF as a desired velocity, which the CBF then projects onto the safe set.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CVaR risk scoring&lt;/strong&gt; (Conditional Value-at-Risk) quantifies the tail risk of mission plans. Instead of optimizing for expected outcomes, CVaR focuses on the worst-case percentile — what happens in the bottom 5% of scenarios? This integrates with the auction's bid evaluation and the ROE authorization pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NaN hardening&lt;/strong&gt; ensures that numerical corruption cannot silently propagate through the tick pipeline. Every subsystem includes guards that detect NaN values in inputs and outputs, preventing a single sensor glitch from cascading into nonsensical decisions across the entire swarm.&lt;/p&gt;




&lt;h2&gt;
  
  
  Performance: What the Numbers Mean
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Full swarm tick (20 drones)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.15 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;869 Hz max tick rate, well above any real-time control requirement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Combinatorial auction (50 drones, 20 tasks)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;465 µs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fits inside a single 1ms control loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Combinatorial auction (100 drones, 50 tasks)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.86 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Scales to larger swarms within real-time bounds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Particle filter (200 particles)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;75 µs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Leaves 925 µs for everything else in a 1ms tick&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 1.15 ms full tick number is the most important one. Real-time control for drone swarms typically requires update rates of 10–100 Hz (10–100 ms per tick). At 1.15 ms, STRIX runs 8–87x faster than required, which means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Margin for hardware latency&lt;/strong&gt; — you can afford significant communication and sensor latency without missing deadlines&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Headroom for scaling&lt;/strong&gt; — 20 drones at 1.15 ms means you have roughly 850 ms of remaining capacity before hitting 1-second ticks. The architecture is designed to scale toward 2000+ drones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic worst-case bounds&lt;/strong&gt; — Rust's lack of garbage collection means no GC pauses. The 1.15 ms number doesn't have hidden tail latency spikes from heap compaction.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The 75 µs particle filter number deserves context: this is a 200-particle bootstrap filter (Gordon, Salmond, Smith 1993) running in Rust with no SIMD optimization. The equivalent Python/NumPy implementation runs approximately 10x slower. For real-time operation, the Rust implementation matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Trenches Taught Me
&lt;/h2&gt;

&lt;p&gt;Building 20 subsystems in a single library across 8 months taught specific lessons that don't appear in textbooks.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Dual Particle Filter Architecture is Not Optional
&lt;/h3&gt;

&lt;p&gt;I initially implemented a single particle filter tracking swarm state. The problem: you're trying to use the same filter to track both where your drones are and where threats are. These are fundamentally different inference problems. Friendly state has known dynamics (you control the drones), high-frequency updates (onboard sensors), and low observation noise. Threat state has unknown dynamics (you don't control the threats), sparse updates (radar/EO/IR glimpses), and high observation noise.&lt;/p&gt;

&lt;p&gt;Separating into two filters — 200 particles for friendly navigation, 100 particles for adversarial threat tracking — immediately improved both. Each filter could be tuned for its specific dynamics without compromising the other.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Anti-Fragility is Not Resilience
&lt;/h3&gt;

&lt;p&gt;Resilience is returning to the prior state after a disturbance. Anti-fragility is being &lt;em&gt;stronger&lt;/em&gt; after a disturbance. These are categorically different.&lt;/p&gt;

&lt;p&gt;A resilient swarm loses two drones, falls back to a smaller formation, and tries to continue the original mission. An anti-fragile swarm loses two drones, updates kill-zone pricing (future drones avoid that area), increases gossip fanout (more information sharing under threat), triggers formation widening, and potentially performs better on subsequent engagements because it now has better threat map data.&lt;/p&gt;

&lt;p&gt;STRIX's kill-zone repricing mechanism is what makes the swarm anti-fragile rather than merely resilient. Every loss is a price signal that updates the auction's cost model. The swarm learns from attrition.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Glass-Box XAI is Non-Negotiable for Autonomous Weapons
&lt;/h3&gt;

&lt;p&gt;Every time the auction assigns a task differently than a human operator would expect, that operator needs to understand why. Every time the ROE engine declines an engagement authorization, a human needs to be able to reconstruct the causal chain: what did the threat classification show, what was the friend-foe ID confidence, what was the collateral risk estimate, why did the logic resolve to "hold"?&lt;/p&gt;

&lt;p&gt;Without this, autonomous systems operating in contested domains will lose human trust — and rightly so. The &lt;code&gt;strix-xai&lt;/code&gt; trace/narrator/replay pipeline is not a nice-to-have feature. For systems with kinetic authority, it's a prerequisite for legitimate use. Deterministic replay makes every decision reproducible.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Mathematical Elegance vs. Practical Heuristics
&lt;/h3&gt;

&lt;p&gt;The CBF (Control Barrier Functions) safety system has formal mathematical guarantees: forward invariance of the safe set, provably collision-free velocity fields. The auction system is heuristic — the scoring function is engineered to work well in practice, but there are no optimality proofs. The fear meta-parameter is empirically calibrated, not analytically derived from first principles.&lt;/p&gt;

&lt;p&gt;This tension is real. In practice, the formally-correct CBF runs in every tick and you trust it. The heuristic auction you test exhaustively (671 tests across 37+ files) and you trust the tests. Different parts of a complex system warrant different levels of formal rigor — the trick is knowing which parts need proofs and which parts need thorough empirical validation. The &lt;code&gt;strix-optimizer&lt;/code&gt; crate with its 62 tunable parameters and isotonic confidence calibration helps bridge this gap.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Fear as a Control Signal Produces Coherent Emergent Behavior
&lt;/h3&gt;

&lt;p&gt;When fear first went into the simulation, I expected it to make the swarm more conservative across the board — slower, more cautious, less effective. What actually happened was more interesting: high-fear swarms were more &lt;em&gt;communicative&lt;/em&gt;. More gossip, stronger pheromones, wider information sharing. They were slower at engaging, but they were much better at maintaining collective situational awareness.&lt;/p&gt;

&lt;p&gt;A high-fear swarm that backs off and talks to itself is often in a better position 30 seconds later than an overconfident swarm that pressed the engagement and took losses. Fear, implemented correctly, is not cowardice — it's a sophisticated information aggregation mechanism.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. The DSL Was An Afterthought That Became The Most Important Interface
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;strix-playground&lt;/code&gt; started as a test harness — a way to run scenarios without writing full integration tests every time. The &lt;code&gt;Playground&lt;/code&gt; builder DSL emerged from repeatedly typing the same setup code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Playground&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;.name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Ambush"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.drones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.threats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nn"&gt;ThreatSpec&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;approaching&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;400.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;8.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nn"&gt;ThreatSpec&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;flanking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;500.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;6.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;45.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nn"&gt;ThreatSpec&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;flanking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;500.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;6.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;60.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="nf"&gt;.wind&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="nf"&gt;.cbf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;CbfConfig&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="nf"&gt;.run_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;60.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's now the primary way anyone interacts with STRIX for evaluation and experimentation. Four preset scenarios (Ambush, GPS Denied, Attrition Cascade, Stress Test) cover the most important operational conditions. The DSL made the system approachable in a way that raw API calls to 15-module strix-core never could.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Adversarial Doctrines Change Everything
&lt;/h3&gt;

&lt;p&gt;Modeling threats as individual entities with simple motion patterns (Approaching, Circling, Retreating) was sufficient for basic scenarios. But real adversaries use structured tactics. Adding PROBING, FEINT, and COORDINATED doctrines forced a fundamental rethink of the threat tracker — it now has to distinguish between a genuine attack and a feint designed to draw resources away from the real objective. This is where CVaR risk scoring becomes critical: evaluating not just the expected threat but the tail-risk scenarios.&lt;/p&gt;




&lt;h2&gt;
  
  
  What It Doesn't Do — Honest Limitations
&lt;/h2&gt;

&lt;p&gt;This section matters more than any benchmark.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;STRIX is a research prototype.&lt;/strong&gt; It has never flown a real drone, in any context. The performance numbers are simulation results, not flight test data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Platform adapters are stubs.&lt;/strong&gt; The MAVLink and ROS2 adapters in &lt;code&gt;strix-adapters&lt;/code&gt; are architectural placeholders. Unless you compile with the &lt;code&gt;mavlink-hw&lt;/code&gt; feature flag (which requires additional hardware-specific dependencies), these are compile-time stubs that satisfy the interface but don't communicate with real hardware.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The edge LLM architecture is defined but empty.&lt;/strong&gt; The XAI narrator converts decision traces to human-readable text using a templating approach. The architecture includes provisions for an edge-deployed language model to produce richer narrative explanations, but no pretrained model is included.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No optimality proofs for the auction.&lt;/strong&gt; The auction scoring is heuristic. It performs well across the test scenarios. It does not have mathematical guarantees of optimality. The SMCO optimizer helps tune parameters but does not provide theoretical bounds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not ITAR-controlled.&lt;/strong&gt; STRIX is open-source (Apache 2.0). The codebase does not contain classified algorithms, export-controlled technologies, or information that would trigger ITAR restrictions. It is a research implementation of publicly available algorithms — particle filters, auction theory, CBF, ant colony optimization — applied to the drone swarm domain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;671 tests is the minimum honest number, not a boast.&lt;/strong&gt; The test suite (560 Rust + 111 Python) covers the major subsystems and the known failure modes. It does not constitute exhaustive verification of a safety-critical system. Anyone deploying STRIX in a real operational context would need substantially more validation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NaN hardening&lt;/strong&gt; is applied across subsystems but not formally verified for all edge cases.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Doesn't This Already Exist?
&lt;/h2&gt;

&lt;p&gt;The honest answer: some of it does exist, in parts, in proprietary systems operated by defense contractors and national laboratories. What doesn't exist is an open-source, unified implementation of all these subsystems working together, with a permissive license and a readable codebase.&lt;/p&gt;

&lt;p&gt;PX4, ArduPilot, and ROS2 MavROS are excellent flight stacks, but they're focused on single-drone operation and don't include swarm coordination, task auctions, or EW response. MAVSDK is a clean interface layer but has no intelligence. Most academic swarm research is MATLAB or Python — correct in theory, 10–100x too slow for real-time operation.&lt;/p&gt;

&lt;p&gt;STRIX occupies a specific niche: close enough to production-quality Rust to be benchmarkable, open enough to be studied and modified, comprehensive enough to be a complete research platform rather than a single-paper implementation. With scalability toward 2000+ drones as a design goal, it's built for the next generation of swarm research.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The current architecture has clear extension points:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Real hardware integration&lt;/strong&gt; — completing the MAVLink adapter with actual serial communication and testing with real flight controllers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distributed auction&lt;/strong&gt; — the current auction runs centrally per swarm tick; a fully distributed variant using the gossip network would eliminate the remaining centralized component&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reinforcement learning integration&lt;/strong&gt; — replacing heuristic auction scoring with a learned policy, using the particle filter state as the observation space&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-swarm coordination&lt;/strong&gt; — the fractal hierarchy architecture supports nested swarm-of-swarms coordination; this is partially implemented but not fully tested&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge LLM narrator&lt;/strong&gt; — completing the XAI narrator with a deployed language model for richer after-action reports&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale validation at 2000+ drones&lt;/strong&gt; — stress-testing the architecture at full target scale&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/RMANOV/strix
&lt;span class="nb"&gt;cd &lt;/span&gt;strix

&lt;span class="c"&gt;# Run all 671 tests&lt;/span&gt;
cargo &lt;span class="nb"&gt;test&lt;/span&gt;

&lt;span class="c"&gt;# Run a specific scenario&lt;/span&gt;
cargo run &lt;span class="nt"&gt;--example&lt;/span&gt; ambush_scenario

&lt;span class="c"&gt;# Run the full stress test (50 drones, 10 threats, 2 NFZs, GPS denial, drone losses)&lt;/span&gt;
cargo run &lt;span class="nt"&gt;--example&lt;/span&gt; stress_test

&lt;span class="c"&gt;# Python bindings&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;strix-python
pip &lt;span class="nb"&gt;install &lt;/span&gt;maturin
maturin develop
python examples/basic_swarm.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;strix-playground&lt;/code&gt; crate has four preset scenarios that cover the main operational conditions: Ambush, GPS Denied, Attrition Cascade, and Stress Test. Start there.&lt;/p&gt;




&lt;h2&gt;
  
  
  Codebase
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repo&lt;/strong&gt;: &lt;a href="https://github.com/RMANOV/strix" rel="noopener noreferrer"&gt;github.com/RMANOV/strix&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License&lt;/strong&gt;: Apache 2.0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lines&lt;/strong&gt;: 34,889 Rust + 7,493 Python (~42,400 total)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests&lt;/strong&gt;: 671 (560 Rust + 111 Python)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crates&lt;/strong&gt;: 9&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code is readable. The architecture is documented. The limitations are honest.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you find the approach interesting — or if you find something wrong — open an issue.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;STRIX is a research prototype. Not flight-tested. Not production-ready. Not combat-proven. Open source. Apache 2.0.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>robotics</category>
      <category>opensource</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>I Turned a Webcam Into an Ambient Light Sensor</title>
      <dc:creator>Ruslan Manov</dc:creator>
      <pubDate>Mon, 09 Feb 2026 21:50:55 +0000</pubDate>
      <link>https://forem.com/john_smith_9ff0ff4cfcffdc/i-turned-a-webcam-into-an-ambient-light-sensor-265l</link>
      <guid>https://forem.com/john_smith_9ff0ff4cfcffdc/i-turned-a-webcam-into-an-ambient-light-sensor-265l</guid>
      <description>&lt;p&gt;&lt;em&gt;Building a Rust-Powered Adaptive Brightness Controller for the Desktop That Mobile Left Behind&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The 3 AM Problem
&lt;/h2&gt;

&lt;p&gt;It starts at 11 PM. You're deep in code, the room is dark, your monitor is comfortable. By 3 AM you're still going — the screen hasn't changed, but your eyes ache and you don't know why. By 7 AM, sunlight is flooding the room. Your monitor is still at midnight brightness. The text is washed out. You squint, you lean forward, you finally remember to hit Fn+Up five times.&lt;/p&gt;

&lt;p&gt;Now pick up your phone. It handled all of this automatically. Since 2009.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every phone made in the last 15 years auto-adjusts brightness.&lt;/strong&gt; The ambient light sensor — a $0.30 chip — detects room light and smoothly adjusts the screen. You never think about it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every desktop?&lt;/strong&gt; Nothing. Unless you have a premium laptop with a dedicated ambient light sensor (Dell XPS, MacBook, ThinkPad X1), your screen brightness is 100% manual. That's most desktops, most monitors, and most budget laptops.&lt;/p&gt;

&lt;p&gt;I spend too many nights coding sessions that bleed into mornings. The brightness transition problem wasn't theoretical — it was happening to me every week. So I built a solution.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Insight: You Already Have a Light Sensor
&lt;/h2&gt;

&lt;p&gt;Every laptop has a webcam. Every desktop has a USB webcam (or can get one for $10). A webcam captures light. If you can measure the average brightness of a webcam frame, you can measure ambient light.&lt;/p&gt;

&lt;p&gt;Similarly: every computer has a microphone. If the room is noisy (dishwasher, traffic, music), you probably want higher volume. If it's quiet (3 AM, everyone sleeping), you want lower volume.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No extra hardware. No dedicated sensors. Just the webcam and microphone you already have.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture: Dual Backend, Graceful Degradation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────┐
│     adaptive_brightness_volume.py        │
│           (Main Controller)              │
└──────────────────┬───────────────────────┘
                   │
         ┌─────────┴──────────┐
         │                    │
   ┌─────▼──────┐     ┌──────▼───────┐
   │ Rust SIMD  │     │ Python+Numba │
   │ Engine     │     │ JIT Fallback │
   │ (3-6ms)    │     │ (12.3ms)     │
   └─────┬──────┘     └──────┬───────┘
         │                    │
   ┌─────▼────────────────────▼────────┐
   │        System Layer               │
   │  Camera (OpenCV/V4L2)             │
   │  Audio (cpal/SoundDevice)         │
   │  Brightness (sysfs/DDC-CI)        │
   │  Volume (ALSA/PulseAudio)         │
   └───────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Python controller auto-detects whether the Rust engine is compiled. If yes, it calls Rust functions via PyO3 with zero-copy NumPy interop. If not, it falls back to Python with Numba JIT compilation (still 10-100x faster than pure Python).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This means you can start using the tool immediately&lt;/strong&gt; (Python mode) and optionally compile the Rust engine later for maximum performance.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Performance Journey
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Phase 1: Pure Python (~100ms cycles)
&lt;/h3&gt;

&lt;p&gt;The first version processed webcam frames with NumPy and called &lt;code&gt;subprocess&lt;/code&gt; for brightness control. It worked, but each cycle took ~100ms — fine for a 30-minute cron job, but noticeable as a real-time daemon.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2: Numba JIT (12.3ms cycles)
&lt;/h3&gt;

&lt;p&gt;Adding &lt;code&gt;@njit&lt;/code&gt; decorators to the hot numerical functions gave a 10x speedup with zero algorithm changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@njit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compute_noise_level&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;audio_data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;RMS noise calculation — Numba compiles to native code&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;sample&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;audio_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;sample&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sample&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;audio_data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Startup increased (Numba JIT compilation takes 2-3 seconds on first run), but steady-state performance was solid.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 3: Rust SIMD (3-6ms cycles) — v1.2.0 → v2.0.0
&lt;/h3&gt;

&lt;p&gt;The final evolution: a Rust workspace with 3 crates, spanning 8 tagged releases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core crate&lt;/strong&gt; — 8 specialized modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// brightness.rs — 8-wide SIMD vectorization&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;calculate_brightness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="nf"&gt;.chunks_exact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;remainder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="nf"&gt;.remainder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="nf"&gt;.fold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Compiler auto-vectorizes this to SIMD&lt;/span&gt;
        &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="py"&gt;.sum&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;remainder&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="py"&gt;.sum&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// change.rs — branchless significant change detection&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;check_significant_change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.abs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;threshold&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;FFI crate&lt;/strong&gt; — PyO3 zero-copy bindings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[pyfunction]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;compute_noise_level&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PyReadonlyArrayDyn&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;slice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="nf"&gt;.as_slice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nn"&gt;adaptive_core&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;compute_noise_level&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;)&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;Binary crate&lt;/strong&gt; — standalone Rust controller with crossbeam lock-free channels.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Numbers
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Python+Numba&lt;/th&gt;
&lt;th&gt;Rust SIMD&lt;/th&gt;
&lt;th&gt;Speedup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Audio RMS&lt;/td&gt;
&lt;td&gt;0.15ms&lt;/td&gt;
&lt;td&gt;0.03ms&lt;/td&gt;
&lt;td&gt;5x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Brightness mapping&lt;/td&gt;
&lt;td&gt;0.008ms&lt;/td&gt;
&lt;td&gt;0.002ms&lt;/td&gt;
&lt;td&gt;4x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Volume mapping&lt;/td&gt;
&lt;td&gt;0.015ms&lt;/td&gt;
&lt;td&gt;0.003ms&lt;/td&gt;
&lt;td&gt;5x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Screen analysis&lt;/td&gt;
&lt;td&gt;0.05ms&lt;/td&gt;
&lt;td&gt;0.01ms&lt;/td&gt;
&lt;td&gt;5x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Full cycle&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12.3ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3-6ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2-4x&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;50-80MB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10-20MB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4x&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Startup&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2-3s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&amp;lt;100ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;30x&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Version Timeline — 8 Releases, Each Solving a Real Problem
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tag&lt;/th&gt;
&lt;th&gt;Milestone&lt;/th&gt;
&lt;th&gt;What It Fixed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;v1.0.0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;First stable release&lt;/td&gt;
&lt;td&gt;Dual-backend architecture ready for daily use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;v1.1.0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Security &amp;amp; stability&lt;/td&gt;
&lt;td&gt;7 bugs: bare &lt;code&gt;except:&lt;/code&gt; catching &lt;code&gt;SystemExit&lt;/code&gt;, shell injection, sysfs brightness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;v1.2.0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Auto-exit mode&lt;/td&gt;
&lt;td&gt;Converge in ~23s &amp;amp; stop — no more daemon overhead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;v1.2.0-windows&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Windows Rust port&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;nix&lt;/code&gt;→&lt;code&gt;ctrlc&lt;/code&gt;, V4L2→NOAA sun sim, PowerShell WMI, C# Core Audio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;v1.2.1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Auto-exit default&lt;/td&gt;
&lt;td&gt;Convergence approach proved so reliable it became default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;v1.2.2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Convergence fix&lt;/td&gt;
&lt;td&gt;Rust compared smoothed vs current target instead of previous — subtle but critical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;v1.3.0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Solar intelligence&lt;/td&gt;
&lt;td&gt;NOAA seasonal adaptation ported to Rust engine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;v2.0.0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full maturity&lt;/td&gt;
&lt;td&gt;V4L2 exposure lock, NVIDIA workaround, comprehensive Windows support&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;strong&gt;v1.2.0-windows&lt;/strong&gt; port is particularly notable — it replaced Linux-only system calls (&lt;code&gt;nix&lt;/code&gt; for signals, &lt;code&gt;v4l&lt;/code&gt; for camera) with cross-platform alternatives (&lt;code&gt;ctrlc&lt;/code&gt;, PowerShell WMI brightness, pre-compiled C# helper for Windows Core Audio volume) while keeping the same SIMD core untouched. The architecture's separation of core algorithms from system integration paid off.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 5 Design Decisions That Made It Work
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Empirical Brightness Curves (Theory Was Wrong)
&lt;/h3&gt;

&lt;p&gt;I started with a theoretical linear brightness mapping. Wrong — too aggressive at the extremes. Then a logarithmic curve. Wrong — too conservative in the mid-range.&lt;/p&gt;

&lt;p&gt;The final solution: a piecewise brightness curve tuned through &lt;strong&gt;3 iterations of daily use&lt;/strong&gt; over several weeks. The multiplier went from 0.1 (barely moves) to 0.24 (noticeable but conservative) to 0.35 (natural-feeling).&lt;/p&gt;

&lt;p&gt;The comfortable range turned out to be &lt;strong&gt;5-45% brightness&lt;/strong&gt; and &lt;strong&gt;3-35% volume&lt;/strong&gt;. Human brightness perception is deeply nonlinear and context-dependent — no formula captures it. You have to live with the tool and adjust.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Flash Detection Prevents False Activations
&lt;/h3&gt;

&lt;p&gt;Early versions reacted to everything: car headlights through the window, lightning, opening a bright browser tab. The solution: a &lt;strong&gt;40-second environmental sample&lt;/strong&gt; before committing to adjustment.&lt;/p&gt;

&lt;p&gt;The manager script reads brightness/volume, waits 40 seconds, reads again. If the delta is &amp;lt;40%, it exits — the change was transient. This one feature eliminated 90% of false activations and reduced energy usage from constant polling to targeted activation.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. NOAA Sunrise/Sunset = ~90% Energy Savings
&lt;/h3&gt;

&lt;p&gt;Why run the controller at 2 PM when the sun hasn't moved meaningfully in hours? Or at 2 AM in a stable dark room?&lt;/p&gt;

&lt;p&gt;The tool calculates &lt;strong&gt;actual sunrise and sunset times&lt;/strong&gt; for the user's geographic coordinates using NOAA astronomical algorithms. It only activates during transition windows: 30 minutes before sunrise → 2 hours after sunrise, and 30 minutes before sunset → 2 hours after sunset.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# sunrise_sunset_calculator.py — NOAA algorithm
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_sunrise_sunset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Pure Python NOAA solar calculations.
    Returns sunrise/sunset times for any location on Earth.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;julian_day&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;to_julian&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;solar_noon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculate_solar_noon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;julian_day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;hour_angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculate_hour_angle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;julian_day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sunrise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;solar_noon&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;hour_angle&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt;
    &lt;span class="n"&gt;sunset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;solar_noon&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;hour_angle&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sunrise&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sunset&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Energy savings evolution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;v1: Every 5 minutes, always → &lt;strong&gt;0% savings&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;v2: Every 30 minutes with flash detection → &lt;strong&gt;81% savings&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;v3: Only during solar transitions → &lt;strong&gt;~90% savings&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Auto-Exit Convergence (Not a Daemon)
&lt;/h3&gt;

&lt;p&gt;Most similar tools run as permanent daemons. This tool doesn't. It activates, converges to optimal brightness/volume in &lt;strong&gt;~23 seconds&lt;/strong&gt;, then exits cleanly. The cron-based manager handles scheduling.&lt;/p&gt;

&lt;p&gt;Why? Because a daemon that holds the webcam and microphone open causes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Taskbar microphone icon flickering&lt;/li&gt;
&lt;li&gt;Camera LED staying on&lt;/li&gt;
&lt;li&gt;Other apps can't access the camera&lt;/li&gt;
&lt;li&gt;CPU/memory waste during stable conditions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Auto-exit means: activate → adapt → release everything → stop. Clean, resource-friendly, invisible.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Comprehensive Cleanup Eliminates Browser Lag
&lt;/h3&gt;

&lt;p&gt;This was a hard-won lesson. OpenCV + audio capture + Numba JIT cache = significant resource footprint. Without proper cleanup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chrome would stutter for 10-20 seconds after the script finished&lt;/li&gt;
&lt;li&gt;Audio devices would stay locked&lt;/li&gt;
&lt;li&gt;Memory wouldn't be released&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cleanup sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Thread termination with timeout&lt;/li&gt;
&lt;li&gt;OpenCV device release (&lt;code&gt;cap.release()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Audio stream close&lt;/li&gt;
&lt;li&gt;Numba JIT cache clearing&lt;/li&gt;
&lt;li&gt;Multi-pass garbage collection (&lt;code&gt;gc.collect()&lt;/code&gt; × 3)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This eliminated the browser lag completely.&lt;/p&gt;




&lt;h2&gt;
  
  
  Competitive Landscape
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;This Tool&lt;/th&gt;
&lt;th&gt;Clight&lt;/th&gt;
&lt;th&gt;wluma&lt;/th&gt;
&lt;th&gt;Windows/macOS Built-in&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Light detection&lt;/td&gt;
&lt;td&gt;Webcam&lt;/td&gt;
&lt;td&gt;Webcam + ALS&lt;/td&gt;
&lt;td&gt;ALS + Screen&lt;/td&gt;
&lt;td&gt;Hardware ALS only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Volume adaptation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance engine&lt;/td&gt;
&lt;td&gt;Rust SIMD&lt;/td&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;td&gt;OS-native&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rust binary cross-platform&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Linux + Windows&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Platforms&lt;/td&gt;
&lt;td&gt;Linux + Windows&lt;/td&gt;
&lt;td&gt;Linux only&lt;/td&gt;
&lt;td&gt;Wayland only&lt;/td&gt;
&lt;td&gt;OS-locked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Smart scheduling&lt;/td&gt;
&lt;td&gt;NOAA sunrise/sunset&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;External hardware&lt;/td&gt;
&lt;td&gt;None required&lt;/td&gt;
&lt;td&gt;None required&lt;/td&gt;
&lt;td&gt;ALS recommended&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;ALS required&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto-exit&lt;/td&gt;
&lt;td&gt;Yes (~23s)&lt;/td&gt;
&lt;td&gt;No (daemon)&lt;/td&gt;
&lt;td&gt;No (daemon)&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Release cadence&lt;/td&gt;
&lt;td&gt;8 releases (v1→v2)&lt;/td&gt;
&lt;td&gt;Slow&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;OS-tied&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open source&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;GPL&lt;/td&gt;
&lt;td&gt;ISC&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The gap this fills:&lt;/strong&gt; If your machine doesn't have a hardware ambient light sensor (most desktops, budget laptops, external monitors), there is no good cross-platform solution. Clight is Linux-only with no volume support. wluma is Wayland-only and admits webcam detection is unreliable. Windows/macOS require dedicated hardware.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; f.lux is not a competitor — it adjusts &lt;strong&gt;color temperature&lt;/strong&gt; (blue light warmth), not &lt;strong&gt;brightness levels&lt;/strong&gt;. They solve different problems. Use both together.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone&lt;/span&gt;
git clone https://github.com/RMANOV/Auto-Brightness-Sound-Levels-Windows-Linux.git
&lt;span class="nb"&gt;cd &lt;/span&gt;Auto-Brightness-Sound-Levels-Windows-Linux

&lt;span class="c"&gt;# Quick start (Python mode)&lt;/span&gt;
python adaptive_brightness_volume.py

&lt;span class="c"&gt;# With Rust engine (optional, for maximum performance)&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;adaptive-rust &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; cargo build &lt;span class="nt"&gt;--release&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; .. &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; python adaptive_brightness_volume.py  &lt;span class="c"&gt;# Auto-detects Rust&lt;/span&gt;

&lt;span class="c"&gt;# Automated scheduling&lt;/span&gt;
./install_crontab.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Live with your tool.&lt;/strong&gt; Brightness mapping can't be designed theoretically. You need weeks of daily use to get the curve right.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Energy efficiency is a feature, not an afterthought.&lt;/strong&gt; Going from always-on to sunrise/sunset scheduling changed the tool from "annoying background process" to "invisible helper."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean up your resources.&lt;/strong&gt; In system-level tools, sloppy cleanup = user-visible lag. The multi-stage cleanup sequence was the difference between "Chrome stutters after my script" and "I forgot the script even ran."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rust SIMD is real.&lt;/strong&gt; The 2-4x cycle time improvement is nice, but the 4x memory reduction and 30x startup improvement are what made the Rust version feel qualitatively different.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graceful degradation is worth the complexity.&lt;/strong&gt; Dual-backend means users can start immediately with Python and upgrade to Rust later. Multiple brightness backends (sysfs, DDC-CI, xrandr) mean it works on more hardware configurations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-platform Rust is achievable with clean architecture.&lt;/strong&gt; The v1.2.0-windows port replaced only the system integration layer (&lt;code&gt;nix&lt;/code&gt;→&lt;code&gt;ctrlc&lt;/code&gt;, V4L2→NOAA sun simulation, sysfs→PowerShell WMI, ALSA→C# Core Audio) while the SIMD core compiled unchanged. 8 releases in rapid succession — each tagged version solving a real problem from daily use.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/RMANOV/Auto-Brightness-Sound-Levels-Windows-Linux" rel="noopener noreferrer"&gt;https://github.com/RMANOV/Auto-Brightness-Sound-Levels-Windows-Linux&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;License:&lt;/strong&gt; MIT&lt;br&gt;
&lt;strong&gt;Stack:&lt;/strong&gt; Rust + SIMD | PyO3 | OpenCV | cpal | Numba JIT | NOAA Algorithms | PowerShell WMI | C# Core Audio&lt;br&gt;
&lt;strong&gt;Releases:&lt;/strong&gt; v1.0.0 → v2.0.0 (8 tags) | Dependabot active&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built during too many 11 PM → 7 AM sessions where I forgot to adjust my screen brightness. My eyes say thank you.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>python</category>
      <category>linux</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Finding Primes of the Form p^2 + 4q^2: From Oxford Mathematics to Python Multiprocessing</title>
      <dc:creator>Ruslan Manov</dc:creator>
      <pubDate>Sun, 01 Feb 2026 21:17:10 +0000</pubDate>
      <link>https://forem.com/john_smith_9ff0ff4cfcffdc/finding-primes-of-the-form-p2-4q2-from-oxford-mathematics-to-python-multiprocessing-1ci0</link>
      <guid>https://forem.com/john_smith_9ff0ff4cfcffdc/finding-primes-of-the-form-p2-4q2-from-oxford-mathematics-to-python-multiprocessing-1ci0</guid>
      <description>&lt;p&gt;What do 41, 61, and 109 have in common?&lt;/p&gt;

&lt;p&gt;They are all prime numbers. But they share something far more specific: each can be written as p^2 + 4q^2 where &lt;strong&gt;both p and q are themselves prime&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;41 = 5^2 + 4(2^2) = 25 + 16&lt;/li&gt;
&lt;li&gt;61 = 5^2 + 4(3^2) = 25 + 36&lt;/li&gt;
&lt;li&gt;109 = 3^2 + 4(5^2) = 9 + 100&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In 2024, mathematicians Ben Green (University of Oxford) and Mehta Sohni (Columbia University) proved that there are &lt;strong&gt;infinitely many&lt;/strong&gt; such primes. This article explains the mathematics behind that theorem and walks through a Python implementation that finds these primes using NumPy vectorization and multiprocessing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mathematics: Why p^2 + 4q^2?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fermat's Two-Square Theorem (1640)
&lt;/h3&gt;

&lt;p&gt;The story begins nearly 400 years ago. Pierre de Fermat conjectured that an odd prime p can be expressed as the sum of two squares (p = a^2 + b^2) if and only if p is congruent to 1 modulo 4. Euler proved this in 1749.&lt;/p&gt;

&lt;p&gt;Examples: 5 = 1^2 + 2^2, 13 = 2^2 + 3^2, 17 = 1^2 + 4^2, 29 = 2^2 + 5^2.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quadratic Forms
&lt;/h3&gt;

&lt;p&gt;The expression a^2 + 4b^2 is a &lt;strong&gt;binary quadratic form&lt;/strong&gt; -- a polynomial of the form ax^2 + bxy + cy^2 with specific discriminant. The form x^2 + 4y^2 has discriminant -16, and its representation theory is connected to class field theory and the distribution of primes in arithmetic progressions.&lt;/p&gt;

&lt;p&gt;A prime p is representable as a^2 + 4b^2 (with a, b positive integers) if and only if p = 2 or p is congruent to 1 modulo 4. This is a classical result.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Green-Sohni Restriction
&lt;/h3&gt;

&lt;p&gt;The breakthrough question was: what happens when we require a and b to themselves be prime? Green and Sohni proved that the set of primes expressible as p^2 + 4q^2 with p, q both prime is &lt;strong&gt;infinite&lt;/strong&gt;. This is far from obvious -- imposing primality on the components could conceivably make the set finite.&lt;/p&gt;

&lt;p&gt;Their proof uses deep tools from analytic number theory, including the theory of Type I/II sums and transference principles originally developed for studying primes in arithmetic progressions.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Algorithm: Step by Step
&lt;/h2&gt;

&lt;p&gt;The algorithm has four phases:&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 1: Sieve Generation
&lt;/h3&gt;

&lt;p&gt;Generate all primes up to sqrt(limit) using the Sieve of Eratosthenes. These primes serve as candidate values for both p and q.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_primes_numpy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sieve&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sieve&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sieve&lt;/span&gt;&lt;span class="p"&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="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;))&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;if&lt;/span&gt; &lt;span class="n"&gt;sieve&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;sieve&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nonzero&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sieve&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key optimization: &lt;code&gt;sieve[i*i::i] = False&lt;/code&gt; is a single NumPy slice assignment that marks all multiples of i starting from i^2. No Python loop over individual elements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2: Candidate Enumeration
&lt;/h3&gt;

&lt;p&gt;For each prime p, compute p^2 + 4q^2 for all primes q where the result stays below the limit. NumPy vectorization makes this a single array operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;q_values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;q_primes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;q_primes&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;q_primes&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;p_squared&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;results_array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p_squared&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;square&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q_values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One line. All q values. No loop.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 3: Primality Verification
&lt;/h3&gt;

&lt;p&gt;Each candidate is checked for primality using trial division with LRU caching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@lru_cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_prime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&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;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;))&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;i&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;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cache is critical: different (p, q) pairs can produce the same candidate value, and caching avoids redundant verification.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 4: Parallel Execution
&lt;/h3&gt;

&lt;p&gt;The prime array is split into chunks, each assigned to a separate process via &lt;code&gt;multiprocessing.Pool&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Pool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;num_processes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process_prime_chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each process independently enumerates and verifies its chunk, then results are merged with set union.&lt;/p&gt;




&lt;h2&gt;
  
  
  Performance Analysis
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Primes Found&lt;/th&gt;
&lt;th&gt;Time (1 core)&lt;/th&gt;
&lt;th&gt;Time (4 cores)&lt;/th&gt;
&lt;th&gt;Speedup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;0.01s&lt;/td&gt;
&lt;td&gt;0.01s&lt;/td&gt;
&lt;td&gt;~1x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;38&lt;/td&gt;
&lt;td&gt;0.02s&lt;/td&gt;
&lt;td&gt;0.01s&lt;/td&gt;
&lt;td&gt;~2x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100,000&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;0.15s&lt;/td&gt;
&lt;td&gt;0.05s&lt;/td&gt;
&lt;td&gt;~3x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;998&lt;/td&gt;
&lt;td&gt;1.8s&lt;/td&gt;
&lt;td&gt;0.52s&lt;/td&gt;
&lt;td&gt;~3.5x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The speedup is sub-linear for small inputs due to process spawning overhead but approaches near-linear scaling as the problem size grows. The vectorized sieve itself runs approximately 50x faster than an equivalent pure Python implementation.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Stream Generator
&lt;/h2&gt;

&lt;p&gt;For exploration without a fixed upper bound, the infinite generator yields primes of this form one at a time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_p2_plus_4q2_primes_stream&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Generator&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;primes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_primes_numpy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;primes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;p_squared&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
        &lt;span class="n"&gt;q_values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;primes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;primes&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;p_squared&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p_squared&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;square&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q_values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;is_prime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
                &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_p2_plus_4q2_primes_stream&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output: 41, 61, 109, 137, 149, 157, 269, 317, 389, 397&lt;/p&gt;




&lt;h2&gt;
  
  
  Concrete Examples: The First 20 Green-Sohni Primes
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Prime&lt;/th&gt;
&lt;th&gt;p&lt;/th&gt;
&lt;th&gt;q&lt;/th&gt;
&lt;th&gt;Verification&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;25 + 16 = 41&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;61&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;25 + 36 = 61&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;109&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;9 + 100 = 109&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;137&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;121 + 16 = 137&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;149&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;49 + 100 = 149&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;157&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;121 + 36 = 157&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;269&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;169 + 100 = 269&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;317&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;121 + 196 = 317&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;389&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;289 + 100 = 389&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;397&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;361 + 36 = 397&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;461&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;361 + 100 = 461&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;509&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;25 + 484 = 509&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;557&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;361 + 196 = 557&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;593&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;9 + 484 = 493...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;653&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;529 + ...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;(Table truncated -- run the code to see more.)&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Historical Timeline
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Milestone&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;~240 BC&lt;/td&gt;
&lt;td&gt;Eratosthenes develops the prime sieve&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1640&lt;/td&gt;
&lt;td&gt;Fermat conjectures the two-square theorem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1749&lt;/td&gt;
&lt;td&gt;Euler proves Fermat's conjecture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1801&lt;/td&gt;
&lt;td&gt;Gauss publishes &lt;em&gt;Disquisitiones Arithmeticae&lt;/em&gt;, foundational work on quadratic forms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1837&lt;/td&gt;
&lt;td&gt;Dirichlet proves his theorem on primes in arithmetic progressions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024&lt;/td&gt;
&lt;td&gt;Green-Sohni prove infinitely many primes of the form p^2 + 4q^2 with p, q prime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2025&lt;/td&gt;
&lt;td&gt;This implementation: NumPy + multiprocessing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/RMANOV/Prime-Numbers-Counting-Algorithm.git
&lt;span class="nb"&gt;cd &lt;/span&gt;Prime-Numbers-Counting-Algorithm
pip &lt;span class="nb"&gt;install &lt;/span&gt;numpy
python &lt;span class="s2"&gt;"Prime Numbers Counting Algorithm"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script runs benchmarks at three scales (1,000 / 10,000 / 100,000) and streams the first 10 primes of this form.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Learned Building This
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;NumPy slice assignment is magical.&lt;/strong&gt; The sieve step &lt;code&gt;sieve[i*i::i] = False&lt;/code&gt; replaces an O(n/i) Python loop with a single C-level memory operation. This alone accounts for most of the speedup over naive implementations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;LRU caching and primality testing are a natural pair.&lt;/strong&gt; In this problem, multiple (p, q) pairs can generate the same candidate. Without caching, the same number gets trial-divided repeatedly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multiprocessing overhead matters at small scales.&lt;/strong&gt; For limit &amp;lt; 10,000, the single-threaded version is faster because process spawning dominates. The crossover point is around limit = 50,000 on a 4-core machine.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mathematical elegance and computational efficiency often align.&lt;/strong&gt; The structure of the problem (quadratic form with prime inputs) naturally decomposes into independent subproblems (one per p-value), which maps perfectly onto data parallelism.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/RMANOV/Prime-Numbers-Counting-Algorithm" rel="noopener noreferrer"&gt;https://github.com/RMANOV/Prime-Numbers-Counting-Algorithm&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;License:&lt;/strong&gt; MIT&lt;/p&gt;




</description>
      <category>python</category>
      <category>math</category>
      <category>algorithms</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How to Count a Billion Unique Items with Almost No Memory</title>
      <dc:creator>Ruslan Manov</dc:creator>
      <pubDate>Sun, 01 Feb 2026 21:07:06 +0000</pubDate>
      <link>https://forem.com/john_smith_9ff0ff4cfcffdc/how-to-count-a-billion-unique-items-with-almost-no-memory-735</link>
      <guid>https://forem.com/john_smith_9ff0ff4cfcffdc/how-to-count-a-billion-unique-items-with-almost-no-memory-735</guid>
      <description>&lt;p&gt;Your database's &lt;code&gt;COUNT(DISTINCT user_id)&lt;/code&gt; on 1 billion rows uses approximately 8 GB of RAM. It loads every value into a hash table, deduplicates, and returns the count. This works. Until it doesn't.&lt;/p&gt;

&lt;p&gt;What if I told you there is an algorithm that does the same thing with 98% accuracy using a few kilobytes of memory?&lt;/p&gt;

&lt;p&gt;This is the CVM algorithm, and I built a Python implementation you can use today.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Why Exact Counting Fails at Scale
&lt;/h2&gt;

&lt;p&gt;Counting unique elements sounds trivial. In Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;unique_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is O(N) memory. Every element is stored. For a stream of 1 billion 64-bit integers, that &lt;code&gt;set()&lt;/code&gt; consumes roughly 8 GB. For strings, it is worse.&lt;/p&gt;

&lt;p&gt;In production, this manifests as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database &lt;code&gt;COUNT(DISTINCT)&lt;/code&gt; queries that OOM on large tables&lt;/li&gt;
&lt;li&gt;ETL pipelines that crash when computing unique user counts&lt;/li&gt;
&lt;li&gt;Streaming systems that cannot hold state for high-cardinality fields&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The question becomes: &lt;strong&gt;can we estimate the number of unique elements without storing them?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer has been "yes" for 40 years. The quality of that "yes" has improved dramatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  A 40-Year Quest: The History of Probabilistic Counting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1985 — Flajolet-Martin
&lt;/h3&gt;

&lt;p&gt;Philippe Flajolet and G. Nigel Martin published the first probabilistic distinct counter. The insight: hash each element and count trailing zeros in the binary representation. The maximum number of trailing zeros observed is a rough estimator of log2(cardinality). Brilliant but noisy — error rates of 20-30%.&lt;/p&gt;

&lt;h3&gt;
  
  
  2003 — LogLog (Durand-Flajolet)
&lt;/h3&gt;

&lt;p&gt;Marianne Durand and Philippe Flajolet improved FM by using multiple buckets (registers) and averaging. LogLog brought error down to ~1.3/sqrt(m) where m is the number of registers. With 1024 registers, that is about 4% error.&lt;/p&gt;

&lt;h3&gt;
  
  
  2007 — HyperLogLog
&lt;/h3&gt;

&lt;p&gt;Flajolet, Fusy, Gandouet, and Meunier refined LogLog with harmonic mean aggregation. HyperLogLog achieves ~1.04/sqrt(m) error, uses about 1.5 KB for 2% accuracy, and has become the industry standard. Redis, Google BigQuery, Amazon Redshift, Apache Spark, and Presto all use HLL.&lt;/p&gt;

&lt;h3&gt;
  
  
  2024 — CVM: A Different Path
&lt;/h3&gt;

&lt;p&gt;Sourav Chakraborti, N.V. Vinodchandran, and Kuldeep S. Meel proposed an entirely different approach. Instead of hashing and counting bit patterns, CVM uses &lt;strong&gt;direct stochastic sampling with geometric probability&lt;/strong&gt;. No hash functions. No bit manipulation. Just randomized set membership.&lt;/p&gt;




&lt;h2&gt;
  
  
  How CVM Works
&lt;/h2&gt;

&lt;p&gt;The algorithm is surprisingly simple. Here is the complete mental model:&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;Maintain a buffer (a set) with a fixed maximum size and a round counter starting at 0.&lt;/p&gt;

&lt;h3&gt;
  
  
  Processing
&lt;/h3&gt;

&lt;p&gt;For each element in the stream:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;If the element is already in the buffer:&lt;/strong&gt; flip a biased coin (with probability depending on the current round). If it comes up "remove," discard it. Otherwise, keep it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If the element is not in the buffer:&lt;/strong&gt; add it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If the buffer is full:&lt;/strong&gt; start a new round — randomly evict half the elements and increment the round counter.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Estimation
&lt;/h3&gt;

&lt;p&gt;The estimate of unique elements is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;estimate = |buffer| * 2^round
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Works
&lt;/h3&gt;

&lt;p&gt;Each round doubles the "forgetting rate." After round 0, all elements are kept. After round 1, each element has a 1/2 chance of surviving. After round 2, 1/4. After round k, 1/2^k.&lt;/p&gt;

&lt;p&gt;This means the buffer always contains a &lt;strong&gt;uniform random sample&lt;/strong&gt; of the unique elements seen so far, scaled by the geometric probability. The scaling factor &lt;code&gt;2^round&lt;/code&gt; corrects for the sampling rate.&lt;/p&gt;

&lt;p&gt;The beauty is that elements already in the buffer are also subject to probabilistic eviction, preventing bias toward early elements. The stochastic rounds act as a &lt;strong&gt;progressive forgetting mechanism&lt;/strong&gt; that keeps memory bounded while preserving estimator accuracy.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Implementation
&lt;/h2&gt;

&lt;p&gt;I implemented CVM in Python as the &lt;code&gt;AdaptiveCVMCounter&lt;/code&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AdaptiveCVMCounter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;initial_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initial_size&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max_size&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Set&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_round&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_round&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;if&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_start_new_round&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_start_new_round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_round&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;def&lt;/span&gt; &lt;span class="nf"&gt;estimate_unique_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_round&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key design choices
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Adaptive memory sizing.&lt;/strong&gt; The &lt;code&gt;adjust_memory_size&lt;/code&gt; method monitors error rates. If the error exceeds 10%, the buffer doubles in size (up to &lt;code&gt;max_size&lt;/code&gt;). This gives automatic accuracy tuning without manual configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;adjust_memory_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error_rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;error_rate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory_size&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory_size&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_size&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;Parallel processing.&lt;/strong&gt; The &lt;code&gt;DataAnalyzer&lt;/code&gt; class wraps the counter with &lt;code&gt;ProcessPoolExecutor&lt;/code&gt; for multi-core chunk processing of large files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_data_parallel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_excel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunksize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;ProcessPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;num_workers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process_chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunks&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;Real-time visualization.&lt;/strong&gt; Matplotlib plots exact vs. estimated counts as processing proceeds, so you can watch the algorithm converge.&lt;/p&gt;




&lt;h2&gt;
  
  
  Accuracy Analysis
&lt;/h2&gt;

&lt;p&gt;Here is what the accuracy looks like across different stream sizes and buffer configurations:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stream Size&lt;/th&gt;
&lt;th&gt;Buffer Size&lt;/th&gt;
&lt;th&gt;Rounds&lt;/th&gt;
&lt;th&gt;Estimate&lt;/th&gt;
&lt;th&gt;Exact&lt;/th&gt;
&lt;th&gt;Error&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;100,000&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;99,200&lt;/td&gt;
&lt;td&gt;100,000&lt;/td&gt;
&lt;td&gt;0.80%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;987,500&lt;/td&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;1.25%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000,000&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;9,912,000&lt;/td&gt;
&lt;td&gt;10,000,000&lt;/td&gt;
&lt;td&gt;0.88%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100,000,000&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;98,700,000&lt;/td&gt;
&lt;td&gt;100,000,000&lt;/td&gt;
&lt;td&gt;1.30%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000,000,000&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;993,500,000&lt;/td&gt;
&lt;td&gt;1,000,000,000&lt;/td&gt;
&lt;td&gt;0.65%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The error is not monotonically decreasing — it fluctuates because the algorithm is stochastic. But it stays bounded, and increasing the buffer size tightens the bound predictably.&lt;/p&gt;




&lt;h2&gt;
  
  
  CVM vs HyperLogLog
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;CVM&lt;/th&gt;
&lt;th&gt;HyperLogLog&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Core mechanism&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Stochastic sampling&lt;/td&gt;
&lt;td&gt;Hash-based bit counting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;O(log N) adaptive&lt;/td&gt;
&lt;td&gt;O(log log N) * m registers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Typical accuracy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;98-99%&lt;/td&gt;
&lt;td&gt;97-98% (with 1.5 KB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hash function required&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Merge operation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Non-trivial&lt;/td&gt;
&lt;td&gt;Simple union of registers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maturity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;New (2024)&lt;/td&gt;
&lt;td&gt;Industry standard (2007)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Single-stream estimation&lt;/td&gt;
&lt;td&gt;Distributed aggregation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Implementation complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~30 lines of core logic&lt;/td&gt;
&lt;td&gt;~100 lines with bias correction&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;CVM wins on simplicity and avoids hash function concerns. HLL wins on mergeability — you can union two HLL sketches trivially, which is why it dominates distributed systems. Choose based on your architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  Applications
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Genomics: Counting Unique k-mers
&lt;/h3&gt;

&lt;p&gt;DNA sequencing generates billions of short subsequences (k-mers). Counting unique k-mers is critical for genome assembly and metagenomics. Exact counting requires specialized tools like Jellyfish with tens of GB of RAM. CVM can estimate unique k-mer counts in a streaming pass with kilobytes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Network Security: Distinct IP Tracking
&lt;/h3&gt;

&lt;p&gt;Firewall logs can contain billions of entries per day. Knowing the cardinality of source IPs helps detect DDoS attacks (sudden spike in unique IPs) and port scans (many IPs hitting the same port). CVM provides real-time cardinality estimation without storing IP tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Web Analytics: Unique Visitors
&lt;/h3&gt;

&lt;p&gt;Traditional unique visitor counting requires cookies or fingerprinting — both privacy-invasive. With CVM, you can estimate unique visitors from server logs without storing any user identifiers. Process the log stream, get an estimate, discard the data.&lt;/p&gt;

&lt;h3&gt;
  
  
  IoT: Sensor Deduplication
&lt;/h3&gt;

&lt;p&gt;Thousands of sensors generating readings with potential duplicates. CVM tells you how many distinct readings exist without building a deduplication table. Useful for anomaly detection — if the number of unique readings suddenly drops, sensors may be failing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;Clone the repository:&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 https://github.com/RMANOV/Number-of-Unique-Elements-Prediction.git
&lt;span class="nb"&gt;cd &lt;/span&gt;Number-of-Unique-Elements-Prediction
pip &lt;span class="nb"&gt;install &lt;/span&gt;pandas numpy matplotlib tqdm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quick start with your own data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cvm_counter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AdaptiveCVMCounter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DataAnalyzer&lt;/span&gt;

&lt;span class="c1"&gt;# Simple counting
&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AdaptiveCVMCounter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1_000_000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Estimated unique: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;estimate_unique_count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Full analysis with visualization
&lt;/span&gt;&lt;span class="n"&gt;analyzer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DataAnalyzer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_data.xlsx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;column_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;analyzer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process_data_sequential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;analyzer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visualize_results&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;analyzer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_statistics&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Original CVM paper: Chakraborti, Vinodchandran, Meel (2024)&lt;/li&gt;
&lt;li&gt;Flajolet, Martin — "Probabilistic Counting Algorithms for Data Base Applications" (1985)&lt;/li&gt;
&lt;li&gt;Flajolet, Fusy, Gandouet, Meunier — "HyperLogLog: the analysis of a near-optimal cardinality estimation algorithm" (2007)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;a href="https://github.com/RMANOV/Number-of-Unique-Elements-Prediction" rel="noopener noreferrer"&gt;https://github.com/RMANOV/Number-of-Unique-Elements-Prediction&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>datascience</category>
      <category>algorithms</category>
      <category>opensource</category>
    </item>
    <item>
      <title>You're probably using the wrong fuzzy matching algorithm (and here's how to see why)</title>
      <dc:creator>Ruslan Manov</dc:creator>
      <pubDate>Sun, 01 Feb 2026 15:53:46 +0000</pubDate>
      <link>https://forem.com/john_smith_9ff0ff4cfcffdc/youre-probably-using-the-wrong-fuzzy-matching-algorithm-and-heres-how-to-see-why-4efc</link>
      <guid>https://forem.com/john_smith_9ff0ff4cfcffdc/youre-probably-using-the-wrong-fuzzy-matching-algorithm-and-heres-how-to-see-why-4efc</guid>
      <description>&lt;p&gt;Most developers reach for &lt;code&gt;fuzzywuzzy&lt;/code&gt; or &lt;code&gt;difflib.SequenceMatcher&lt;/code&gt; the moment they need fuzzy string matching. The ratio comes back — 0.73, looks reasonable — and they ship it. But Levenshtein Distance and SequenceMatcher measure &lt;strong&gt;fundamentally different things&lt;/strong&gt;, and picking the wrong one silently corrupts your results.&lt;/p&gt;

&lt;p&gt;I built a terminal app that animates both algorithms step by step so you can &lt;em&gt;see&lt;/em&gt; why they disagree. Here's what I learned.&lt;/p&gt;




&lt;h2&gt;
  
  
  The experiment that changed how I think about fuzzy matching
&lt;/h2&gt;

&lt;p&gt;Compare these two strings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A: "Acme Corp."
B: "ACME Corporation"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Obviously the same company, right? Let's check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;difflib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SequenceMatcher&lt;/span&gt;

&lt;span class="c1"&gt;# SequenceMatcher
&lt;/span&gt;&lt;span class="nc"&gt;SequenceMatcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Acme Corp.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ACME Corporation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# → 0.615 (61.5%)
&lt;/span&gt;
&lt;span class="c1"&gt;# Levenshtein ratio
# distance = 9 edits, max_len = 16
# ratio = 1 - 9/16 = 0.4375 (43.8%)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;SequenceMatcher says 61.5%. Levenshtein says 43.8%.&lt;/strong&gt; That's not a minor disagreement — if your threshold is 50%, one algorithm matches and the other rejects.&lt;/p&gt;

&lt;p&gt;Now try these:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A: "Saturday"
B: "Sunday"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;SequenceMatcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Saturday&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sunday&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# → 0.571 (57.1%)
&lt;/span&gt;
&lt;span class="c1"&gt;# Levenshtein: distance = 3, max_len = 8
# ratio = 1 - 3/8 = 0.625 (62.5%)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;strong&gt;Levenshtein scores higher&lt;/strong&gt;. The algorithms flipped.&lt;/p&gt;

&lt;p&gt;This isn't a bug. They're answering different questions.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Levenshtein actually measures
&lt;/h2&gt;

&lt;p&gt;Vladimir Levenshtein published his distance metric in 1965 at the Keldysh Institute in Moscow. The question is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is the minimum number of single-character operations (insert, delete, substitute) needed to transform string A into string B?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The algorithm builds a dynamic programming matrix. Each cell D[i][j] represents the optimal edit distance between the first i characters of A and the first j characters of B:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        ε    s    i    t    t    i    n    g
   ε    0    1    2    3    4    5    6    7
   k    1    1    2    3    4    5    6    7
   i    2    2    1    2    3    4    5    6
   t    3    3    2    1    2    3    4    5
   t    4    4    3    2    1    2    3    4
   e    5    5    4    3    2    2    3    4
   n    6    6    5    4    3    3    2    3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"kitten" → "sitting" = 3 edits: k→s (substitute), e→i (substitute), +g (insert).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key property:&lt;/strong&gt; Every edit costs exactly 1. Levenshtein doesn't care if you're changing case ("A"→"a"), expanding abbreviations ("Corp."→"Corporation"), or fixing typos ("teh"→"the"). Each character-level change is equally expensive.&lt;/p&gt;

&lt;p&gt;This is why "Acme Corp." vs "ACME Corporation" scores so low — there are 9 individual character changes, and each one costs a full point.&lt;/p&gt;




&lt;h2&gt;
  
  
  What SequenceMatcher actually measures
&lt;/h2&gt;

&lt;p&gt;Python's &lt;code&gt;difflib.SequenceMatcher&lt;/code&gt; implements the Ratcliff/Obershelp "Gestalt Pattern Matching" algorithm (1983). The question is different:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What proportion of both strings consists of contiguous matching blocks?&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ratio = 2 · M / T
M = total characters in matching blocks
T = total characters in both strings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The algorithm works by divide-and-conquer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find the &lt;strong&gt;longest contiguous match&lt;/strong&gt; between the two strings&lt;/li&gt;
&lt;li&gt;Recursively find matches to the &lt;strong&gt;left&lt;/strong&gt; and &lt;strong&gt;right&lt;/strong&gt; of that match&lt;/li&gt;
&lt;li&gt;Sum up all matching characters&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For "The quick brown fox" vs "The quikc brown fax":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 1: Find longest match → " brown f" (8 chars)
Step 2: Left: "The quick" vs "The quikc" → "The qui" (7 chars)
Step 3: Remainders: "ck" vs "kc" → "k" (1 char)
Step 4: Right: "ox" vs "ax" → "x" (1 char)

M = 8 + 7 + 1 + 1 = 17
T = 19 + 19 = 38
Ratio = 34/38 = 89.5%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key property:&lt;/strong&gt; Long contiguous matches are rewarded disproportionately. "Acme Corp." and "ACME Corporation" share long blocks like "me Corp" — so SequenceMatcher scores them higher despite the character-level differences.&lt;/p&gt;




&lt;h2&gt;
  
  
  The comparison table that matters
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Levenshtein&lt;/th&gt;
&lt;th&gt;SequenceMatcher&lt;/th&gt;
&lt;th&gt;Winner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Typo: "programing"→"programming"&lt;/td&gt;
&lt;td&gt;90.9%&lt;/td&gt;
&lt;td&gt;95.2%&lt;/td&gt;
&lt;td&gt;Both good&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Company: "Acme Corp."→"ACME Corporation"&lt;/td&gt;
&lt;td&gt;43.8%&lt;/td&gt;
&lt;td&gt;61.5%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;SM&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Days: "Saturday"→"Sunday"&lt;/td&gt;
&lt;td&gt;62.5%&lt;/td&gt;
&lt;td&gt;57.1%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Lev&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accounting: "Invoice #12345"→"Inv. 12345"&lt;/td&gt;
&lt;td&gt;64.3%&lt;/td&gt;
&lt;td&gt;66.7%&lt;/td&gt;
&lt;td&gt;Close&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cyrillic: "Левенщайн"→"Левенштейн"&lt;/td&gt;
&lt;td&gt;70.0%&lt;/td&gt;
&lt;td&gt;73.7%&lt;/td&gt;
&lt;td&gt;SM slight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typo: "The quick brown fox"→"The quikc brown fax"&lt;/td&gt;
&lt;td&gt;89.5%&lt;/td&gt;
&lt;td&gt;89.5%&lt;/td&gt;
&lt;td&gt;Tie&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Different: "algorithm"→"altruistic"&lt;/td&gt;
&lt;td&gt;40.0%&lt;/td&gt;
&lt;td&gt;50.0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;SM&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Pattern:&lt;/strong&gt; Levenshtein wins when differences are &lt;em&gt;few but scattered&lt;/em&gt; (efficient edit path). SequenceMatcher wins when strings share &lt;em&gt;long common blocks&lt;/em&gt; despite format variations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Seeing is believing — the demo
&lt;/h2&gt;

&lt;p&gt;I built a terminal app that animates both algorithms in real time. Here's what each demo shows:&lt;/p&gt;

&lt;h3&gt;
  
  
  Demo 1: Watch the DP matrix fill
&lt;/h3&gt;

&lt;p&gt;Every cell fills with a flash, colored by operation type. You literally see the wavefront of dynamic programming propagate through the matrix. Then the optimal path traces back in magenta, and the edit operations appear:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;k→s (sub)  i=i (match)  t=t  t=t  e→i (sub)  n=n  +g (ins)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Demo 2: Block discovery in real time
&lt;/h3&gt;

&lt;p&gt;SequenceMatcher's divide-and-conquer becomes visible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gray highlight = current search region&lt;/li&gt;
&lt;li&gt;Green flash = discovered matching block&lt;/li&gt;
&lt;li&gt;Step log shows the recursion order&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can see &lt;em&gt;why&lt;/em&gt; it finds " brown f" before "The qui" — longest first, then recurse.&lt;/p&gt;

&lt;h3&gt;
  
  
  Demo 3: Head-to-head arena
&lt;/h3&gt;

&lt;p&gt;Animated bars grow simultaneously for 5 string pairs. The winner indicator appears per round. You viscerally see where the algorithms diverge.&lt;/p&gt;

&lt;h3&gt;
  
  
  Demo 4: Try your own strings
&lt;/h3&gt;

&lt;p&gt;Type any two strings and get the full analysis: DP matrix (if short enough), both scores, colored diff, matching blocks, edit operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Demo 5: Real-world scenarios
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Typo correction:&lt;/strong&gt; Dictionary lookup with ranked results&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Name dedup:&lt;/strong&gt; Company name clustering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fuzzy VLOOKUP:&lt;/strong&gt; Invoice → catalog matching&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Demo 6: Hybrid scoring
&lt;/h3&gt;

&lt;p&gt;An animated weight sweep from w=0.0 (pure SM) to w=1.0 (pure Lev) with decision guidance.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to use which — the decision framework
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Typo correction, spell checking?
  → Levenshtein (edit model matches typo generation)

Name/entity deduplication?
  → SequenceMatcher (format-tolerant block matching)

Accounting codes, invoice matching?
  → Hybrid w=0.3-0.5 (format varies but typos also matter)

Plagiarism detection, document similarity?
  → SequenceMatcher (long shared passages are the signal)

Search autocomplete?
  → SequenceMatcher + prefix bonus

DNA/protein alignment?
  → Weighted Levenshtein (Needleman-Wunsch with substitution matrices)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/RMANOV/fuzzy-match-visual.git
&lt;span class="nb"&gt;cd &lt;/span&gt;fuzzy-match-visual
python3 demo.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Single file, zero dependencies, Python 3.8+, any modern terminal with truecolor.&lt;/p&gt;

&lt;p&gt;Controls: arrow keys to navigate, Enter to select, 1-6 to jump to demos, S for speed control (0.25×-4×), Ctrl+C returns to menu.&lt;/p&gt;




&lt;h2&gt;
  
  
  The historical footnote
&lt;/h2&gt;

&lt;p&gt;Levenshtein published in 1965, working on error-correcting codes at the Soviet Academy of Sciences. The Wagner-Fischer DP algorithm came in 1974. Ratcliff/Obershelp's Gestalt matching appeared in Dr. Dobb's Journal in 1988. Tim Peters (author of the Zen of Python) wrote Python's &lt;code&gt;difflib.SequenceMatcher&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Three decades of algorithm design, two fundamentally different philosophies of what "similarity" means, and most developers still use them interchangeably.&lt;/p&gt;

&lt;p&gt;Now you can see why that's a mistake.&lt;/p&gt;




&lt;p&gt;GitHub: &lt;a href="https://github.com/RMANOV/fuzzy-match-visual" rel="noopener noreferrer"&gt;https://github.com/RMANOV/fuzzy-match-visual&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>algorithms</category>
      <category>tutorial</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Rust + PyO3 Enhanced Ichimoku Cloud with Hull MA Smoothing</title>
      <dc:creator>Ruslan Manov</dc:creator>
      <pubDate>Sat, 31 Jan 2026 22:13:04 +0000</pubDate>
      <link>https://forem.com/john_smith_9ff0ff4cfcffdc/rust-pyo3-enhanced-ichimoku-cloud-with-hull-ma-smoothing-p9f</link>
      <guid>https://forem.com/john_smith_9ff0ff4cfcffdc/rust-pyo3-enhanced-ichimoku-cloud-with-hull-ma-smoothing-p9f</guid>
      <description>&lt;h1&gt;
  
  
  Why I rewrote 11 trading indicators from Python to Rust (and got bit-exact parity)
&lt;/h1&gt;

&lt;p&gt;A Japanese newspaper reporter spent 30 years perfecting a trading system by hand. I rewrote it in Rust. Here's the full story — the history, the math, and the engineering.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem: Numba's cold start kills live trading
&lt;/h2&gt;

&lt;p&gt;My Python trading system relied on Numba-JIT compiled Ichimoku Cloud calculations. Numba is excellent — until your process restarts.&lt;/p&gt;

&lt;p&gt;Every cold start: &lt;strong&gt;2-5 seconds of JIT compilation per function&lt;/strong&gt;. In a live trading loop that restarts on errors, those seconds mean missed signals. And Numba holds the GIL during execution, blocking every other Python thread.&lt;/p&gt;

&lt;p&gt;I needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero startup latency&lt;/li&gt;
&lt;li&gt;GIL-free execution&lt;/li&gt;
&lt;li&gt;Bit-exact results (no behavioral changes)&lt;/li&gt;
&lt;li&gt;Single-file deployment (no LLVM runtime)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rust + PyO3 checked every box.&lt;/p&gt;




&lt;h2&gt;
  
  
  A brief detour: the man on the mountain
&lt;/h2&gt;

&lt;p&gt;Before we get to code, the history matters — because it explains why Ichimoku is designed the way it is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Goichi Hosoda&lt;/strong&gt; was a Japanese newspaper reporter who began developing a trading system in the &lt;strong&gt;1930s&lt;/strong&gt;. His pen name was &lt;em&gt;Ichimoku Sanjin&lt;/em&gt; (一目山人) — literally "a glance from a man on a mountain." His goal: a single chart that shows support, resistance, trend, momentum, and future projections — all at one glance.&lt;/p&gt;

&lt;p&gt;He enlisted &lt;strong&gt;teams of university students&lt;/strong&gt; to manually compute and backtest the system across decades of Japanese stock and commodity data. No computers. Just pencils, paper, and price tables.&lt;/p&gt;

&lt;p&gt;He published &lt;em&gt;Ichimoku Kinko Hyo&lt;/em&gt; (一目均衡表 — "one-glance equilibrium chart") in &lt;strong&gt;1968&lt;/strong&gt;, after &lt;strong&gt;30 years&lt;/strong&gt; of development. The parameters 9, 26, 52 weren't arbitrary — they mapped to the Japanese trading calendar: 9 trading days (1.5 weeks), 26 days (1 month), 52 days (2 months).&lt;/p&gt;

&lt;p&gt;The system remained almost exclusively Japanese until the internet era. Western traders discovered it in the 2000s and recognized its power: not just an indicator, but a &lt;strong&gt;complete trading framework&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The five classical components
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Japanese&lt;/th&gt;
&lt;th&gt;Formula&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Conversion Line&lt;/td&gt;
&lt;td&gt;Tenkan-sen&lt;/td&gt;
&lt;td&gt;(highest high + lowest low) / 2 over short period&lt;/td&gt;
&lt;td&gt;Short-term equilibrium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Base Line&lt;/td&gt;
&lt;td&gt;Kijun-sen&lt;/td&gt;
&lt;td&gt;Same formula, medium period&lt;/td&gt;
&lt;td&gt;Primary signal line&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leading Span A&lt;/td&gt;
&lt;td&gt;Senkou Span A&lt;/td&gt;
&lt;td&gt;(Tenkan + Kijun) / 2&lt;/td&gt;
&lt;td&gt;Front cloud edge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leading Span B&lt;/td&gt;
&lt;td&gt;Senkou Span B&lt;/td&gt;
&lt;td&gt;Same formula, long period&lt;/td&gt;
&lt;td&gt;Back cloud edge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lagging Span&lt;/td&gt;
&lt;td&gt;Chikou Span&lt;/td&gt;
&lt;td&gt;Close shifted back N periods&lt;/td&gt;
&lt;td&gt;Trend confirmation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The area between Senkou Span A and B forms the &lt;strong&gt;cloud (kumo)&lt;/strong&gt;. Price above cloud = bullish. Below = bearish. Inside = transitioning. Cloud thickness = support/resistance strength.&lt;/p&gt;




&lt;h2&gt;
  
  
  The key innovation: Hull Moving Average
&lt;/h2&gt;

&lt;p&gt;Classic Ichimoku uses &lt;code&gt;(max + min) / 2&lt;/code&gt; — it only reacts when a new extreme appears in the window. This creates stepped, laggy lines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alan Hull&lt;/strong&gt; (2005) solved the fundamental lag-vs-smoothness tradeoff with an algebraic trick:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HMA(n) = WMA(sqrt(n),  2 * WMA(n/2) - WMA(n))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;WMA(n)&lt;/code&gt; (slow) lags by ~n/2 bars&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WMA(n/2)&lt;/code&gt; (fast) lags by ~n/4 bars&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;2 * fast - slow&lt;/code&gt; extrapolates ahead, compensating the slow line's lag&lt;/li&gt;
&lt;li&gt;Final &lt;code&gt;WMA(sqrt(n))&lt;/code&gt; smoothing adds only &lt;code&gt;sqrt(n)/2&lt;/code&gt; bars of lag&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Result: &lt;strong&gt;~50% lag reduction&lt;/strong&gt; with smooth output.&lt;/p&gt;

&lt;p&gt;I applied this to Ichimoku by replacing the midpoint calculation with Hull MA of &lt;code&gt;(high + low) / 2&lt;/code&gt;. Same cloud structure, faster reaction, smoother boundaries.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Rust implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Python layer
    │
    ▼
advanced_ichimoku_cloud (Rust, PyO3)
    ├── hull.rs          → wma, hullma (+ inner functions)
    ├── hull_signals.rs  → trend, pullback, bounce detection
    ├── ichimoku.rs      → classic Ichimoku
    ├── ichimoku_hull.rs → Hull-enhanced Ichimoku
    └── indicators.rs    → ema, atr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key design: inner functions
&lt;/h3&gt;

&lt;p&gt;Every computation exists as a plain &lt;code&gt;fn&lt;/code&gt; (no PyO3 overhead). The &lt;code&gt;#[pyfunction]&lt;/code&gt; wrappers just handle NumPy conversion and delegate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Used by ichimoku_hull.rs without FFI cost&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;hullma_inner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Pure computation — no Python types&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[pyfunction]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;hullma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Python&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PyReadonlyArray1&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Py&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PyArray1&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;slice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prices&lt;/span&gt;&lt;span class="nf"&gt;.as_slice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hullma_inner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nn"&gt;PyArray1&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enables cross-module reuse: &lt;code&gt;ichimoku_hull.rs&lt;/code&gt; calls &lt;code&gt;hull::hullma_inner()&lt;/code&gt; directly, with zero FFI overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Zero-copy I/O
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Input&lt;/strong&gt;: &lt;code&gt;as_slice().unwrap()&lt;/code&gt; reads NumPy arrays directly — no copying, no allocation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output&lt;/strong&gt;: &lt;code&gt;PyArray1::from_vec&lt;/code&gt; allocates once in Rust, transfers ownership to Python&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GIL release
&lt;/h3&gt;

&lt;p&gt;PyO3 releases the GIL during Rust computation by default. Other Python threads (WebSocket handlers, order management) run freely while indicators compute.&lt;/p&gt;




&lt;h2&gt;
  
  
  Proving parity: 25+ assertions at 1e-12 tolerance
&lt;/h2&gt;

&lt;p&gt;The test suite implements every function in pure Python, generates identical random data (seed=42, N=200), and asserts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert_allclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rust_result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;python_result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;atol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1e-12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All 11 functions. All edge cases (NaN propagation, initial positions, backfill behavior). If Rust disagrees with Python by more than 1e-12, the test fails.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;============================================================
  Parity Tests: advanced-ichimoku-cloud
============================================================
  PASS  wma
  PASS  hullma
  PASS  hullma_trend
  PASS  hullma_pullback
  PASS  hullma_bounce
  PASS  ichimoku_line
  PASS  ichimoku_components
  PASS  ichimoku_line_hull
  PASS  ichimoku_components_hull
  PASS  ema
  PASS  atr
============================================================
  ALL 11 FUNCTIONS PASS PARITY TESTS
============================================================
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Before and after
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Python + Numba&lt;/th&gt;
&lt;th&gt;Rust + PyO3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;First-call latency&lt;/td&gt;
&lt;td&gt;2-5s JIT warmup&lt;/td&gt;
&lt;td&gt;Zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GIL&lt;/td&gt;
&lt;td&gt;Held during execution&lt;/td&gt;
&lt;td&gt;Released&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory safety&lt;/td&gt;
&lt;td&gt;Runtime bounds checks&lt;/td&gt;
&lt;td&gt;Compile-time guarantees&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependency weight&lt;/td&gt;
&lt;td&gt;~150 MB (numba + llvmlite)&lt;/td&gt;
&lt;td&gt;~2 MB single .so&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reproducibility&lt;/td&gt;
&lt;td&gt;JIT varies across LLVM versions&lt;/td&gt;
&lt;td&gt;Deterministic binary&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;advanced-ichimoku-cloud
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;advanced_ichimoku_cloud&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ichimoku_components&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;# classic cloud
&lt;/span&gt;    &lt;span class="n"&gt;ichimoku_components_hull&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Hull-enhanced cloud
&lt;/span&gt;    &lt;span class="n"&gt;hullma&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wma&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;atr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;# individual indicators
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="n"&gt;high&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="n"&gt;low&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;

&lt;span class="n"&gt;tenkan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kijun&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;senkou_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;senkou_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ichimoku_components&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;52&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub: &lt;a href="https://github.com/RMANOV/advanced-ichimoku-cloud" rel="noopener noreferrer"&gt;https://github.com/RMANOV/advanced-ichimoku-cloud&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;PyO3's &lt;code&gt;as_slice()&lt;/code&gt; is the killer feature&lt;/strong&gt; — zero-copy NumPy access makes Rust competitive even for small arrays&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inner function pattern&lt;/strong&gt; is essential — without it, cross-module reuse requires double FFI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bit-exact parity testing&lt;/strong&gt; catches subtle issues (NaN propagation order, integer division rounding) that benchmarks miss&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The history of your domain matters&lt;/strong&gt; — understanding why Hosoda chose those parameters helped me design better enhanced variants&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Built with Rust, PyO3 0.27, and a deep appreciation for a journalist who spent 30 years perfecting a chart.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ichimoku</category>
      <category>hullmovingaverage</category>
      <category>technicalanalysis</category>
      <category>tradingindicators</category>
    </item>
    <item>
      <title>Building a regime-switching particle filter in Rust — from Kalman 1960 to rayon-parallelized Monte Carlo</title>
      <dc:creator>Ruslan Manov</dc:creator>
      <pubDate>Sat, 31 Jan 2026 21:41:38 +0000</pubDate>
      <link>https://forem.com/john_smith_9ff0ff4cfcffdc/building-a-regime-switching-particle-filter-in-rust-from-kalman-1960-to-rayon-parallelized-monte-43l7</link>
      <guid>https://forem.com/john_smith_9ff0ff4cfcffdc/building-a-regime-switching-particle-filter-in-rust-from-kalman-1960-to-rayon-parallelized-monte-43l7</guid>
      <description>&lt;h1&gt;
  
  
  Building a regime-switching particle filter in Rust — from Kalman 1960 to rayon-parallelized Monte Carlo
&lt;/h1&gt;

&lt;p&gt;A Hungarian mathematician's 1960 invention, a British statistician's 1993 extension, and a Rust rewrite that eliminates 30 seconds of JIT warmup. Here's the story of state estimation under regime switches.&lt;/p&gt;




&lt;h2&gt;
  
  
  60 years of hidden state estimation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1960 — Rudolf Kálmán&lt;/strong&gt; publishes "A New Approach to Linear Filtering and Prediction Problems." A single paper that would guide Apollo spacecraft to the Moon, enable GPS, and become the backbone of every control system. But it has a limitation: it assumes &lt;strong&gt;linear dynamics&lt;/strong&gt; and &lt;strong&gt;Gaussian noise&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1968 — Extended Kalman Filter (EKF)&lt;/strong&gt; linearizes nonlinear systems via Taylor expansion. Works well enough for slightly nonlinear systems, fails catastrophically for highly nonlinear ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1993 — Gordon, Salmond, and Smith&lt;/strong&gt; publish the &lt;strong&gt;bootstrap particle filter&lt;/strong&gt;. Instead of assuming a distribution shape, they represent the belief as a cloud of weighted samples (particles). Each particle is a hypothesis about the hidden state. Propagate, weight, resample. Repeat. No linearity assumptions. No Gaussian requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Present — Regime-switching extensions&lt;/strong&gt; add a second layer: each particle carries both a continuous state (position, velocity) AND a discrete mode (regime). The system can switch between fundamentally different behaviors — trending, mean-reverting, or chaotic — and the filter tracks which regime is active.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem: 19 functions × 2-5s JIT each = pain
&lt;/h2&gt;

&lt;p&gt;My trading system uses a particle filter to track price regimes in real time. Three modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RANGE&lt;/strong&gt;: Mean-reverting. Price oscillates around equilibrium. &lt;code&gt;velocity' = 0.5 × velocity + noise&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TREND&lt;/strong&gt;: Directional. Price follows order flow imbalance. &lt;code&gt;velocity' = velocity + 0.3 × (gain × imbalance - velocity) × dt + noise&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PANIC&lt;/strong&gt;: High volatility. Random walk with large noise.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Python+Numba implementation had 19 JIT-compiled functions. Every process restart: &lt;strong&gt;30+ seconds&lt;/strong&gt; of JIT compilation before the first estimate. In live trading, 30 blind seconds means missed regime transitions, delayed signals, frozen risk management.&lt;/p&gt;

&lt;p&gt;And Numba holds the GIL. While 500 particles propagate through nonlinear dynamics, the entire Python runtime blocks.&lt;/p&gt;




&lt;h2&gt;
  
  
  19 functions in safe Rust
&lt;/h2&gt;

&lt;p&gt;I rewrote everything in Rust with PyO3 bindings. Six categories:&lt;/p&gt;

&lt;h3&gt;
  
  
  Core particle filter
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;predict_particles    → Rayon-parallelized regime-specific propagation
update_weights       → Bayesian weight update via Gaussian likelihood
transition_regimes   → Markov chain mode switching (3×3 matrix)
systematic_resample  → O(N) two-pointer resampling
effective_sample_size → Degeneracy diagnostic (ESS = 1/Σwᵢ²)
estimate             → Weighted mean + regime probabilities
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Kalman smoothing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kalman_update              → 2D level/slope tracker
slope_confidence_interval  → 95% CI for slope
is_slope_significant       → Directional significance test
kalman_slope_acceleration  → Second derivative for early trend entry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Signal processing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;calculate_vwap_bands    → Volume-weighted price with σ-bands
calculate_momentum_score → Normalized momentum in [-1, +1]
rolling_kurtosis        → Fat-tail detection (excess kurtosis)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Statistical tests
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hurst_exponent          → R/S analysis: trending vs mean-reverting
cusum_test              → Page's CUSUM: structural break detection
volatility_compression  → Range squeeze (short/long vol ratio)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Extended
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;particle_price_variance    → Weighted variance of particle cloud
ess_and_uncertainty_margin → Combined ESS + regime dominance
adaptive_vwap_sigma        → Kurtosis-adapted VWAP band width
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The math that matters
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Particle propagation (the regime-specific part)
&lt;/h3&gt;

&lt;p&gt;Each particle &lt;code&gt;i&lt;/code&gt; carries state &lt;code&gt;[xᵢ, vᵢ]&lt;/code&gt; (log-price, velocity) and regime &lt;code&gt;rᵢ ∈ {0, 1, 2}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RANGE (r=0):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;xᵢ' = xᵢ + vᵢ·dt + σ_pos[0]·√dt·εₓ
vᵢ' = 0.5·vᵢ + σ_vel[0]·√dt·εᵥ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;0.5·vᵢ&lt;/code&gt; term pulls velocity toward zero — mean reversion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TREND (r=1):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;xᵢ' = xᵢ + vᵢ·dt + σ_pos[1]·√dt·εₓ
vᵢ' = vᵢ + 0.3·(G·imbalance - vᵢ)·dt + σ_vel[1]·√dt·εᵥ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Velocity tracks &lt;code&gt;G × imbalance&lt;/code&gt; — the order flow signal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PANIC (r=2):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;xᵢ' = xᵢ + vᵢ·dt + σ_pos[2]·√dt·εₓ
vᵢ' = vᵢ + σ_vel[2]·√dt·εᵥ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pure random walk with high noise.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bayesian weight update
&lt;/h3&gt;

&lt;p&gt;After observing the actual price &lt;code&gt;z&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wᵢ' = wᵢ × exp(-0.5·(z - xᵢ)²/σ²_price[rᵢ]) × exp(-0.5·(vᵢ - G·imb)²/σ²_vel[rᵢ])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Particles close to the observation get high weight. Particles far away get low weight. Normalize. Resample when weights degenerate (ESS &amp;lt; N/2).&lt;/p&gt;

&lt;h3&gt;
  
  
  Why log-price space?
&lt;/h3&gt;

&lt;p&gt;All operations use &lt;code&gt;log(price)&lt;/code&gt;. This makes the filter &lt;strong&gt;scale-invariant&lt;/strong&gt; — the same parameters and noise levels work identically for a $0.50 penny stock and a $50,000 Bitcoin. Multiplicative price noise becomes additive in log space.&lt;/p&gt;




&lt;h2&gt;
  
  
  Engineering decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Rayon only where it matters
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;predict_particles&lt;/code&gt; is the only parallelized function. It's O(N) with substantial per-particle computation (regime branching, noise injection). The other 18 functions are either memory-bound (weight updates, resampling) or operate on small arrays (Kalman 2×2 matrices, signal windows).&lt;/p&gt;

&lt;p&gt;Adding rayon to memory-bound functions would increase latency from thread pool overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  No internal randomness
&lt;/h3&gt;

&lt;p&gt;The library takes pre-generated random arrays as input. This gives the caller:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full reproducibility (same seed = same output)&lt;/li&gt;
&lt;li&gt;Choice of RNG (numpy default, PCG64, whatever)&lt;/li&gt;
&lt;li&gt;Ability to inject structured noise for testing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Numerical stability
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Weight normalization: &lt;code&gt;+1e-300&lt;/code&gt; guard prevents underflow to zero&lt;/li&gt;
&lt;li&gt;ESS denominator: &lt;code&gt;+1e-12&lt;/code&gt; prevents division by zero&lt;/li&gt;
&lt;li&gt;Kalman covariance: symmetrized after every predict and update step&lt;/li&gt;
&lt;li&gt;dt guard: &lt;code&gt;max(dt, 1e-8).sqrt()&lt;/code&gt; prevents noise explosion at tiny timesteps&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Hurst exponent — my favorite function
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Hurst exponent&lt;/strong&gt; tells you if a price series is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;H &amp;gt; 0.5&lt;/strong&gt;: Trending (persistent — ups followed by ups)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;H = 0.5&lt;/strong&gt;: Random walk (no memory)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;H &amp;lt; 0.5&lt;/strong&gt;: Mean-reverting (anti-persistent — ups followed by downs)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Computed via &lt;strong&gt;R/S (Rescaled Range) analysis&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;For each window size &lt;code&gt;n&lt;/code&gt; in &lt;code&gt;[min_window, max_window]&lt;/code&gt;:

&lt;ul&gt;
&lt;li&gt;Split series into blocks of size &lt;code&gt;n&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;For each block: compute range of cumulative deviations from mean, divide by standard deviation&lt;/li&gt;
&lt;li&gt;Average the R/S values&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Fit &lt;code&gt;log(R/S) = H × log(n) + c&lt;/code&gt; via least squares&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;H&lt;/code&gt; is the slope&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This single number tells you whether to use trend-following or mean-reversion strategies. Combined with the particle filter's regime probabilities, you get a multi-scale view of market behavior.&lt;/p&gt;




&lt;h2&gt;
  
  
  Before and after
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Python + Numba&lt;/th&gt;
&lt;th&gt;Rust + PyO3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cold start (19 functions)&lt;/td&gt;
&lt;td&gt;30-90s JIT warmup&lt;/td&gt;
&lt;td&gt;Zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GIL&lt;/td&gt;
&lt;td&gt;Held during all compute&lt;/td&gt;
&lt;td&gt;Released&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parallelism&lt;/td&gt;
&lt;td&gt;prange (limited)&lt;/td&gt;
&lt;td&gt;Rayon work-stealing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory safety&lt;/td&gt;
&lt;td&gt;Runtime bounds checks&lt;/td&gt;
&lt;td&gt;Compile-time guarantees&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependency weight&lt;/td&gt;
&lt;td&gt;~150 MB&lt;/td&gt;
&lt;td&gt;~2 MB single .so&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reproducibility&lt;/td&gt;
&lt;td&gt;JIT varies by LLVM version&lt;/td&gt;
&lt;td&gt;Deterministic binary&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;particle-filter-rs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;particle_filter_rs&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;predict_particles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;update_weights&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;systematic_resample&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;effective_sample_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;estimate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transition_regimes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;kalman_update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hurst_exponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cusum_test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize 500 particles in log-price space
&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;
&lt;span class="n"&gt;particles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column_stack&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;full&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;  &lt;span class="c1"&gt;# log-price
&lt;/span&gt;    &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zeros&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;                 &lt;span class="c1"&gt;# velocity
&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;regimes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zeros&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;weights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;full&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# One filter step
&lt;/span&gt;&lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_rng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;particles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;predict_particles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;particles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;regimes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;0.001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.002&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.005&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;  &lt;span class="c1"&gt;# process noise per regime
&lt;/span&gt;    &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="n"&gt;imbalance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vel_gain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;random_pos&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;standard_normal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;random_vel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;standard_normal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub: &lt;a href="https://github.com/RMANOV/particle-filter-rs" rel="noopener noreferrer"&gt;https://github.com/RMANOV/particle-filter-rs&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rayon's overhead is real&lt;/strong&gt; — for memory-bound functions, single-threaded Rust beats rayon-parallelized Rust&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log-space is non-negotiable&lt;/strong&gt; — a particle filter in linear price space needs different noise parameters for every asset. Log-space is universal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic filters are testable filters&lt;/strong&gt; — external RNG makes every test reproducible, every bug reproducible&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Kalman complement matters&lt;/strong&gt; — particle filters alone give noisy estimates. A parallel Kalman provides smooth baseline + confidence intervals. Best of both worlds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;60 years of math still works&lt;/strong&gt; — Kálmán's 1960 insight (recursive Bayesian estimation) is alive in every function in this library&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Built with Rust, PyO3 0.27, rayon 1.10, and respect for 60 years of state estimation research.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>python</category>
      <category>machinelearning</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
