<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ruslan Manov</title>
    <description>The latest articles on DEV Community by Ruslan Manov (@john_smith_9ff0ff4cfcffdc).</description>
    <link>https://dev.to/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>DEV Community: Ruslan Manov</title>
      <link>https://dev.to/john_smith_9ff0ff4cfcffdc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/john_smith_9ff0ff4cfcffdc"/>
    <language>en</language>
    <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://dev.to/john_smith_9ff0ff4cfcffdc/i-turned-a-webcam-into-an-ambient-light-sensor-265l</link>
      <guid>https://dev.to/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://dev.to/john_smith_9ff0ff4cfcffdc/finding-primes-of-the-form-p2-4q2-from-oxford-mathematics-to-python-multiprocessing-1ci0</link>
      <guid>https://dev.to/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://dev.to/john_smith_9ff0ff4cfcffdc/how-to-count-a-billion-unique-items-with-almost-no-memory-735</link>
      <guid>https://dev.to/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://dev.to/john_smith_9ff0ff4cfcffdc/youre-probably-using-the-wrong-fuzzy-matching-algorithm-and-heres-how-to-see-why-4efc</link>
      <guid>https://dev.to/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://dev.to/john_smith_9ff0ff4cfcffdc/rust-pyo3-enhanced-ichimoku-cloud-with-hull-ma-smoothing-p9f</link>
      <guid>https://dev.to/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://dev.to/john_smith_9ff0ff4cfcffdc/building-a-regime-switching-particle-filter-in-rust-from-kalman-1960-to-rayon-parallelized-monte-43l7</link>
      <guid>https://dev.to/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>
