<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

 <title>Thomas Countz</title>
 <link href="https://thomascountz.com/atom.xml" rel="self"/>
 <link href="https://thomascountz.com/"/>
 <updated>2026-04-29T21:42:39+00:00</updated>
 <id>https://thomascountz.com/</id>
 <author>
   <name>thomascountz</name>
   <email>thomascountz@gmail.com</email>
 </author>

 
 <entry>
   <title>Job Scheduling with at(1)</title>
   <link href="https://thomascountz.com/2026/02/22/one-off-scheduling-with-at"/>
   <updated>2026-02-22T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2026/02/22/one-off-scheduling-with-at</id>
   <summary type="html">at(1) is a one-off job scheduler that&apos;s been hiding in plain sight on Unix systems. Yes, it knows when teatime is.</summary>
   <content type="html">&lt;p&gt;I recently hacked together an ESP-01 and a cheap solar garden lantern&lt;sup id=&quot;fnref:lantern&quot;&gt;&lt;a href=&quot;#fn:lantern&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; so that I can have warm flickering candlelight whenever the mood strikes. For example, by using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;at(1)&lt;/code&gt;, I can make teatime&lt;sup id=&quot;fnref:teatime&quot;&gt;&lt;a href=&quot;#fn:teatime&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; just a little more magical:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;curl &apos;http://ember.local/on&apos;&quot;&lt;/span&gt; | at teatime
job 42 at Sun Feb 22 16:00:00 2026
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://man7.org/linux/man-pages/man1/at.1p.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;at(1)&lt;/code&gt;&lt;/a&gt; is a Unix utility for scheduling one-off commands. In the example above, a request to turn on the lantern will be sent at 16:00 local time.&lt;/p&gt;

&lt;p&gt;You can verify it’s queued with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;atq&lt;/code&gt;, and view the job details with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;at -c [job]&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;atq
42	Sun Feb 22 16:00:00 2026
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;at &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; 42
&lt;span class=&quot;c&quot;&gt;#!/bin/sh&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# atrun uid=502 gid=20&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;
curl &lt;span class=&quot;s1&quot;&gt;&apos;http://ember.local/on&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Whereas &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cron(8)&lt;/code&gt; is used for scheduling recurring jobs, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;at&lt;/code&gt; is used for scheduling &lt;em&gt;one-off&lt;/em&gt; tasks. For example, here are some ways I’ve found it useful:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reminding myself to take a break&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;at now + 1 minute &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;
osascript -e &quot;display dialog &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;Stop what you&apos;re doing!&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt; with title &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;Take a break&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Posting fresh data before a team sync&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;curl https://api.example.com/stats | jq . | ~/pipe-to-slack.sh&quot;&lt;/span&gt; | at 9:15AM tomorrow
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Stopping that debug container I’m probably going to forget about&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;docker stop my-debug&quot;&lt;/span&gt; | at 1700 friday
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Although I haven’t tried it myself, you can even implement “recurring” jobs by recursively rescheduling the next run at the end of the current job. That said, if such a job fails, the chain breaks silently; there is no built-in retry or alerting like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cron&lt;/code&gt; provides.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; ~/daily.sh
&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Do some work here...&lt;/span&gt;
at &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; ~/daily.sh 5pm tomorrow

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;at &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; ~/daily.sh 5pm
job 43 at Mon Feb 23 17:00:00 2026
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;This example uses the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-f&lt;/code&gt; flag to tell &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;at&lt;/code&gt; to read the job from a file.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After a job is executed, its output will be captured and sent to you via local &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sendmail(8)&lt;/code&gt;&lt;sup id=&quot;fnref:force-mail&quot;&gt;&lt;a href=&quot;#fn:force-mail&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;. On macOS, mail lands in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/mail/$USER&lt;/code&gt; by default, and you can read it with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mail(1)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Keep in mind that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;at&lt;/code&gt; snapshots your exported environment (working directory, env vars, umask) from wherever you schedule a job, but it &lt;strong&gt;does not&lt;/strong&gt; capture your shell profile (e.g. anything from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.bashrc&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.zshrc&lt;/code&gt;, etc.). Therefore, it’s common practice to use absolute paths and avoid shell-specific features.&lt;/p&gt;

&lt;h2 id=&quot;quick-start&quot;&gt;Quick Start&lt;/h2&gt;

&lt;p&gt;To get started using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;at&lt;/code&gt; on macOS, you’ll need to manually enable the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;atrun(8)&lt;/code&gt; daemon using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launchctl(1)&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;launchctl load &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /System/Library/LaunchDaemons/com.apple.atrun.plist
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Command reference:&lt;/strong&gt;&lt;/p&gt;

&lt;dl&gt;
  &lt;dt&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;at &amp;lt;time&amp;gt;&lt;/code&gt;&lt;/dt&gt;
  &lt;dd&gt;schedules a job&lt;/dd&gt;
  &lt;dt&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;atq&lt;/code&gt;&lt;/dt&gt;
  &lt;dd&gt;lists pending jobs&lt;/dd&gt;
  &lt;dt&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;at -c &amp;lt;id&amp;gt;&lt;/code&gt;&lt;/dt&gt;
  &lt;dd&gt;shows what a job will run&lt;/dd&gt;
  &lt;dt&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;atrm &amp;lt;id&amp;gt;&lt;/code&gt;&lt;/dt&gt;
  &lt;dd&gt;cancels a job&lt;/dd&gt;
  &lt;dt&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;at -f script.sh 3pm&lt;/code&gt;&lt;/dt&gt;
  &lt;dd&gt;reads from a file instead of stdin&lt;/dd&gt;
  &lt;dt&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;at -m&lt;/code&gt;&lt;/dt&gt;
  &lt;dd&gt;sends mail even if there’s no output&lt;/dd&gt;
&lt;/dl&gt;
&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:lantern&quot;&gt;
      &lt;p&gt;Like this: &lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Lampioncino_solare.jpg&quot;&gt;Lampioncino_solare.jpg&lt;/a&gt;, &lt;a href=&quot;https://creativecommons.org/licenses/by-sa/4.0&quot;&gt;Antonia Mette, CC BY-SA 4.0&lt;/a&gt;, via Wikimedia Commons &lt;a href=&quot;#fnref:lantern&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:teatime&quot;&gt;
      &lt;p&gt;“…the following keywords may be specified: midnight, noon, or teatime (4pm)…” &lt;a href=&quot;https://man7.org/linux/man-pages/man1/at.1p.html&quot;&gt;at(1) - POSIX specification&lt;/a&gt; &lt;a href=&quot;#fnref:teatime&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:force-mail&quot;&gt;
      &lt;p&gt;By default, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;at&lt;/code&gt; only sends mail if a job produces output. If you want to receive mail even when there’s no output, you can use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-m&lt;/code&gt; flag. &lt;a href=&quot;#fnref:force-mail&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Good Citizen Syndrome</title>
   <link href="https://thomascountz.com/2026/01/26/good-citizen-syndrome"/>
   <updated>2026-01-26T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2026/01/26/good-citizen-syndrome</id>
   <summary type="html">When skilled engineers constantly fill gaps in process and standards, it can mask weak reliability strategy.</summary>
   <content type="html">&lt;p&gt;&lt;strong&gt;Good Citizen Syndrome is what happens when engineering leaders—often unwittingly—confuse good reliable engineers with good reliability engineering.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Skilled engineers who care about reliability constantly step in to fill gaps in process and standards. They’re &lt;em&gt;Good Citizens&lt;/em&gt;. While this &lt;em&gt;can&lt;/em&gt; be a sign of a mature organization, latent conditions persist when leadership mistakes this vigilance for sustainable systemic health.&lt;/p&gt;

&lt;p&gt;In his book about organizational culture, Karl Weick calls reliability a “dynamic non-event;” a phrase which underscores the false sense of safety good reliability can cause.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Reliability is dynamic in the sense that it is an ongoing condition in which problems are momentarily under control due to compensating changes in components… Operators see nothing and seeing nothing, presume that nothing is happening.&lt;sup id=&quot;fnref:weick&quot;&gt;&lt;a href=&quot;#fn:weick&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When reliability engineering&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; is done well, nothing bad happens. When nothing bad happens, it looks like nothing is being done at all. &lt;strong&gt;But it takes a lot of work to make nothing happen!&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:weick&quot;&gt;
      &lt;p&gt;Weick, K. E. (1987). Organizational culture as a source of high reliability. &lt;em&gt;California Management Review&lt;/em&gt;, 29(2), 112-127. &lt;a href=&quot;#fnref:weick&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;Replace “reliability engineering” with any practice you care about, e.g. security, quality assurance, compliance, maintenance, etc. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Event Streaming vs Background Jobs</title>
   <link href="https://thomascountz.com/2025/07/21/streaming-vs-background-jobs"/>
   <updated>2025-07-21T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2025/07/21/streaming-vs-background-jobs</id>
   <summary type="html">At their core, event streaming platforms and background job systems both solve problems related to scaling and asynchronous processing. But we can think of one as managing a log of events, and the other as managing a to-do list.</summary>
   <content type="html">&lt;p&gt;I was recently asked the question:&lt;/p&gt;

&lt;p&gt;“Can we just use ActiveJob instead of Kafka?”&lt;/p&gt;

&lt;p&gt;I think there may have been a misunderstanding of the differences between event streaming platforms (like Apache Kafka) and background job systems (like ActiveJob).&lt;/p&gt;

&lt;p&gt;At their core, they both solve problems related to scaling and asynchronous processing. But, we can think of one as managing a &lt;strong&gt;log of events&lt;/strong&gt;, and the other as managing a &lt;strong&gt;to-do list&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;log-of-events&quot;&gt;Log of Events&lt;/h2&gt;

&lt;p&gt;Streaming platforms like Kafka are designed for decoupling components which all need to be informed about specific events and when they happened, using a &lt;strong&gt;publish-subscribe&lt;/strong&gt; model.&lt;/p&gt;

&lt;p&gt;To do this, &lt;strong&gt;message brokers&lt;/strong&gt; capture &lt;strong&gt;events&lt;/strong&gt; from various sources called &lt;strong&gt;producers&lt;/strong&gt; (or &lt;strong&gt;publishers&lt;/strong&gt;), and broadcast them as a continuous &lt;strong&gt;stream&lt;/strong&gt; (or &lt;strong&gt;log&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;These events can then be read by one or more &lt;strong&gt;consumers&lt;/strong&gt; (or &lt;strong&gt;subscribers&lt;/strong&gt;), each using the data for its own purpose.&lt;/p&gt;

&lt;p&gt;This is useful for scenarios where different consumers need to react differently to the same event. For example, one consumer might record the event data in a data lake, another might update a search index, and yet another might trigger an alert, all in response to the same event.&lt;/p&gt;

&lt;p&gt;Crucially, the log preserves the &lt;strong&gt;order&lt;/strong&gt; of events. This ensures that if a record is updated and then deleted, consumers process those changes in that exact sequence.&lt;/p&gt;

&lt;p&gt;Each consumer can read the log independently of each other. The log of events is often &lt;strong&gt;durable&lt;/strong&gt;, meaning they’re stored for a given period of time, allowing some consumers to react in near real time, while others choose to read at their leisure.&lt;/p&gt;

&lt;h2 id=&quot;to-do-list&quot;&gt;To-Do List&lt;/h2&gt;

&lt;p&gt;Background job systems, like Resque or SolidQueue, are built for &lt;strong&gt;offloading work&lt;/strong&gt; from an application’s main process, using a &lt;strong&gt;queue-based&lt;/strong&gt; model.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;job&lt;/strong&gt; acts like an instruction for work to be done, most often as soon as possible. It’s placed on a &lt;strong&gt;queue&lt;/strong&gt; by a job &lt;strong&gt;orchestrator&lt;/strong&gt; (or &lt;strong&gt;scheduler&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workers&lt;/strong&gt; wait on the queue for a job to do. When the worker finds one, it &lt;strong&gt;consumes&lt;/strong&gt; and processes it such that no other worker picks it up.&lt;/p&gt;

&lt;p&gt;This is useful for tasks that are too resource-intensive to run in the main application process, but that you’d otherwise like to happen sequentially.&lt;/p&gt;

&lt;p&gt;For example, when a user uploads a new avatar, you may want to resize the image and store it in a CDN without blocking the application server from responding to new requests.&lt;/p&gt;

&lt;p&gt;Workers are interchangeable, which allows for &lt;strong&gt;load balancing&lt;/strong&gt; between them and &lt;strong&gt;horizontally scaling&lt;/strong&gt; additional capacity when needed. Background jobs can also be &lt;strong&gt;durable&lt;/strong&gt;, allowing them to be retried if a worker crashes or a job fails.&lt;/p&gt;

&lt;h2 id=&quot;the-confusion&quot;&gt;The Confusion&lt;/h2&gt;

&lt;p&gt;The overlap between these two systems is that they both deal with &lt;strong&gt;asynchronous processing&lt;/strong&gt; and &lt;strong&gt;scaling&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;However, while Kafka can mimic a job queue using &lt;strong&gt;consumer groups&lt;/strong&gt;, relying on it solely for background jobs means you incur the operational complexity of a streaming platform without leveraging its primary advantages.&lt;/p&gt;

&lt;p&gt;In particular, the benefit of allowing multiple consumers to react differently to the same event is lost when you design for only one consumer to process the event like a background job worker would.&lt;/p&gt;

&lt;p&gt;Additionally, platforms like Kafka often come with the cost of increased complexity. Although you &lt;em&gt;can&lt;/em&gt; use Kafka as a background job system, you likely do not have a use case that justifies the overhead.&lt;/p&gt;

&lt;h2 id=&quot;the-conclusion&quot;&gt;The Conclusion&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Event Streams&lt;/strong&gt; are about &lt;strong&gt;history&lt;/strong&gt;. They broadcast facts (events) in a specific order to multiple services that may use that information in entirely different ways.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Background Jobs&lt;/strong&gt; are about &lt;strong&gt;tasks&lt;/strong&gt;. They offload specific units of work (jobs) to be executed once by any available worker.&lt;/p&gt;

&lt;p&gt;If you need to tell the whole system that “something happened,” use a stream. If you need a specific worker to “do something now,” use a job queue.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Chrome&apos;s SSL Bypass Cheatcode</title>
   <link href="https://thomascountz.com/2025/07/17/chromes-ssl-bypass-cheatcode"/>
   <updated>2025-07-17T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2025/07/17/chromes-ssl-bypass-cheatcode</id>
   <summary type="html">Did you know that Chrome has a secret cheatcode that lets you bypass SSL errors? It&apos;s called `thisisunsafe`, and I couldn&apos;t help but pull on the thread to uncover its history.</summary>
   <content type="html">&lt;h2 id=&quot;this-is-unsafe&quot;&gt;This is Unsafe&lt;/h2&gt;

&lt;p&gt;If you type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;thisisunsafe&lt;/code&gt; on a Chrome SSL error page, Chrome will bypass the error and load the page for you.&lt;/p&gt;

&lt;video src=&quot;/assets/images/thisisunsafe.mp4&quot; controls=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;&lt;/video&gt;

&lt;p&gt;Try it yourself here: &lt;a href=&quot;https://expired.badssl.com/&quot;&gt;https://expired.badssl.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There’s no textbox to type into, just type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;thisisunsafe&lt;/code&gt; blindly with the page in focus.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;TIP: To revert the bypass, click the “Not Secure” button in the URL bar and then click “Turn on warnings.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;history-of-the-bypass-code&quot;&gt;History of the Bypass Code&lt;/h2&gt;

&lt;p&gt;My first reaction upon discovering &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;thisisunsafe&lt;/code&gt; was one of delight! I was flooded with memories of trying &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;↑ ↑ ↓ ↓ ← → ← → B A&lt;/code&gt; on every Playstation game I had as a kid (see &lt;em&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Konami_Code&quot;&gt;Konami Code&lt;/a&gt;&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;By why does Chrome have a cheatcode?&lt;/p&gt;

&lt;p&gt;Like Tomb Raider searching for ancient artifacts, my sense of wonder led me to dig through the history of Chromium to try and find out how this code came to be. I was curious to know if it was a joke, a mistake, or something else entirely.&lt;/p&gt;

&lt;p&gt;Here’s what I found.&lt;/p&gt;

&lt;h3 id=&quot;danger-2014&quot;&gt;Danger (2014)&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://codereview.chromium.org/480393002/patch/60001/70017&quot;&gt;https://codereview.chromium.org/480393002/patch/60001/70017&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;/*
 * This allows errors to be skippped [sic] by typing &quot;danger&quot; into the page.
 * @param {string} e The key that was just pressed.
 */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handleKeypress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;BYPASS_SEQUENCE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;danger&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;BYPASS_SEQUENCE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;charCodeAt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;keyPressState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;keyCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;keyPressState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;keyPressState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;BYPASS_SEQUENCE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;sendCommand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;CMD_PROCEED&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;keyPressState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;keyPressState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The bypass code was first introduced in 2014. Originally set to “&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;danger&lt;/code&gt;,” it was newly created part of a larger piece of work related to DRY-ing up duplication in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chrome/browser/resources/safe_browsing/&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chrome/browser/resources/ssl/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;See: &lt;a href=&quot;https://issues.chromium.org/issues/41125304&quot;&gt;Aug 11, 2014 18:03UTC - Chromium Issue #41125304&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;Unfortunately, the reason it was added doesn’t appear to have been documented. My guess is that developers needed a convenient way to bypass SSL errors during the rise of HTTPS adoption and enforcement.&lt;/p&gt;

&lt;h3 id=&quot;bad-idea-2015&quot;&gt;Bad Idea (2015)&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://codereview.chromium.org/1416273004/patch/1/10001&quot;&gt;https://codereview.chromium.org/1416273004/patch/1/10001&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gd&quot;&gt;--- a/components/security_interstitials/core/browser/resources/interstitial_v2.js
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+++ b/components/security_interstitials/core/browser/resources/interstitial_v2.js
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@@ -40,7 +40,7 @@&lt;/span&gt; function sendCommand(cmd) {
  * @param {string} e The key that was just pressed.
  */
 function handleKeypress(e) {
&lt;span class=&quot;gd&quot;&gt;-  var BYPASS_SEQUENCE = &apos;danger&apos;;
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+  var BYPASS_SEQUENCE = &apos;badidea&apos;;
&lt;/span&gt;   if (BYPASS_SEQUENCE.charCodeAt(keyPressState) == e.keyCode) {
     keyPressState++;
     if (keyPressState == BYPASS_SEQUENCE.length) {
&lt;span class=&quot;err&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A year later, in 2015, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BYPASS_SEQUENCE&lt;/code&gt; was changed to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;badidea&lt;/code&gt;. Like before, there’s little evidence left to understand the intention behind the changes.&lt;/p&gt;

&lt;p&gt;However, as we’ll see, later changes were made due to concerns around the bypass code’s popularity and misuse, so it seems likely that the change to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;badidea&lt;/code&gt; was made for similar reasons.&lt;/p&gt;

&lt;p&gt;Interestingly, in 2014, Google published a paper entitled &lt;em&gt;Experimenting At Scale With Google Chrome’s SSL Warning&lt;/em&gt;, where authors experimented with ways to reduce the number of users who bypassed SSL warnings via the UI.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Our goal in this work is to decrease the number of users who click through the Google Chrome SSL warning… We investigate several factors that could be responsible: the use of imagery, extra steps before the user can proceed, and style choices.&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/41927.pdf&quot;&gt;Felt, Adrienne Porter, et al. “Experimenting At Scale With Google Chrome’s SSL Warning.” (2014).&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;this-is-not-safe-2018&quot;&gt;This is not Safe (2018)&lt;/h3&gt;

&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gd&quot;&gt;--- a/components/security_interstitials/core/browser/resources/interstitial_large.js
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+++ b/components/security_interstitials/core/browser/resources/interstitial_large.js
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@@ -13,7 +13,7 @@&lt;/span&gt;
  * @param {string} e The key that was just pressed.
  */
 function handleKeypress(e) {
&lt;span class=&quot;gd&quot;&gt;-  var BYPASS_SEQUENCE = &apos;badidea&apos;;
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+  var BYPASS_SEQUENCE = &apos;thisisnotsafe&apos;;
&lt;/span&gt;   if (BYPASS_SEQUENCE.charCodeAt(keyPressState) == e.keyCode) {
     keyPressState++;
     if (keyPressState == BYPASS_SEQUENCE.length) {
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On January 03, 2018, &lt;a href=&quot;https://chromium-review.googlesource.com/c/chromium/src/+/843085/4/components/security_interstitials/core/browser/resources/interstitial_large.js&quot;&gt;the bypass code was updated again&lt;/a&gt;, this time to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;thisisnotsafe&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Unlike before, the code was changed explicitly due to concern around the growing popularity of being able to bypass SSL warnings in Chrome using the bypass code.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The security interstitial bypass keyword hasn’t changed in two years and awareness of the bypass has been increased in blogs and social media. Rotate the keyword to help prevent misuse.&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;https://chromium-review.googlesource.com/c/chromium/src/+/843085&quot;&gt;Jan 03, 2018 03:03UTC - Chromium Issue #843085&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Based on the source code, the bypass code was only intended for testing and not for general use. The click-through UI or Chrome flags were abled to be monitored for patterns, like we saw in the 2014 paper. Use of the bypass code, however, doesn’t seem to have been tracked.&lt;/p&gt;

&lt;h3 id=&quot;dghpc2lzdw5zywzl-2018---present&quot;&gt;dGhpc2lzdW5zYWZl (2018 - Present)&lt;/h3&gt;

&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gd&quot;&gt;--- a/components/security_interstitials/core/browser/resources/interstitial_large.js
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+++ b/components/security_interstitials/core/browser/resources/interstitial_large.js
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@@ -13,7 +13,10 @@&lt;/span&gt;
  * @param {string} e The key that was just pressed.
  */
 function handleKeypress(e) {
&lt;span class=&quot;gd&quot;&gt;-  var BYPASS_SEQUENCE = &apos;thisisnotsafe&apos;;
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+  // HTTPS errors are serious and should not be ignored. For testing purposes,
+  // other approaches are both safer and have fewer side-effects.
+  // See https://goo.gl/ZcZixP for more details.
+  var BYPASS_SEQUENCE = window.atob(&apos;dGhpc2lzdW5zYWZl&apos;);
&lt;/span&gt;   if (BYPASS_SEQUENCE.charCodeAt(keyPressState) == e.keyCode) {
     keyPressState++;
     if (keyPressState == BYPASS_SEQUENCE.length) {
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Just a few days later, on January 10, 2018, &lt;a href=&quot;https://chromium-review.googlesource.com/c/chromium/src/+/860418&quot;&gt;the bypass code was changed once again&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;thisisnotsafe&lt;/code&gt; was changed to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dGhpc2lzdW5zYWZl&lt;/code&gt;, in what I believe was an attempt at obfuscation.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo &lt;/span&gt;dGhpc2lzdW5zYWZl | &lt;span class=&quot;nb&quot;&gt;base64&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt;
thisisunsafe
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This made no difference in terms of functionality; the code was simply base64 encoded, and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;window.atob&lt;/code&gt; function was used to decode it back to its original form.&lt;/p&gt;

&lt;p&gt;Along with this code change, the Chromium developers added a comment linking to a public document titled: &lt;a href=&quot;https://goo.gl/ZcZixP&quot;&gt;Deprecating Powerful Features on Insecure Origins&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Though the document made no mention of the bypass code itself, it included instructions for how to bypass SSL errors during development and testing:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;You can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chrome://flags/#unsafely-treat-insecure-origin-as-secure&lt;/code&gt; to run Chrome, or use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--unsafely-treat-insecure-origin-as-secure=&quot;http://example.com&quot;&lt;/code&gt; flag (replacing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;example.com&quot;&lt;/code&gt; with the origin you actually want to test), which will treat that origin as secure for this session.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I presume this is what was meant by the “…other approaches are both safer and have fewer side-effects,” comment in the code snippet above.&lt;/p&gt;

&lt;h2 id=&quot;is-this-unsafe&quot;&gt;Is this Unsafe?&lt;/h2&gt;

&lt;p&gt;Despite all the digging, I am still unaware of the intention behind the bypass code’s original creation.&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;/**
 * This allows errors to be skippped [sic] by typing a secret phrase into the page.
 * @param {string} e The key that was just pressed.
 */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handleKeypress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// HTTPS errors are serious and should not be ignored. For testing purposes,&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// other approaches are both safer and have fewer side-effects.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// See https://goo.gl/ZcZixP for more details.&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;BYPASS_SEQUENCE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;atob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;dGhpc2lzdW5zYWZl&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;BYPASS_SEQUENCE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;charCodeAt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;keyPressState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;keyCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;keyPressState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;keyPressState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;BYPASS_SEQUENCE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;sendCommand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;SecurityInterstitialCommandId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;CMD_PROCEED&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;keyPressState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;keyPressState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As of July 2025, the bypass (along with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;skippped&lt;/code&gt; typo) has remained unchanged. You can see it in the latest version: &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/refs/tags/140.0.7301.1/components/security_interstitials/core/browser/resources/interstitial_large.js#51&quot;&gt;Chromium (140.0.7301.1)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It still shows up in blogs and social media posts today (&lt;em&gt;though none of them do a deep dive like this one!&lt;/em&gt;), which is how I stumbled upon it.&lt;/p&gt;

&lt;p&gt;What may have started as a convenience for developers, later became a point of concern due to its potential misuse. The change to base64 encoding was likely an attempt to obscure the code from casual users or code scanners, but it is by no means a secret.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/thisisunsafe_trends.png&quot; alt=&quot;thisisunsafe has been rising in popularity since its introduction in 2018&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The rise of HTTPS enforcement has been a net-positive for the web, but it’s hard to articulate the risks of broken SSL to everyday users and why they shouldn’t simply ignore it.&lt;/p&gt;

&lt;p&gt;Is it the job of a browser to keep users safe? Even if one could argue that web access shouldn’t be gated by corporations on the basis of security, the enforcement of HTTPS has undoubtedly incentivized web developers to adopt better security practices.&lt;/p&gt;

&lt;p&gt;What do you think?&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Debouncing API Calls</title>
   <link href="https://thomascountz.com/2025/07/02/debouncing-api-calls"/>
   <updated>2025-07-02T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2025/07/02/debouncing-api-calls</id>
   <summary type="html">Debouncing is a technique used to limit the rate at which a function is executed. It&apos;s particularly useful for optimizing performance in scenarios where a function is called frequently, such as handling user input events or making API calls.</summary>
   <content type="html">
&lt;div&gt;
  &lt;label for=&quot;delay&quot;&gt;Debounce Delay: &lt;span id=&quot;delay-value&quot;&gt;500&lt;/span&gt;ms&lt;/label&gt;
  &lt;input type=&quot;range&quot; id=&quot;delay&quot; min=&quot;0&quot; max=&quot;1000&quot; value=&quot;500&quot; step=&quot;25&quot; /&gt;
  &lt;input type=&quot;text&quot; id=&quot;textInput&quot; placeholder=&quot;Type something...&quot; /&gt;
&lt;/div&gt;

&lt;div id=&quot;output-container&quot; style=&quot;margin-top: 1rem; margin-bottom: 1rem; min-height: 24px;&quot;&gt;
  &lt;input type=&quot;text&quot; id=&quot;outputText&quot; readonly=&quot;&quot; disabled=&quot;&quot; style=&quot;box-sizing: border-box;&quot; /&gt;
  &lt;input type=&quot;range&quot; id=&quot;countdown-visualizer&quot; min=&quot;0&quot; max=&quot;1000&quot; value=&quot;0&quot; step=&quot;25&quot; disabled=&quot;&quot; style=&quot;display: none;&quot; /&gt;
&lt;/div&gt;

&lt;script&gt;
   function debounceWithVisualization(callback, delay, visualizationCallback) {
     let timerId;
     let countdownTimer;

     return (...args) =&gt; {
       clearTimeout(timerId);
       clearInterval(countdownTimer);

       visualizationCallback(delay, true);

       let remaining = delay;
       const interval = 10;

       countdownTimer = setInterval(() =&gt; {
         remaining -= interval;
         if (remaining &gt;= 0) {
           visualizationCallback(remaining, true);
         } else {
           clearInterval(countdownTimer);
         }
       }, interval);

       timerId = setTimeout(() =&gt; {
         clearInterval(countdownTimer);
         visualizationCallback(0, false);
         callback(...args);
       }, delay);
     };
   }

   const textInput = document.getElementById(&apos;textInput&apos;);
   const outputText = document.getElementById(&apos;outputText&apos;);
   const delayInput = document.getElementById(&apos;delay&apos;);
   const delayValue = document.getElementById(&apos;delay-value&apos;);
   const countdownVisualizer = document.getElementById(&apos;countdown-visualizer&apos;);

   function updateOutput(text) {
     outputText.value = text;
   }

   function updateVisualization(remaining, isRunning) {
     if (isRunning) {
       outputText.style.display = &apos;none&apos;;
       countdownVisualizer.style.display = &apos;block&apos;;
       countdownVisualizer.value = remaining;
     } else {
       countdownVisualizer.style.display = &apos;none&apos;;
       outputText.style.display = &apos;block&apos;;
     }
   }

   let debouncedUpdate = debounceWithVisualization(updateOutput, delayInput.value, updateVisualization);

   textInput.addEventListener(&apos;input&apos;, (event) =&gt; {
     debouncedUpdate(event.target.value);
   });

   delayInput.addEventListener(&apos;input&apos;, (event) =&gt; {
     const newDelay = event.target.value;
     delayValue.textContent = newDelay;
     countdownVisualizer.max = newDelay;
     debouncedUpdate = debounceWithVisualization(updateOutput, newDelay, updateVisualization);
   });

   countdownVisualizer.max = delayInput.value;
 &lt;/script&gt;

&lt;h2 id=&quot;hardware&quot;&gt;Hardware&lt;/h2&gt;

&lt;p&gt;The term “debouncing” originates from electronics (at least that’s where I first came across it). When you press a physical button or flip a switch, its metal contacts don’t connect cleanly just once; they “bounce,” opening and closing rapidly, creating multiple pulses of current as the circuit connects and disconnects.&lt;/p&gt;

&lt;p&gt;Although this happens on the order of milliseconds, filtering the rapid switching is necessary to prevent other components from misinterpreting the bounces as intentional signals. Debouncing in hardware ensures that only a single, clean signal is sent when the button is pressed.&lt;/p&gt;

&lt;h2 id=&quot;software&quot;&gt;Software&lt;/h2&gt;

&lt;p&gt;In software, we hit this same problem.&lt;/p&gt;

&lt;p&gt;Take search bars, for example. They often fire off an API call on every keystroke to give search results to users with broken enter keys.&lt;/p&gt;

&lt;p&gt;With a typing speed of around 200 characters-per-minute (3.33 characters-per-second), a user searching for “parachute pants” could trigger 16 API calls in about 5 seconds—and that’s assuming they don’t make any typos.&lt;/p&gt;

&lt;p&gt;We can’t possibly know which of those 16 API calls actually improve the user experience, but if I had to guess, search suggestions are most effective when presented after a natural pause in typing—not during every keystroke.&lt;/p&gt;

&lt;h2 id=&quot;debouncing-with-delays&quot;&gt;Debouncing with Delays&lt;/h2&gt;

&lt;p&gt;This &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;debounce&lt;/code&gt; function uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setTimeout&lt;/code&gt; to delay the execution of a callback.&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;debounce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;timerId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;clearTimeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;timerId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;timerId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If the returned function gets called again before the timer finishes, it clears the previous timer and starts a new one. This resets the wait time and prevents callbacks from executing until there has been a pause in calls for the specified delay.&lt;/p&gt;

&lt;p&gt;This is exactly what the demo above does. It waits for you to stop typing for a set number of milliseconds before updating the output field with your input.&lt;/p&gt;

&lt;h2 id=&quot;delays-and-cancellations&quot;&gt;Delays and Cancellations&lt;/h2&gt;

&lt;p&gt;For input-triggered API calls (like the search bar example) we can take this further by combining debouncing with request cancellation.&lt;/p&gt;

&lt;p&gt;When the user stops typing in the search bar, the app waits until after a delay (similar to our earlier debounce implementation) before making an API call to fetch search suggestions.&lt;/p&gt;

&lt;p&gt;&lt;input type=&quot;text&quot; id=&quot;apiInput&quot; placeholder=&quot;Type to search products...&quot; /&gt;&lt;/p&gt;
&lt;ul style=&quot;min-height: 8em; width: calc(round(down, 100%, 1ch)); border: var(--border-thickness) solid var(--text-color)&quot; id=&quot;apiOutput&quot;&gt;&lt;/ul&gt;

&lt;script&gt;
  const apiInput = document.getElementById(&quot;apiInput&quot;);
  const apiOutput = document.getElementById(&quot;apiOutput&quot;);

  let timerId;
  let controller;

  apiInput.addEventListener(&quot;input&quot;, (event) =&gt; {
    const query = event.target.value;

    clearTimeout(timerId);
    if (controller) {
      controller.abort();
    }

    if (!query || query.trim() === &quot;&quot;) {
      apiOutput.innerHTML = &quot;&quot;;
      return;
    }

    apiOutput.innerHTML = &quot;&lt;li&gt;Debouncing...&lt;/li&gt;&quot;;

    timerId = setTimeout(async () =&gt; {
      try {
        controller = new AbortController();
        apiOutput.innerHTML = &quot;&lt;li&gt;Fetching...&lt;/li&gt;&quot;;

        const response = await fetch(`https://dummyjson.com/products/search?q=${query}&amp;delay=1000`, {
          signal: controller.signal,
        });

        const data = await response.json();
        apiOutput.innerHTML = &quot;&quot;;

        if (data.products &amp;&amp; data.products.length &gt; 0) {
          const productsToShow = data.products.slice(0, 5);
          for (const product of productsToShow) {
            const li = document.createElement(&quot;li&quot;);
            li.textContent = product.title;
            apiOutput.appendChild(li);
          }
        } else {
          apiOutput.innerHTML = &quot;&lt;li&gt;No products found.&lt;/li&gt;&quot;;
        }
      } catch (error) {
        if (error.name == &quot;AbortError&quot;) {
          apiOutput.innerHTML = &quot;&lt;li&gt;Request cancelled.&lt;/li&gt;&quot;;
        } else {
          apiOutput.innerHTML = &quot;&lt;li&gt;Error fetching data.&lt;/li&gt;&quot;;
          console.error(error);
        }
      }
    }, 750);
  });
&lt;/script&gt;

&lt;p&gt;If, however, the user begins typing again &lt;em&gt;before&lt;/em&gt; the fetch resolves, we need a way to throw away the request. This is because, by the time we would receive a response, the user has already moved on and started searching for something else.&lt;/p&gt;

&lt;p&gt;So, before making any new API requests, we first cancel the previous &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch&lt;/code&gt;, using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AbortController&lt;/code&gt; API.&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;debouncedFetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;timerId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;controller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;return &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;clearTimeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;timerId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;controller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;controller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;abort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;controller&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AbortController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;signal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;controller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;signal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;timerId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;async &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`https://example.com`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;signal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;controller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;signal&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// Handle the response here&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;debouncedFetch&lt;/code&gt; function works in three steps.&lt;/p&gt;

&lt;p&gt;First, it clears any pending timer and cancels any in-flight request. If there’s no previous request (like on the first call), the latter step is skipped.&lt;/p&gt;

&lt;p&gt;Then, it creates a new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AbortController&lt;/code&gt; and extracts its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;signal&lt;/code&gt;. This signal acts like a remote control that can cancel the upcoming &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch&lt;/code&gt; request at any time. This works because the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch&lt;/code&gt; API supports aborting requests using an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AbortSignal&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finally, it starts a new timer. When the timer expires, it calls &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch&lt;/code&gt;. And when &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch&lt;/code&gt; resolves, we handle the response as needed.&lt;/p&gt;

&lt;p&gt;So, as before, if the user types again before the timer expires, the timer is restarted before a new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch&lt;/code&gt; request is made.&lt;/p&gt;

&lt;p&gt;In addition to this, if the user types again &lt;em&gt;while the previous &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch&lt;/code&gt; is still in progress&lt;/em&gt;, that request is cancelled, the new input is debounced, and then finally, a new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch&lt;/code&gt; request is made with the latest input.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Where Does Apple Hide Your Voice Memo Transcripts?</title>
   <link href="https://thomascountz.com/2025/06/08/unlocking-apple-voice-memo-transcripts"/>
   <updated>2025-06-08T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2025/06/08/unlocking-apple-voice-memo-transcripts</id>
   <summary type="html">Apple&apos;s Voice Memos app automatically transcribes your recordings, but accessing those transcripts isn&apos;t straightforward. I explored the .m4a file format and show you how to extract them.</summary>
   <content type="html">&lt;p&gt;Much like having thoughts in the shower and working on trains, I often find my best thinking happens while walking my dog in the morning. To not lose track of all this good thinking, I’ve started recording voice notes on my iPhone using Apple’s &lt;em&gt;Voice Memos&lt;/em&gt; app.&lt;/p&gt;

&lt;p&gt;After recording, the app automatically generates transcriptions, allowing me to easily search, copy, and edit my memos. Unfortunately—Apple being Apple—you can’t access these transcripts from anywhere outside the Voice Memos app.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Just want to see the code? Check out these Gists:&lt;/p&gt;

&lt;div class=&quot;grid&quot;&gt;

  &lt;a href=&quot;https://gist.github.com/thomascountz/b84b68f0a7c6f2f851ebc5db152b676a&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; class=&quot;button&quot; style=&quot;text-align: center;text-decoration:none;&quot;&gt;
    Ruby
  &lt;/a&gt;

  &lt;a href=&quot;https://gist.github.com/thomascountz/287d7dd1e04674d22a6396433937cd29&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; class=&quot;button&quot; style=&quot;text-align: center;text-decoration:none;&quot;&gt;
    Bash
  &lt;/a&gt;
&lt;/div&gt;

&lt;hr /&gt;

&lt;p&gt;Using &lt;em&gt;Voice Memos&lt;/em&gt; ticks a lot of boxes for me:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;I can be a better dog dad&lt;/strong&gt;: I used to bring a small notebook on our morning walks, but I felt rude asking my dog to wait while I scribbled something every few meters.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;I get to think out loud&lt;/strong&gt;: I’ve found that verbalizing my ideas helps me organize, prioritize, and clarify them.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;I actually use my notes&lt;/strong&gt;: Writing emails, drafting blog posts, making todo lists; automatic transcriptions are queryable and ready for editing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is what’s called a “win-win-win”—and I love it when it happens—but that last “win” doesn’t come as easily as the others.&lt;/p&gt;

&lt;h2 id=&quot;step-0-the-problem&quot;&gt;Step 0: The Problem&lt;/h2&gt;

&lt;p&gt;Unfortunately, the only way to get your transcripts out of &lt;em&gt;Voice Memos&lt;/em&gt; and into somewhere useful, is to clumsily copy-and-paste them.&lt;/p&gt;

&lt;p&gt;This is an example of the worst kind of problem.&lt;/p&gt;

&lt;p&gt;Copying-and-pasting isn’t a big deal, I know. But it is &lt;em&gt;just enough of a deal&lt;/em&gt; to stop me from using the transcripts like I want to—which have become more valuable to me than the audio itself.&lt;/p&gt;

&lt;p&gt;Plus, if there’s anything we programmers hate most, it’s copying-and-pasting… isn’t that what we have computers for?&lt;/p&gt;

&lt;h2 id=&quot;step-1-find-the-transcripts&quot;&gt;Step 1: Find the Transcripts&lt;/h2&gt;

&lt;p&gt;Thinking of how I might solve this, I was reminded of a time when I tried exporting text from &lt;em&gt;Apple Notes&lt;/em&gt;. When synced via iCloud, I learned that &lt;em&gt;Notes&lt;/em&gt; uses SQLite and that it was accessible on Mac.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;~/Library/Group Containers/group.com.apple.notes/NoteStore.sqlite
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Thankfully, the authors of tools like &lt;a href=&quot;https://github.com/dogsheep/apple-notes-to-sqlite&quot;&gt;apple-notes-to-sqlite&lt;/a&gt; had already deciphered and documented the complicated schemas and built open source tools to solve my problem.&lt;/p&gt;

&lt;p&gt;I dove in hoping the same would be true for &lt;em&gt;Voice Memos&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;However, in the far reaches of Apple StackExchange, I came across this:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Q. Where are the transcripts that are generated from recordings in the Voice Memos app stored in the file system?&lt;/p&gt;

  &lt;p&gt;A. There is no documentation from Apple that I know of, but the MPEG-4 standard provides for encoding a text stream in the container itself; i.e., the *.m4a file. That must be what Voice Memos is doing. I don’t see any separate file resulting from transciption [sic]. See: ISO/IEC 14496-17:2006(en) Information technology — Coding of audio-visual objects — Part 17: Streaming text format (iso.org)&lt;/p&gt;

  &lt;p&gt;— &lt;a href=&quot;https://apple.stackexchange.com/questions/478073/location-of-voice-memos-transcripts-in-the-file-system&quot;&gt;&lt;em&gt;Location of Voice Memos transcripts in the file system?&lt;/em&gt;, Apple StackExchange&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The good news was that, when synced via iCloud, the &lt;em&gt;Voice Memos&lt;/em&gt; audio could be accessed as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.m4a&lt;/code&gt; files directly.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;~/Library/Group Containers/group.com.apple.VoiceMemos.shared/Recordings/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The bad news was that I didn’t know anything about audio file formats or what “…encoding a text stream in the container itself…” meant.&lt;/p&gt;

&lt;h2 id=&quot;step-2-explore-the-files&quot;&gt;Step 2: Explore the Files&lt;/h2&gt;

&lt;p&gt;To postpone having to open up any part of ISO/IEC 14496&lt;sup id=&quot;fnref:14496-12&quot;&gt;&lt;a href=&quot;#fn:14496-12&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, I thought I’d just poke around the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.m4a&lt;/code&gt; files a bit.&lt;/p&gt;

&lt;p&gt;If you try opening one of those files in a text editor, you’ll see that it is not meant to be read by humans. But, useful plaintext strings can often be found buried within various binary formats.&lt;/p&gt;

&lt;p&gt;Finding these hidden strings is something reverse engineers do when they otherwise don’t know how to decompile or deconstruct a binary file. (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.m4a&lt;/code&gt; is a well established format, but we haven’t read the documentation yet, remember?)&lt;/p&gt;

&lt;p&gt;If Apple is encoding transcripts directly within the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.m4a&lt;/code&gt; file, as the StackExchange answer suggested, we might be able to see them using the command line tool, &lt;a href=&quot;https://manpages.ubuntu.com/manpages/questing/en/man1/strings.1.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strings&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;strings &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; 24 &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;*.m4a&apos;&lt;/span&gt;

7724    com.apple.VoiceMemos &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;iPhone Version 18.5 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Build 22F76&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;hll&quot;&gt;7438979 tsrp&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;hll&quot;&gt;  &lt;span class=&quot;s2&quot;&gt;&quot;attributedString&quot;&lt;/span&gt;:&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;hll&quot;&gt;    &lt;span class=&quot;s2&quot;&gt;&quot;attributeTable&quot;&lt;/span&gt;:[&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;timeRange&quot;&lt;/span&gt;:[0,2.52]&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;timeRange&quot;&lt;/span&gt;:[2.52,2.7]&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;timeRange&quot;&lt;/span&gt;:[2.7,2.88]&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;timeRange&quot;&lt;/span&gt;:[2.88,3.12]&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;timeRange&quot;&lt;/span&gt;:  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;3.12,3.72]&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;timeRange&quot;&lt;/span&gt;:[3.72,4.74]&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;timeRange&quot;&lt;/span&gt;:[4.74,4.92]&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;timeRange&quot;&lt;/span&gt;:[4.92,5.22]&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;timeRange&quot;&lt;/span&gt;:[5.22,5.34]&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;timeRange&quot;&lt;/span&gt;:[5.34,5.52]&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;timeRange&quot;&lt;/span&gt;:[5.52,5.7]&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;timeRange&quot;&lt;/span&gt;:[5.7,6.6]&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;timeRange&quot;&lt;/span&gt;:[6.6,6.78]&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;timeRange&quot;&lt;/span&gt;:  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;6.78,6.96]&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;timeRange&quot;&lt;/span&gt;:[6.96,7.2]&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,...],
&lt;/span&gt;&lt;span class=&quot;hll&quot;&gt;    &lt;span class=&quot;s2&quot;&gt;&quot;runs&quot;&lt;/span&gt;:[&lt;span class=&quot;s2&quot;&gt;&quot;OK,&quot;&lt;/span&gt;,0,&lt;span class=&quot;s2&quot;&gt;&quot; I&quot;&lt;/span&gt;,1,&lt;span class=&quot;s2&quot;&gt;&quot; went&quot;&lt;/span&gt;,2,&lt;span class=&quot;s2&quot;&gt;&quot; back&quot;&lt;/span&gt;,3,&lt;span class=&quot;s2&quot;&gt;&quot; and&quot;&lt;/span&gt;,4,&lt;span class=&quot;s2&quot;&gt;&quot; re&quot;&lt;/span&gt;,5,&lt;span class=&quot;s2&quot;&gt;&quot; read&quot;&lt;/span&gt;,6,&lt;span class=&quot;s2&quot;&gt;&quot; at&quot;&lt;/span&gt;,7,&lt;span class=&quot;s2&quot;&gt;&quot; least&quot;&lt;/span&gt;,8,&lt;span class=&quot;s2&quot;&gt;&quot; the&quot;&lt;/span&gt;,9,&lt;span class=&quot;s2&quot;&gt;&quot; beginning&quot;&lt;/span&gt;,10,&lt;span class=&quot;s2&quot;&gt;&quot; of&quot;&lt;/span&gt;,11,  &lt;span class=&quot;s2&quot;&gt;&quot; the&quot;&lt;/span&gt;,12,&lt;span class=&quot;s2&quot;&gt;&quot; Google&quot;&lt;/span&gt;,13,&lt;span class=&quot;s2&quot;&gt;&quot; paper&quot;&lt;/span&gt;,14,...]
&lt;/span&gt;&lt;span class=&quot;hll&quot;&gt;  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,
&lt;/span&gt;&lt;span class=&quot;hll&quot;&gt;  &lt;span class=&quot;s2&quot;&gt;&quot;locale&quot;&lt;/span&gt;:&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;identifier&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;en_US&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;current&quot;&lt;/span&gt;:0&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;hll&quot;&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/span&gt;7607856 date2025-06-08T08:46:16Z
7607958 Self-Replicators Metrics &amp;amp; Analysis Architecture

&lt;span class=&quot;c&quot;&gt;# -n &amp;lt;number&amp;gt;: The minimum string length to print&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# -o: Preceded each string by its offset (in decimal)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Sure enough, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strings&lt;/code&gt; reveals that, starting at byte &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;7438979&lt;/code&gt;, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.m4a&lt;/code&gt; file contains a JSON-esque string which has some of my &lt;em&gt;Voice Memos&lt;/em&gt; transcript in it.&lt;/p&gt;

&lt;p&gt;If it really was JSON (spoiler: it was, except for the “&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tsrp&lt;/code&gt;” part before the opening brace), then we should be able to extract and parse it.&lt;/p&gt;

&lt;h2 id=&quot;step-3-json-extraction&quot;&gt;Step 3: JSON Extraction&lt;/h2&gt;

&lt;p&gt;Sticking to the commandline, let’s use a few more tools for wrangling this text.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://manpages.debian.org/testing/binutils-common/strings.1.en.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strings&lt;/code&gt;&lt;/a&gt;, as before, will output printable strings,&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://manpages.debian.org/testing/ripgrep/rg.1.en.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rg&lt;/code&gt;&lt;/a&gt;, will filter for that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tsrp&lt;/code&gt; prefix,&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://manpages.debian.org/testing/sed/sed.1.en.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sed&lt;/code&gt;&lt;/a&gt;, will remove the prefix and leave just the JSON, and finally,&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://manpages.debian.org/testing/jq/jq.1.en.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jq&lt;/code&gt;&lt;/a&gt;, will let us explore and manipulate the payload.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Author’s note: this was a very exploratory and iterative process, especially the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jq&lt;/code&gt; code. I’ve written a tool called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ijq&lt;/code&gt; to help with this. You can read about it here: &lt;a href=&quot;/2025/01/31/interactive-jq&quot;&gt;Interactive jq&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The first thing I’m interested in is the structure of the JSON.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;c&quot;&gt;# Extract printable strings from the audio file&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;strings &lt;span class=&quot;s1&quot;&gt;&apos;*.m4a&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# Only keep line that starts with the &quot;tsrp&quot; prefix&lt;/span&gt;
| rg &lt;span class=&quot;s1&quot;&gt;&apos;tsrp&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# Remove the &quot;tsrp&quot; prefix keep only the JSON&lt;/span&gt;
| &lt;span class=&quot;nb&quot;&gt;sed&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;s/tsrp//g&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# Extract the paths of every key in the JSON&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# If the path is an array index, replace it with brackets&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# Join the paths with a dot&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# Remove duplicates&lt;/span&gt;
| jq &lt;span class=&quot;s1&quot;&gt;&apos;[paths | map(if type == &quot;number&quot; then &quot;[]&quot; else . end) | join(&quot;.&quot;)] | unique&apos;&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;attributedString&quot;&lt;/span&gt;,
  &lt;span class=&quot;s2&quot;&gt;&quot;attributedString.attributeTable&quot;&lt;/span&gt;,
  &lt;span class=&quot;s2&quot;&gt;&quot;attributedString.attributeTable.[]&quot;&lt;/span&gt;,
  &lt;span class=&quot;s2&quot;&gt;&quot;attributedString.attributeTable.[].timeRange&quot;&lt;/span&gt;,
  &lt;span class=&quot;s2&quot;&gt;&quot;attributedString.attributeTable.[].timeRange.[]&quot;&lt;/span&gt;,
  &lt;span class=&quot;s2&quot;&gt;&quot;attributedString.runs&quot;&lt;/span&gt;,
  &lt;span class=&quot;s2&quot;&gt;&quot;attributedString.runs.[]&quot;&lt;/span&gt;,
  &lt;span class=&quot;s2&quot;&gt;&quot;locale&quot;&lt;/span&gt;,
  &lt;span class=&quot;s2&quot;&gt;&quot;locale.current&quot;&lt;/span&gt;,
  &lt;span class=&quot;s2&quot;&gt;&quot;locale.identifier&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;details&gt;
&lt;summary&gt;Take a closer look at how the &lt;code&gt;jq&lt;/code&gt; code works &lt;/summary&gt;


&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;demo.json
&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;foo&quot;&lt;/span&gt;: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;bar&quot;&lt;/span&gt;: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; 1, 2, 3 &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;%

&lt;span class=&quot;c&quot;&gt;# Extract the paths of every key in the JSON&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;demo.json | jq &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;[paths]&apos;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;,[&lt;span class=&quot;s2&quot;&gt;&quot;foo&quot;&lt;/span&gt;,0],[&lt;span class=&quot;s2&quot;&gt;&quot;foo&quot;&lt;/span&gt;,0,&lt;span class=&quot;s2&quot;&gt;&quot;bar&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;,[&lt;span class=&quot;s2&quot;&gt;&quot;foo&quot;&lt;/span&gt;,0,&lt;span class=&quot;s2&quot;&gt;&quot;bar&quot;&lt;/span&gt;,0],[&lt;span class=&quot;s2&quot;&gt;&quot;foo&quot;&lt;/span&gt;,0,&lt;span class=&quot;s2&quot;&gt;&quot;bar&quot;&lt;/span&gt;,1],[&lt;span class=&quot;s2&quot;&gt;&quot;foo&quot;&lt;/span&gt;,0,&lt;span class=&quot;s2&quot;&gt;&quot;bar&quot;&lt;/span&gt;,2]]

&lt;span class=&quot;c&quot;&gt;# If the path is an array index, replace it with&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;demo.json | jq &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;[paths | map(if type == &quot;number&quot; then &quot;[]&quot; else . end)]&apos;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;,[&lt;span class=&quot;s2&quot;&gt;&quot;foo&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;[]&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;,[&lt;span class=&quot;s2&quot;&gt;&quot;foo&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;[]&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;bar&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;,[&lt;span class=&quot;s2&quot;&gt;&quot;foo&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;[]&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;bar&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;[]&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;,[&lt;span class=&quot;s2&quot;&gt;&quot;foo&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;[]&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;bar&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;[]&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;,[&lt;span class=&quot;s2&quot;&gt;&quot;foo&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;[]&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;bar&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;[]&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Join the paths with a dot&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;demo.json | jq &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;[paths | map(if type == &quot;number&quot; then &quot;[]&quot; else . end) | join(&quot;.&quot;)]&apos;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;foo&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;foo.[]&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;foo.[].bar&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;foo.[].bar.[]&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;foo.[].bar.[]&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;foo.[].bar.[]&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Remove duplicates&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;demo.json | jq &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;[paths | map(if type == &quot;number&quot; then &quot;[]&quot; else . end) | join(&quot;.&quot;)] | unique&apos;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;foo&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;foo.[]&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;foo.[].bar&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;foo.[].bar.[]&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;


&lt;/details&gt;

&lt;p&gt;The transcript data is stored in an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;attributedString&lt;/code&gt; object, which has an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;attributeTable&lt;/code&gt; (an array of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timeRange&lt;/code&gt; objects) and a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runs&lt;/code&gt; array. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;locale&lt;/code&gt; object contains the language identifier and current locale.&lt;/p&gt;

&lt;p&gt;The actual text of the transcript is inside the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runs&lt;/code&gt; array. Each word is followed by an index.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;strings &lt;span class=&quot;s1&quot;&gt;&apos;*.m4a&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  | rg &lt;span class=&quot;s2&quot;&gt;&quot;tsrp&quot;&lt;/span&gt;  &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  | &lt;span class=&quot;nb&quot;&gt;sed&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;s/tsrp//g&apos;&lt;/span&gt;
  | jq &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;[limit(30; .attributedString.runs[])]&apos;&lt;/span&gt;

 &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;OK,&quot;&lt;/span&gt;,0,&lt;span class=&quot;s2&quot;&gt;&quot; I&quot;&lt;/span&gt;,1,&lt;span class=&quot;s2&quot;&gt;&quot; went&quot;&lt;/span&gt;,2,&lt;span class=&quot;s2&quot;&gt;&quot; back&quot;&lt;/span&gt;,3,&lt;span class=&quot;s2&quot;&gt;&quot; and&quot;&lt;/span&gt;,4,&lt;span class=&quot;s2&quot;&gt;&quot; we&quot;&lt;/span&gt;,5,&lt;span class=&quot;s2&quot;&gt;&quot; read&quot;&lt;/span&gt;,6,&lt;span class=&quot;s2&quot;&gt;&quot; at&quot;&lt;/span&gt;,7,&lt;span class=&quot;s2&quot;&gt;&quot; least&quot;&lt;/span&gt;,8,&lt;span class=&quot;s2&quot;&gt;&quot; the&quot;&lt;/span&gt;,9,&lt;span class=&quot;s2&quot;&gt;&quot; beginning&quot;&lt;/span&gt;,10,&lt;span class=&quot;s2&quot;&gt;&quot; of&quot;&lt;/span&gt;,11,&lt;span class=&quot;s2&quot;&gt;&quot; the&quot;&lt;/span&gt;,12,&lt;span class=&quot;s2&quot;&gt;&quot; Google&quot;&lt;/span&gt;,13,&lt;span class=&quot;s2&quot;&gt;&quot; paper&quot;&lt;/span&gt;,14]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The index maps to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timeRange&lt;/code&gt; in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;attributeTable&lt;/code&gt; array.&lt;/p&gt;

&lt;p&gt;Each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timeRange&lt;/code&gt; is an array itself, containing two numbers: the start and end time offset of each transcribed word, in seconds.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;strings &lt;span class=&quot;s1&quot;&gt;&apos;*.m4a&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  | rg &lt;span class=&quot;s2&quot;&gt;&quot;tsrp&quot;&lt;/span&gt;  &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  | &lt;span class=&quot;nb&quot;&gt;sed&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;s/tsrp//g&apos;&lt;/span&gt;
  | jq  &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;[limit(15; .attributedString.attributeTable[].timeRange)]&apos;&lt;/span&gt;

 &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt;0,2.52],[2.52,2.7],[2.7,2.88],[2.88,3.12],[3.12,3.72],[3.72,4.74],[4.74,4.92],[4.92,5.22],[5.22,5.34],[5.34,5.52],[5.52,5.7],[5.7,6.6],[6.6,6.78],[6.78,6.96],[6.96,7.2]]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;p&gt;Let’s just take a moment to pause and appreciate that, despite not knowing anything about audio formats or how text streaming works in ISO/IEC 14496-17:2006(en), we were able to find the raw transcript embedded in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.m4a&lt;/code&gt; file!&lt;/p&gt;

&lt;p&gt;From here, we could continue wrangling and reconstruct the text.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;... | jq &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;[limit(30; .attributedString.runs[])] | map(if type == &quot;string&quot; then . else empty end) | join(&quot;&quot;)&apos;&lt;/span&gt;

&lt;span class=&quot;s2&quot;&gt;&quot;OK, I went back and we read at least the beginning of the Google paper&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But, since the dog has already gone for a walk, I guess there’s no better time than now to crack open ISO/IEC 14496, and really try to understand what’s going on.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;containers&quot;&gt;Containers&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.m4a&lt;/code&gt; files are a type of MPEG-4 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.mp4&lt;/code&gt;), specifically an audio-only variant. The MPEG-4 Part 14 (.mp4) standard is an implementation of the ISO Base Media File Format (Part 12 of ISO/IEC 14496).&lt;/p&gt;

&lt;p&gt;These files are called “container formats” because they bundle together multiple different types of data.&lt;/p&gt;

&lt;p&gt;The “A” in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.m4a&lt;/code&gt; stands for “audio”, but these file types can also hold things like images, video, artist metadata, chapter markers, and yes, even transcriptions.&lt;/p&gt;

&lt;h3 id=&quot;atoms&quot;&gt;Atoms&lt;/h3&gt;

&lt;p&gt;The “container” is organized in a hierarchical structure of “boxes” (which Apple calls “atoms”). Each atom contains a particular piece of data related to the media being stored; like metadata, configuration, the media itself, or other nested atoms.&lt;/p&gt;

&lt;p&gt;We can use the &lt;a href=&quot;https://www.bento4.com/documentation/mp4dump/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mp4dump&lt;/code&gt;&lt;/a&gt; command-line tool to look at the atom structure of our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.m4a&lt;/code&gt; file.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;mp4dump &lt;span class=&quot;s1&quot;&gt;&apos;.m4a&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;details&gt;
&lt;summary&gt;View full &lt;code&gt;mp4dump&lt;/code&gt; output&lt;/summary&gt;


&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;ftyp] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+20
  major_brand &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;.m4a&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;
  minor_version &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 0
  compatible_brand &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;.m4a&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;
  compatible_brand &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; isom
  compatible_brand &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; mp42
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;mdat] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;16+7279219
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;moov] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+328928
  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;mvhd] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;12+96
    timescale &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 16000
    duration &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 38190080
    duration&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 2386880
  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;trak] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+328457
    &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;tkhd] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;12+80, &lt;span class=&quot;nv&quot;&gt;flags&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1
      enabled &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 1
      &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 1
      duration &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 38190080
      width &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 0.000000
      height &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 0.000000
    &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;mdia] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+159480
      &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;mdhd] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;12+20
        timescale &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 16000
        duration &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 38190080
        duration&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 2386880
        language &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; und
      &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;hdlr] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;12+37
        handler_type &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; soun
        handler_name &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; Core Media Audio
      &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;minf] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+159391
        &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;smhd] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;12+4
          balance &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 0
        &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;dinf] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+28
          &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;dref] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;12+16
            &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;url &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;12+0, &lt;span class=&quot;nv&quot;&gt;flags&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1
              location &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;local &lt;/span&gt;to file]
        &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;stbl] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+159331
          &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;stsd] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;12+91
            entry_count &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 1
            &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;mp4a] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+79
              data_reference_index &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 1
              channel_count &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 2
              sample_size &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 16
              sample_rate &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 16000
              &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;esds] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;12+39
                &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;ESDescriptor] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;5+34
                  es_id &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 0
                  stream_priority &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 0
                  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;DecoderConfig] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;5+20
                    stream_type &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 5
                    object_type &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 64
                    up_stream &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 0
                    buffer_size &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 6144
                    max_bitrate &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 24000
                    avg_bitrate &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 24000
                    DecoderSpecificInfo &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 14 08
                  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Descriptor:06] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;5+1
          &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;stts] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;12+12
            entry_count &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 1
          &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;stsc] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;12+28
            entry_count &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 2
          &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;stsz] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;12+149188
            sample_size &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 0
            sample_count &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 37295
          &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;stco] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;12+9952
            entry_count &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 2487
    &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;udta] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+168869
      &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;tsrp] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+168861
  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;udta] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+347
    &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+20
    &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;meta] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;12+307
      &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;hdlr] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;12+22
        handler_type &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; mdir
        handler_name &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;ilst] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+265
        &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;.nam] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+64
          &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;data] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+56
            &lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 1
            lang &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 0
            value &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; Self-Replicators Metrics &amp;amp; Analysis Architecture
        &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;----&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+107
          &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;mean] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+20
            value &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; com.apple.iTunes
          &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;name] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+19
            value &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; voice-memo-uuid
          &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;data] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+44
            &lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 1
            lang &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 0
            value &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; DECAFCAFE-ABCD-ABCD-ABCD-AAAAAAAAAAAA
        &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;.too] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+70
          &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;data] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+62
            &lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 1
            lang &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 0
            value &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; com.apple.VoiceMemos &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;iPad Version 15.5 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Build 24F74&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;


&lt;/details&gt;

&lt;p&gt;Each atom begins with an 8-byte header (typically). The first 4 bytes specify the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;atom size&lt;/code&gt; (a 32-bit integer indicating the total size of the atom, including the header). The next 4 bytes specify the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt; (e.g., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ftyp&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mdat&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;moov&lt;/code&gt;), a four-character code identifying how to interpret the atom’s payload.&lt;sup id=&quot;fnref:atomsize&quot;&gt;&lt;a href=&quot;#fn:atomsize&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mp4dump&lt;/code&gt; outputs the size as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;#{header_size}+#{payload_size}&quot;&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;ftyp] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+20
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;moov] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+328928
  ...
  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;trak] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+328457
    &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;tkhd] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;12+80, &lt;span class=&quot;nv&quot;&gt;flags&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1
    &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;mdia] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+159480
      &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;mdhd] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;12+20
    ...
    &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;udta] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+168869
&lt;span class=&quot;hll&quot;&gt;      &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;tsrp] &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8+168861
&lt;/span&gt;...&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mp4dump&lt;/code&gt; output shows us the headers of each atom. Here, we can see one is of type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;tsrp&quot;&lt;/code&gt;, which matches the string we found earlier with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strings&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tsrp&lt;/code&gt; is a “leaf” atom since it contains no child atoms in the hierarchy. Leaf atoms are the ones that usually contain media data, and this particular &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tsrp&lt;/code&gt; atom takes up &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;168861&lt;/code&gt; bytes.&lt;/p&gt;

&lt;p&gt;Apple defines what each of these atoms are for and how they’re structured in their &lt;a href=&quot;https://developer.apple.com/documentation/quicktime-file-format&quot;&gt;Quicktime File Format&lt;/a&gt; documentation.&lt;sup id=&quot;fnref:isot&quot;&gt;&lt;a href=&quot;#fn:isot&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; The documentation provides a list of the most common atoms, their types, and their purposes.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Atom Type&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ftyp&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;An atom that identifies the file type specifications with which the file is compatible.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;moov&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;An atom that specifies the information that defines a movie.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trak&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;An atom that defines a single track of a movie.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tkhd&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;An atom that specifies the characteristics of a single track within a movie. (Required by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trak&lt;/code&gt;)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mdia&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;An atom that describes and defines a track’s media type and sample data. (Required by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trak&lt;/code&gt;)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mdhd&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;An atom that specifies the characteristics of a media, including time scale and duration. (Required by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mdia&lt;/code&gt;)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;udta&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;An atom where you define and store data associated with a QuickTime object, e.g. copyright.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tsrp&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;NULL??&lt;/em&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;As you might have noticed, I didn’t include a definition for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tsrp&lt;/code&gt; atom in the table above.&lt;/p&gt;

&lt;p&gt;This is because Apple doesn’t document it, and being a custom atom, it’s not part of the ISO/IEC 14496 standard itself.&lt;/p&gt;

&lt;p&gt;All we know is that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tsrp&lt;/code&gt; is a custom atom type used to store transcriptions of audio recordings in JSON.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;I’m very curious about this:&lt;/p&gt;

&lt;p&gt;Why doesn’t Apple document their custom &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tsrp&lt;/code&gt; atom, similar to how they document other custom atoms?&lt;/p&gt;

&lt;p&gt;Why have a custom atom at all? Surely there might be a suitable type already in the standards?&lt;sup id=&quot;fnref:4&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;If not, why use a JSON string, instead of nesting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text&lt;/code&gt; atoms within the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tsrp&lt;/code&gt; atom?&lt;sup id=&quot;fnref:qtatoms&quot;&gt;&lt;a href=&quot;#fn:qtatoms&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;And, what are the trade-offs to storing the transcript in SQLite, similar to &lt;em&gt;Notes&lt;/em&gt;?&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;One benefit of storing the transcript directly in the audio file is its portability. Saving the file means you also save the transcript, even outside of Apple’s ecosystem.&lt;/p&gt;

&lt;p&gt;Then again, Apple has this to say about undocumented atom types:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;If your application encounters an atom of an unknown type, it should not attempt to interpret the atom’s data. Use the atom’s size field to skip this atom and all of its contents. This allows a degree of forward compatibility with extensions to the QuickTime file format.&lt;/p&gt;

  &lt;p&gt;—&lt;a href=&quot;https://developer.apple.com/documentation/quicktime-file-format/atoms#Atom-structure&quot;&gt;QuickTime File Format/Storing and sharing media with QuickTime files/Atoms&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;accessing-the-transcript-atom&quot;&gt;Accessing the Transcript Atom&lt;/h2&gt;

&lt;p&gt;Ignoring Apple’s advice, I’ve written a Ruby script to attempt to interpret this atom’s data.&lt;/p&gt;

&lt;p&gt;Using what we’ve discovered, the script parses atom headers, recursively traverses the hierarchy to find the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tsrp&lt;/code&gt; atom, and then extracts and reconstructs the JSON payload within.&lt;/p&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Ruby&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://gist.github.com/thomascountz/b84b68f0a7c6f2f851ebc5db152b676a&quot;&gt;https://gist.github.com/thomascountz/b84b68f&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;It was a great learning experience to read through the documentation and write the Ruby script. But, to make the job even easier, we can use purpose-built tools and libraries.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.bento4.com/documentation/mp4extract/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mp4extract&lt;/code&gt;&lt;/a&gt; (which comes bundled with the same tools as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mp4dump&lt;/code&gt;), takes an atom path, like “&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;moov/trak/udta/tsrp&lt;/code&gt;”, and outputs its payload (using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--payload-only&lt;/code&gt; option avoids also outputting the header).&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;mp4extract &lt;span class=&quot;nt&quot;&gt;--payload-only&lt;/span&gt; moov/trak/udta/tsrp &lt;span class=&quot;s1&quot;&gt;&apos;*.m4a&apos;&lt;/span&gt; tsrp.bin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once we have the payload, we can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jq&lt;/code&gt; to not only parse the JSON, but also extract the text from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runs&lt;/code&gt; array and concatenate it into a single string.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;tsrp.bin | jq &lt;span class=&quot;s1&quot;&gt;&apos;.attributedString.runs | map(if type == &quot;string&quot; then . else empty end) | join(&quot;&quot;)&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This now means that we only need to copy-and-paste a few lines of code to get the transcripts out of &lt;em&gt;Voice Memos&lt;/em&gt; and into somewhere useful!&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Better dog dad? &lt;mark&gt;&amp;nbsp;Win.&lt;/mark&gt;&lt;/li&gt;
  &lt;li&gt;Thinking out loud? &lt;mark&gt;&amp;nbsp;Win.&lt;/mark&gt;&lt;/li&gt;
  &lt;li&gt;&lt;s&gt;No copy-and-paste?&lt;/s&gt; Improved workflow? &lt;mark&gt;&amp;nbsp;Win.&lt;/mark&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;postscript&quot;&gt;Postscript&lt;/h2&gt;
&lt;p&gt;First, I hope you enjoyed this journey into reverse engineering Apple’s &lt;em&gt;Voice Memos&lt;/em&gt; format. I had fun and I hope you did too.&lt;/p&gt;

&lt;p&gt;If you have any questions, comments, or actually know something about this stuff, please feel free to reach out!&lt;/p&gt;

&lt;p&gt;Oh, and if we actually want to avoid having to copy-and-paste, we can, of course, write a bash script :)&lt;/p&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Bash Script&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://gist.github.com/thomascountz/287d7dd1e04674d22a6396433937cd29&quot;&gt;https://gist.github.com/thomascountz/287d7dd&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;footnotes&quot;&gt;Footnotes&lt;/h2&gt;
&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:14496-12&quot;&gt;
      &lt;p&gt;The ISO/IEC 14496 standard is a huge standard with multiple parts. &lt;em&gt;Part 12&lt;/em&gt; defines the ISO Base Media File Format, which is the core of many media file formats, including &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.mp4&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.m4a&lt;/code&gt;. &lt;em&gt;Part 17&lt;/em&gt;, which the StackExchange answer mentions, defines the Streaming Text Format, which is used for text streams in media files. &lt;a href=&quot;#fnref:14496-12&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:atomsize&quot;&gt;
      &lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;size&lt;/code&gt; field may also contain a special value. A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; is used to indicate that the atom is the last one in the file. A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt; is used to indicate that the atom is larger than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2^32&lt;/code&gt; bytes, and it is followed by an additional 64-bit unsigned integer that contains the actual size of the atom. &lt;a href=&quot;#fnref:atomsize&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:isot&quot;&gt;
      &lt;p&gt;Which is convenient, since the real source-of-truth (ISO/IEC 14496) is not available for free. &lt;a href=&quot;#fnref:isot&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:4&quot;&gt;
      &lt;p&gt;Perhaps a standard timed text track (e.g., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tx3g&lt;/code&gt;) within the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;minf&lt;/code&gt; atom could have been used? &lt;a href=&quot;#fnref:4&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:qtatoms&quot;&gt;
      &lt;p&gt;Or use a QT atom or atom container. See: https://developer.apple.com/documentation/quicktime-file-format/qt_atoms_and_atom_containers &lt;a href=&quot;#fnref:qtatoms&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Forth Feedback Loop</title>
   <link href="https://thomascountz.com/2025/06/03/forth-feedback-loop"/>
   <updated>2025-06-03T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2025/06/03/forth-feedback-loop</id>
   <summary type="html">Forth scratches a strange itch in my brain. I wrote a word to help me rapidly iterate on Forth code by automating an edit-reload cycle.</summary>
   <content type="html">&lt;p&gt;Inspired by &lt;a href=&quot;https://youtu.be/mvrE2ZGe-rs?si=xtAowOuaxtU9B3_D&amp;amp;t=1060&quot;&gt;Andreas Wagner’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ecr&lt;/code&gt; word&lt;/a&gt;, I created the word &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-factor highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;go.fs&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;go#empty&lt;/span&gt;  &lt;span class=&quot;s&quot;&gt;s&quot; ---marker--- marker ---marker---&quot;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;evaluate&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;go#open&lt;/span&gt;   &lt;span class=&quot;s&quot;&gt;s&quot; vim my_project.fs&quot;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;system&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;go#run&lt;/span&gt;    &lt;span class=&quot;s&quot;&gt;s&quot; my_project.fs&quot;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;included&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;go&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;go#empty&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;go#open&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;go#run&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;marker&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;---marker---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;marker ---marker---&lt;/code&gt; defines a special word: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;---marker---&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;When executed, it makes Forth forget all definitions made after it (including itself).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go&lt;/code&gt; word orchestrates an edit-reload cycle:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go#empty&lt;/code&gt; is the clever bit.
    &lt;ul&gt;
      &lt;li&gt;It &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;evaluate&lt;/code&gt;-s the string &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;---marker--- marker ---marker---&quot;&lt;/code&gt;.&lt;/li&gt;
      &lt;li&gt;This first runs the &lt;em&gt;existing&lt;/em&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;---marker---&lt;/code&gt; (clearing definitions from the previous load of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;my_project.fs&lt;/code&gt;).&lt;/li&gt;
      &lt;li&gt;Then it defines a &lt;em&gt;new&lt;/em&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;---marker---&lt;/code&gt; at the now-empty top of the dictionary, priming it for the next cycle.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go#open&lt;/code&gt; uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gforth&lt;/code&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;system&lt;/code&gt; to open &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;my_project.fs&lt;/code&gt; in vim.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go#run&lt;/code&gt; uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;included&lt;/code&gt; to load the freshly edited &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;my_project.fs&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>Color Strings Refinement</title>
   <link href="https://thomascountz.com/2025/05/27/color-strings-refinement"/>
   <updated>2025-05-27T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2025/05/27/color-strings-refinement</id>
   <summary type="html">A Ruby refinement that adds methods to the String class for coloring text output in the terminal.</summary>
   <content type="html">&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Colors&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;refine&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;red&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[31m&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[0m&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;green&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[32m&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[0m&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;yellow&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[33m&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[0m&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;blue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[34m&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[0m&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;magenta&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[35m&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[0m&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;cyan&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[36m&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[0m&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;white&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[37m&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[0m&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Usage&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Colors&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Hello, World!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;green&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Good Morning, Team!</title>
   <link href="https://thomascountz.com/2025/03/26/good-morning-team"/>
   <updated>2025-03-26T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2025/03/26/good-morning-team</id>
   <summary type="html">Every so often, I like to write a Ruby script that says good morning to my team in a fun way. Here is the one I made this morning which mimics the famous bouncing DVD logo.</summary>
   <content type="html">&lt;p&gt;Every so often, I like to write a Ruby script that says good morning to my team in a fun way. Here is the one I made this morning which mimics the famous &lt;a href=&quot;https://bouncingdvdlogo.com/&quot;&gt;bouncing DVD logo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/journal/good_morning_team.gif&quot; style=&quot;max-width:480px&quot; alt=&quot;A colorful &apos;Good Morning, Team!&apos; text bouncing around the terminal window&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby wrap-it highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;io/console&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;greeting&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;71&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;111&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;111&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;77&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;111&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;114&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;110&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;105&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;110&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;103&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;44&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;84&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;101&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;97&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;109&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;33&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:chr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;colors&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[31m&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[33m&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[32m&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[36m&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[34m&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[35m&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;columns&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;begin&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;winsize&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;24&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;g_width&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;greeting&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;size&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;g_height&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;columns&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g_width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rows&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g_height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;dx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;dy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;frame&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[H&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[2J&quot;&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;color&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;colors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;frame&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;colors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot; &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;color&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;greeting&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[0m&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;sleep&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.05&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g_width&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;columns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dx&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;frame&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g_height&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dy&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;frame&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dx&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dy&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;sleep&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.25&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Run Commands on File Change</title>
   <link href="https://thomascountz.com/2025/03/17/run-commands-on-file-change"/>
   <updated>2025-03-17T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2025/03/17/run-commands-on-file-change</id>
   <summary type="html">I use `entr` to run commands automatically when files change. Here&apos;s how I use it to run tests.</summary>
   <content type="html">&lt;p&gt;I use &lt;a href=&quot;https://github.com/eradman/entr&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;entr&lt;/code&gt;&lt;/a&gt; to run commands automatically when files change. Here’s how I use it to run tests.&lt;/p&gt;

&lt;p&gt;Use:&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;ls &lt;/span&gt;lib/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; spec/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; | entr &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; bin/test
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Notes:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Install: &lt;a href=&quot;https://formulae.brew.sh/formula/entr#default&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brew install entr&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-c&lt;/code&gt; clears the screen before running the command&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;find lib spec -name &apos;*.rb&apos;&lt;/code&gt; works similarly to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ls&lt;/code&gt;, but I can’t always remember the syntax&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git ls-files&lt;/code&gt; is another option for watching tracked files in a repo&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/watchexec/watchexec&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;watchexec&lt;/code&gt;&lt;/a&gt; is an alternative to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;entr&lt;/code&gt;, known for being more feature-rich&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>Ruby Bug #21117: Numbered Block Params</title>
   <link href="https://thomascountz.com/2025/02/20/breakdown-of-the-fix-for-ruby-bug-21117"/>
   <updated>2025-02-20T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2025/02/20/breakdown-of-the-fix-for-ruby-bug-21117</id>
   <summary type="html">A breakdown of the fix for Ruby Bug #21117, which addressed issues with numbered block parameters and combined assignment operators.</summary>
   <content type="html">&lt;h3 id=&quot;tldr&quot;&gt;tl;dr&lt;/h3&gt;
&lt;ol&gt;
  &lt;li&gt;Numbered block params (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_1&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_2&lt;/code&gt;, etc.) are reserved and read-only, while &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;it&lt;/code&gt; (the new block param in Ruby 3.4) has different mutation rules for compatibility.&lt;/li&gt;
  &lt;li&gt;Using combined assignment operators (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+=&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;&amp;amp;=&lt;/code&gt;), numbered block params could be overwritten without error due to a discrepancy between parse.y and Prism (Ruby’s default parser since 3.3).&lt;/li&gt;
  &lt;li&gt;A patch has been applied to Prism in Ruby 3.4.2 to explicitly check for writes to numbered params via combined assignment operators.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;the-bug&quot;&gt;The Bug&lt;/h3&gt;

&lt;p&gt;In Ruby, block-local numbered parameters (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_1&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_2&lt;/code&gt;, etc.) provide a concise way to reference block arguments without explicit naming. They have received renewed attention recently with the &lt;a href=&quot;https://bugs.ruby-lang.org/issues/18980&quot;&gt;introduction of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;it&lt;/code&gt; parameter&lt;/a&gt; in Ruby 3.4.&lt;/p&gt;

&lt;p&gt;Though they serve a similar purpose, numbered parameters are designed to be read-only, while &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;it&lt;/code&gt; is intentionally mutable for backward compatibility reasons.&lt;/p&gt;

&lt;p&gt;In &lt;a href=&quot;https://bugs.ruby-lang.org/issues/21117&quot;&gt;Bug #21117&lt;/a&gt;, radarek (Radosław Bułat) identified inconsistencies in how Ruby handles assignments to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_1&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;it&lt;/code&gt;. Most concerning was that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_1&lt;/code&gt; could be modified in specific cases, violating its intended immutability.&lt;/p&gt;

&lt;p&gt;Direct assignment correctly raises &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SyntaxError&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;001&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;internal&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:kernel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;168&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:in&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Kernel#loop&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;syntax&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;found&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SyntaxError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
     &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;                  &lt;span class=&quot;o&quot;&gt;^~&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Can&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;assign&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numbered&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parameter&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;However, combined assignment operators work without error:&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;002&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The root cause was an implementation difference between Prism (Ruby’s default parser since 3.3) and parse.y. Prism failed to enforce numbered parameter immutability for combined assignment operations, while the legacy parse.y parser correctly rejected these operations:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# $ ruby --parser &apos;parse.y&apos; -e &apos;binding.irb&apos;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;001&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;internal&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:kernel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;168&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:in&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Kernel#loop&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reserved&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numbered&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parameter&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SyntaxError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;the-fix&quot;&gt;The Fix&lt;/h3&gt;

&lt;p&gt;Commit &lt;a href=&quot;https://github.com/ruby/ruby/commit/d3fc56dcfa7b408cc3b6788efad36fd8df3e55da&quot;&gt;d3fc56d&lt;/a&gt; ports Kevin Newton’s (kddnewton) fix from Prism. The update adds explicit checks when parsing combined assignment tokens (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PM_TOKEN_#{op}_EQUAL&lt;/code&gt;) to reject any attempts to modify numbered parameters.&lt;/p&gt;

&lt;p&gt;Simplified code snippet from the fix:&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// In prism.c:&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// Cases for all combined assignment operators&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PM_TOKEN_PIPE_PIPE_EQUAL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// ...other operator cases...&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PM_TOKEN_PLUS_EQUAL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PM_TOKEN_SLASH_EQUAL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PM_TOKEN_STAR_EQUAL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PM_TOKEN_STAR_STAR_EQUAL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PM_NODE_TYPE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PM_LOCAL_VARIABLE_READ_NODE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Check if we&apos;re trying to modify a numbered parameter&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pm_token_is_numbered_parameter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;PM_PARSER_ERR_FORMAT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                     &lt;span class=&quot;n&quot;&gt;PM_ERR_PARAMETER_NUMBERED_RESERVED&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;parse_target_implicit_parameter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// ...other cases...&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;what-about-it&quot;&gt;What About &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;it&lt;/code&gt;?&lt;/h3&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;it&lt;/code&gt; keyword intentionally has different behavior than numbered parameters for backward compatibility. While direct assignment (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tap { it = 2; p it }&lt;/code&gt;) is currently allowed to avoid breaking legacy code, Ruby core developers encourage treating &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;it&lt;/code&gt; as read-only for consistency with numbered parameters.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Routine Complications</title>
   <link href="https://thomascountz.com/2025/02/15/routine-complications"/>
   <updated>2025-02-15T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2025/02/15/routine-complications</id>
   <summary type="html">Understanding and managing routine complications keeps us honest and responsible, even though we wish things would never go wrong.</summary>
   <content type="html">&lt;p&gt;In &lt;a href=&quot;https://archive.is/0H188&quot;&gt;&lt;em&gt;A Life-Saving Checklist&lt;/em&gt;&lt;/a&gt;, author Atul Gawande describes a patient suffering from an infection introduced by medical equipment. These “line infections” are so common, they’re said to be a &lt;em&gt;routine complication&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In medical practice, a routine complication refers to an &lt;strong&gt;adverse event or difficulty that is known to occur with some regularity in association with a particular procedure or treatment&lt;/strong&gt;. These complications are &lt;em&gt;expected&lt;/em&gt; in the sense that they are well-documented and occur with a known frequency, &lt;strong&gt;allowing healthcare providers to anticipate and manage them effectively&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In other domains, such as software development, it is useful to identify routine complications inherent to various operations, and understand both their potential for happening and their potential negative impacts.&lt;/p&gt;

&lt;p&gt;As an example, during incident response, if standard operating procedure (SOP) includes restarting a web server, routine complications to be understood include: accidentally restarting a healthy server (misdiagnosis), executing a restart incorrectly (human error), cutting off further observations of the issue before it’s fully understood (state evaporation), and encountering unexpected dependencies that cause cascading failures (hidden coupling).&lt;/p&gt;

&lt;p&gt;Just because restarting a web server carries with it these potential issues, doesn’t mean we should shy away from the tried-and-true “turn it off and on again” procedure (every operation carries risk after all). Instead, we should manage routine complications by understanding them honestly, preparing for them openly, and being blameless when they happen.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Hill Chart Web App</title>
   <link href="https://thomascountz.com/2025/02/10/hill-chart-web-app"/>
   <updated>2025-02-10T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2025/02/10/hill-chart-web-app</id>
   <summary type="html">I built an interactive tool for visualizing task progress using hill charts, a visual metaphor for tracking work phases.</summary>
   <content type="html">&lt;p&gt;I built an &lt;a href=&quot;https://hillchart.countzresearch.com&quot;&gt;interactive tool for visualizing task progress&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/journal/hillchart.jpeg&quot; alt=&quot;Screenshot of Hill Chart&quot; /&gt;&lt;/p&gt;

&lt;p&gt;A hill chart is a visual tool for tracking tasks and projects, using the metaphor of going up and down hill to communicate the different phases of work.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Every piece of work has two phases: an uphill phase where you figure out your approach, and a downhill phase focused on execution.&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I appreciate hill charts because they embrace the fuzziness of task-based work. Unlike rigid progress metrics like “done/not done,” percentage completion, or T-shirt sizes, hill charts offer a more nuanced way to describe progress. If you export the chart each time you update it, you can also detect trends over time.&lt;/p&gt;

&lt;p&gt;This tool runs in the browser via Github Pages and is built with plain JavaScript, d3.js, and is styled with TailwindCSS. You can explore the code by right-clicking → View Source or checking out the GitHub repo.&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://basecamp.com/hill-charts&quot;&gt;https://basecamp.com/hill-charts&lt;/a&gt; &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://github.com/Thomascountz/hillchart/&quot;&gt;https://github.com/Thomascountz/hillchart/&lt;/a&gt; &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Capture ActiveRecord Queries</title>
   <link href="https://thomascountz.com/2025/02/09/capture-activerecord-queries"/>
   <updated>2025-02-09T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2025/02/09/capture-activerecord-queries</id>
   <summary type="html">A technique for capturing and inspecting SQL queries generated by ActiveRecord in tests using ActiveSupport::Notifications.</summary>
   <content type="html">&lt;p&gt;When you’re interested in testing the actual SQL queries generated by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveRecord&lt;/code&gt;, rather than just what’s returned from the database, you can capture them using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveSupport::Notifications&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;QueryCapturing&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;IGNORED_QUERIES&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;SCHEMA&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;TRANSACTION&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;capture_queries&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;captured_queries&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;subscriber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Notifications&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sql.active_record&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;captured_queries&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IGNORED_QUERIES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;captured_queries&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;ensure&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Notifications&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;unsubscribe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subscriber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ServiceMetadata::QueryCommentsTest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Minitest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Test&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;QueryCapturing&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_annotates_queries_with_metadata_comments&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;capture_queries&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;queries&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;ServiceMetadata&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;QueryComments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;load&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;queries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;all?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;match?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/\/\*.*?origin_service: my_service.*?\*\//m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Ruby CLI Progress Bar</title>
   <link href="https://thomascountz.com/2025/02/08/ruby-cli-progress-bar"/>
   <updated>2025-02-08T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2025/02/08/ruby-cli-progress-bar</id>
   <summary type="html">A simple Ruby function to display a progress bar in the terminal.</summary>
   <content type="html">&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;print_progress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current_progress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;bar_width: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;progress_pct&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_progress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bar_width&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: [%-&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bar_width&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;s ] -- %s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;▤&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;progress_pct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;round&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_progress&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;total&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Usage&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;upto&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;print_progress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Here we go!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;sleep&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.2&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Output Example:&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Here we go!: [▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤                ] -- 7/10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Checklist App Prototype</title>
   <link href="https://thomascountz.com/2025/02/01/checklist-app-prototype"/>
   <updated>2025-02-01T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2025/02/01/checklist-app-prototype</id>
   <summary type="html">A prototype for a checklist app designed to help users complete procedures step-by-step.</summary>
   <content type="html">&lt;p&gt;I’ve been playing around with the idea of building a checklist app that’s geared towards completing procedures.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/journal/checklist_procedure_app_prototype.png&quot; alt=&quot;Checklist procedure app prototype&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I remember watching a livestream of a rocket launch and seeing the closeout crew go through a checklist using a tablet strapped to their thigh. I thought, “I’m no NASA engineer, but that looks useful!”&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Interactive jq</title>
   <link href="https://thomascountz.com/2025/01/31/interactive-jq"/>
   <updated>2025-01-31T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2025/01/31/interactive-jq</id>
   <summary type="html">fzf is an incredibly configurable command-line fuzzy finder. Here&apos;s how I used it to build an interactive jq TUI.</summary>
   <content type="html">&lt;p&gt;I built an &lt;a href=&quot;https://gist.github.com/Thomascountz/5ae98a738abb9246b9f7749f53cdddcf&quot;&gt;interactive &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jq&lt;/code&gt; TUI&lt;/a&gt; using &lt;a href=&quot;https://github.com/junegunn/fzf&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fzf&lt;/code&gt;&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/journal/ijq.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I was searching for an interactive jq editor when I came across &lt;a href=&quot;https://github.com/fiatjaf/awesome-jq&quot;&gt;this repo&lt;/a&gt;, which had an intriguing suggestion:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;echo &apos;&apos; | fzf --print-query --preview &quot;cat *.json | jq {q}&quot;&lt;/code&gt; – An fzf hack that turns it into an interactive jq explorer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This sent me down a rabbit hole, and I discovered just how incredibly configurable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fzf&lt;/code&gt; is, e.g.:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You can bind custom keys to execute non-default behaviors:
    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;--bind&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;ctrl-y:execute-silent&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;jq &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;q&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$tempfile&lt;/span&gt; | pbcopy&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;You can start &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fzf&lt;/code&gt; with an initial query:
    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;--query&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;.&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;You can configure &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fzf&lt;/code&gt; with different layouts:
    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;--preview-window&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;top:90%:wrap
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;You can add a multi-line header to provide instructions:
    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;--header&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;$&apos;ctrl+y : copy JSON&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;ctrl+f : copy filter&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;enter : output&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;esc : exit&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wonder how many different TUIs I can create with just &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fzf&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Checkout the code for ijq &lt;a href=&quot;https://gist.github.com/Thomascountz/5ae98a738abb9246b9f7749f53cdddcf&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>ActiveRecord Models to a Mermaid ERD</title>
   <link href="https://thomascountz.com/2025/01/28/activerecord-models-to-erd"/>
   <updated>2025-01-28T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2025/01/28/activerecord-models-to-erd</id>
   <summary type="html">A Ruby script that converts ActiveRecord models into a Mermaid Entity-Relationship Diagram (ERD) format.</summary>
   <content type="html">&lt;p&gt;Turn your ApplicationRecord models into a Mermaid ERD&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;eager_load!&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Instead of all ApplicationRecord descendants, you can&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# make an Array of only the models you care about&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;descendants&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each_with_object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({})&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;model_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;upcase&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;columns: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;columns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sql_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;associations: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;reflect_on_all_associations&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each_with_object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({})&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reflection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;assoc_data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;assoc_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reflection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;klass: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reflection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;upcase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;relationship: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reflection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;macro&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Intermediate step to save the data as JSON&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;data.json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;w&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;mermaid_erd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;erDiagram&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;details&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;columns&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;details&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:columns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;  &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;mermaid_erd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; {&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;columns&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;}&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;details&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;details&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:associations&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;association_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;association_details&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;other_table&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;association_details&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:klass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;association_details&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:relationship&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:belongs_to&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;mermaid_erd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; ||--o{ &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;other_table&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; : &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;association_name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:has_one&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;mermaid_erd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; ||--|| &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;other_table&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; : &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;association_name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:has_many&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;mermaid_erd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; ||--o{ &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;other_table&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; : &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;association_name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;out.mermaid&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;w&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mermaid_erd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Adding Journals</title>
   <link href="https://thomascountz.com/2025/01/20/adding-journals"/>
   <updated>2025-01-20T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2025/01/20/adding-journals</id>
   <summary type="html">Adding journals to my blog for quick thoughts and ideas.</summary>
   <content type="html">&lt;blockquote&gt;
  &lt;p&gt;Update 2026-01-04&lt;/p&gt;

  &lt;p&gt;After trying this out for a while, I’ve found that I added a new, potentially worse, friction: having to ask myself “…is this worth a blog post or should it just be a ‘journal’ entry?”&lt;/p&gt;

  &lt;p&gt;What I think I tried to capture was this concept of a “Twitter-like” impermanence to lower the barrier to sharing. But in practice, I’ve found myself hesitating to write anything at all… Now, “journals” are just regular blog posts with a “journal” tag. This keeps things simple and makes my blog feel calmer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The thing I love most about social media feeds is the immediacy of capturing and sharing thoughts and ideas. Writing on my blog, being built with Jekyll, creates &lt;em&gt;just&lt;/em&gt; enough friction for me to overthink what I’m sharing.&lt;/p&gt;

&lt;p&gt;Inspired by Julia Evans’s &lt;a href=&quot;https://jvns.ca/blog/2024/11/09/new-microblog/&quot;&gt;TIL blog&lt;/a&gt; implementation, I’m hoping a dedicated page and a rake task removes enough friction for me to share more often.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Low-Poly Image Generation using Evolutionary Algorithms in Ruby</title>
   <link href="https://thomascountz.com/2023/07/30/low-poly-image-generation"/>
   <updated>2023-07-30T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2023/07/30/low-poly-image-generation</id>
   <summary type="html">A deep dive into evolutionary algorithms through the lens of low-poly image generation. This exploration covers genetic algorithm theory, custom Ruby implementation using the Petri Dish framework, and detailed analysis of genetic operators including roulette wheel selection, random midpoint crossover, and nudge mutation. Includes comprehensive data analysis of fitness convergence, pixel-level accuracy, and efficiency metrics across 6000+ generations.</summary>
   <content type="html">&lt;p&gt;Inspired by biological systems, evolutionary algorithms model the patterns of multi-generational evolution in order to unearth unique ideas. They work by generating a vast number of potential solutions to a particular problem and then pitting them against each other in a process akin to natural selection: only the fittest survive. In this way, evolutionary algorithms are able to navigate large ambiguous search spaces in order to find solutions to problems that may be difficult or inefficient to solve using other methods.&lt;/p&gt;

&lt;p&gt;These algorithms are used for a wide variety of tasks: from optimizing neural network parameters, evolving mechanical structures, simulating protein folding, and even generating art!&lt;/p&gt;

&lt;p&gt;Today, we will use one such algorithm to create a low-poly version of the Ruby logo, using Ruby&lt;sup id=&quot;fnref:why_ruby&quot;&gt;&lt;a href=&quot;#fn:why_ruby&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;style&gt;
  .slider-container {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    width: 80%;
    margin: 50px auto;
    padding: 20px;
    border: var(--border-thickness) solid var(--text-color);
  }

  .data-container,
  .slideContainer {
    width: 100%;
    margin-bottom: 20px;
  }

  .slideContainer {
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .slideContainer img {
    max-width: 100%;
    height: auto;
    display: block;
    margin: 0 auto;
  }

  .slider {
    width: 100%;
  }

  .generation,
  .fitness,
  .timeInSeconds {
    font-family: var(--font-family);
  }

  .info .label {
    font-family: var(--font-family);
    text-align: right;
  }

  /* Responsive media query for devices with width greater than 768px */
  @media (min-width: 768px) {

    .data-container,
    .slideContainer {
      width: 45%;
    }
  }
&lt;/style&gt;

&lt;div class=&quot;slider-container&quot;&gt;
  &lt;div class=&quot;data-container&quot;&gt;
    &lt;div class=&quot;info&quot;&gt;
      &lt;span class=&quot;label&quot;&gt;Generation:&lt;/span&gt;
      &lt;span class=&quot;generation value&quot;&gt;0000&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;info&quot;&gt;
      &lt;span class=&quot;label&quot;&gt;Max Fitness:&lt;/span&gt;
      &lt;span class=&quot;fitness value&quot;&gt;0078.1640&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;info&quot;&gt;
      &lt;span class=&quot;label&quot;&gt;Time (seconds):&lt;/span&gt;
      &lt;span class=&quot;timeInSeconds value&quot;&gt;0001.03&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;slideContainer&quot;&gt;
    &lt;img class=&quot;slide&quot; src=&quot;/assets/images/low_poly_image_generation/out/gen-0000.png&quot; /&gt;
  &lt;/div&gt;
  &lt;input type=&quot;range&quot; min=&quot;1&quot; max=&quot;6128&quot; value=&quot;1&quot; class=&quot;slider&quot; /&gt;
&lt;/div&gt;

&lt;script&gt;
  var generationNumbers = [&quot;0000&quot;, &quot;0001&quot;, &quot;0002&quot;, &quot;0003&quot;, &quot;0004&quot;, &quot;0006&quot;, &quot;0007&quot;,
    &quot;0008&quot;, &quot;0010&quot;, &quot;0016&quot;, &quot;0019&quot;, &quot;0022&quot;, &quot;0023&quot;, &quot;0029&quot;, &quot;0034&quot;, &quot;0045&quot;, &quot;0046&quot;,
    &quot;0047&quot;, &quot;0048&quot;, &quot;0052&quot;, &quot;0057&quot;, &quot;0058&quot;, &quot;0060&quot;, &quot;0065&quot;, &quot;0070&quot;, &quot;0072&quot;, &quot;0073&quot;,
    &quot;0074&quot;, &quot;0076&quot;, &quot;0078&quot;, &quot;0087&quot;, &quot;0088&quot;, &quot;0108&quot;, &quot;0117&quot;, &quot;0120&quot;, &quot;0132&quot;, &quot;0135&quot;,
    &quot;0137&quot;, &quot;0158&quot;, &quot;0169&quot;, &quot;0176&quot;, &quot;0193&quot;, &quot;0224&quot;, &quot;0233&quot;, &quot;0254&quot;, &quot;0256&quot;, &quot;0277&quot;,
    &quot;0288&quot;, &quot;0294&quot;, &quot;0308&quot;, &quot;0358&quot;, &quot;0361&quot;, &quot;0378&quot;, &quot;0422&quot;, &quot;0429&quot;, &quot;0454&quot;, &quot;0510&quot;,
    &quot;0549&quot;, &quot;0634&quot;, &quot;0682&quot;, &quot;0708&quot;, &quot;0746&quot;, &quot;0779&quot;, &quot;0835&quot;, &quot;0851&quot;, &quot;0857&quot;, &quot;0860&quot;,
    &quot;0906&quot;, &quot;0984&quot;, &quot;1045&quot;, &quot;1102&quot;, &quot;1131&quot;, &quot;1180&quot;, &quot;1223&quot;, &quot;1241&quot;, &quot;1245&quot;, &quot;1248&quot;,
    &quot;1265&quot;, &quot;1268&quot;, &quot;1544&quot;, &quot;1558&quot;, &quot;1563&quot;, &quot;1568&quot;, &quot;1700&quot;, &quot;1855&quot;, &quot;1877&quot;, &quot;1897&quot;,
    &quot;1944&quot;, &quot;2194&quot;, &quot;2233&quot;, &quot;2236&quot;, &quot;2241&quot;, &quot;2358&quot;, &quot;2389&quot;, &quot;2461&quot;, &quot;2548&quot;, &quot;2619&quot;,
    &quot;2625&quot;, &quot;2739&quot;, &quot;2740&quot;, &quot;3194&quot;, &quot;3223&quot;, &quot;3826&quot;, &quot;3988&quot;, &quot;4016&quot;, &quot;4069&quot;, &quot;4189&quot;,
    &quot;4308&quot;, &quot;4332&quot;, &quot;4440&quot;, &quot;4684&quot;, &quot;4924&quot;, &quot;5081&quot;, &quot;5143&quot;, &quot;5170&quot;, &quot;5405&quot;, &quot;5433&quot;,
    &quot;5475&quot;, &quot;5560&quot;, &quot;5702&quot;, &quot;6124&quot;, &quot;6198&quot;];

  var fitnessNumbers = [&quot;0078.1640&quot;, &quot;0085.2235&quot;, &quot;0095.8818&quot;, &quot;0100.9292&quot;,
    &quot;0102.3121&quot;, &quot;0116.5887&quot;, &quot;0116.8058&quot;, &quot;0133.0203&quot;, &quot;0167.9346&quot;, &quot;0180.1719&quot;,
    &quot;0193.6908&quot;, &quot;0202.7385&quot;, &quot;0207.6398&quot;, &quot;0215.2024&quot;, &quot;0218.1709&quot;, &quot;0221.1855&quot;,
    &quot;0224.5201&quot;, &quot;0226.6439&quot;, &quot;0232.1746&quot;, &quot;0238.1714&quot;, &quot;0239.7016&quot;, &quot;0241.8438&quot;,
    &quot;0259.5971&quot;, &quot;0287.2607&quot;, &quot;0289.9245&quot;, &quot;0291.3394&quot;, &quot;0292.9619&quot;, &quot;0299.4971&quot;,
    &quot;0321.9478&quot;, &quot;0372.8176&quot;, &quot;0381.7194&quot;, &quot;0410.0227&quot;, &quot;0436.2229&quot;, &quot;0445.3775&quot;,
    &quot;0447.8833&quot;, &quot;0472.2888&quot;, &quot;0475.7925&quot;, &quot;0491.0803&quot;, &quot;0501.8061&quot;, &quot;0514.5392&quot;,
    &quot;0531.9147&quot;, &quot;0551.1477&quot;, &quot;0562.5769&quot;, &quot;0602.7261&quot;, &quot;0606.0258&quot;, &quot;0610.2664&quot;,
    &quot;0617.5342&quot;, &quot;0670.1177&quot;, &quot;0679.4658&quot;, &quot;0750.4697&quot;, &quot;0772.4439&quot;, &quot;0776.8642&quot;,
    &quot;0798.1546&quot;, &quot;0818.3324&quot;, &quot;0853.8499&quot;, &quot;0921.0733&quot;, &quot;0926.0791&quot;, &quot;0950.5562&quot;,
    &quot;0992.6206&quot;, &quot;1005.5427&quot;, &quot;1037.8663&quot;, &quot;1047.9896&quot;, &quot;1064.7530&quot;, &quot;1067.0050&quot;,
    &quot;1071.8805&quot;, &quot;1078.2098&quot;, &quot;1109.0748&quot;, &quot;1111.9042&quot;, &quot;1142.6594&quot;, &quot;1148.4606&quot;,
    &quot;1188.2155&quot;, &quot;1212.7286&quot;, &quot;1220.7054&quot;, &quot;1250.6114&quot;, &quot;1291.7208&quot;, &quot;1356.6018&quot;,
    &quot;1389.3510&quot;, &quot;1389.8488&quot;, &quot;1403.6130&quot;, &quot;1421.7452&quot;, &quot;1433.7339&quot;, &quot;1469.8056&quot;,
    &quot;1489.1271&quot;, &quot;1517.6637&quot;, &quot;1537.4407&quot;, &quot;1564.9784&quot;, &quot;1636.9248&quot;, &quot;1656.3737&quot;,
    &quot;1700.7393&quot;, &quot;1701.7908&quot;, &quot;1722.0287&quot;, &quot;1769.4364&quot;, &quot;1780.9038&quot;, &quot;1798.1664&quot;,
    &quot;1818.8158&quot;, &quot;1819.6638&quot;, &quot;1846.3436&quot;, &quot;1900.1823&quot;, &quot;1949.9552&quot;, &quot;2005.7937&quot;,
    &quot;2083.7737&quot;, &quot;2095.7519&quot;, &quot;2117.4079&quot;, &quot;2144.7930&quot;, &quot;2154.2111&quot;, &quot;2173.4852&quot;,
    &quot;2187.9873&quot;, &quot;2239.0811&quot;, &quot;2281.1902&quot;, &quot;2430.6313&quot;, &quot;2533.8661&quot;, &quot;2550.3649&quot;,
    &quot;2553.3816&quot;, &quot;2574.6566&quot;, &quot;2637.9835&quot;, &quot;2670.8562&quot;, &quot;2709.4564&quot;, &quot;2717.7863&quot;,
    &quot;2750.0546&quot;, &quot;2820.5955&quot;, &quot;2856.9516&quot;, &quot;2857.0822&quot;];

  var timeInSeconds = [&quot;0001.03&quot;, &quot;0001.40&quot;, &quot;0001.97&quot;, &quot;0002.66&quot;, &quot;0003.16&quot;,
    &quot;0004.18&quot;, &quot;0004.85&quot;, &quot;0005.44&quot;, &quot;0006.39&quot;, &quot;0009.99&quot;, &quot;0011.67&quot;, &quot;0013.16&quot;,
    &quot;0013.93&quot;, &quot;0017.01&quot;, &quot;0019.70&quot;, &quot;0026.67&quot;, &quot;0027.66&quot;, &quot;0028.05&quot;, &quot;0028.52&quot;,
    &quot;0031.26&quot;, &quot;0034.63&quot;, &quot;0034.95&quot;, &quot;0036.48&quot;, &quot;0040.78&quot;, &quot;0044.12&quot;, &quot;0045.81&quot;,
    &quot;0046.65&quot;, &quot;0048.06&quot;, &quot;0049.25&quot;, &quot;0050.18&quot;, &quot;0055.43&quot;, &quot;0055.84&quot;, &quot;0066.97&quot;,
    &quot;0072.30&quot;, &quot;0073.74&quot;, &quot;0082.65&quot;, &quot;0084.22&quot;, &quot;0086.36&quot;, &quot;0100.19&quot;, &quot;0107.77&quot;,
    &quot;0111.88&quot;, &quot;0121.47&quot;, &quot;0139.02&quot;, &quot;0144.80&quot;, &quot;0156.88&quot;, &quot;0158.06&quot;, &quot;0168.81&quot;,
    &quot;0174.91&quot;, &quot;0178.13&quot;, &quot;0185.46&quot;, &quot;0211.94&quot;, &quot;0213.32&quot;, &quot;0222.54&quot;, &quot;0246.35&quot;,
    &quot;0250.11&quot;, &quot;0262.99&quot;, &quot;0292.75&quot;, &quot;0313.79&quot;, &quot;0359.18&quot;, &quot;0385.10&quot;, &quot;0398.50&quot;,
    &quot;0419.48&quot;, &quot;0437.47&quot;, &quot;0468.35&quot;, &quot;0476.74&quot;, &quot;0480.18&quot;, &quot;0481.41&quot;, &quot;0507.88&quot;,
    &quot;0551.72&quot;, &quot;0586.78&quot;, &quot;0619.17&quot;, &quot;0635.71&quot;, &quot;0663.73&quot;, &quot;0687.61&quot;, &quot;0697.98&quot;,
    &quot;0700.16&quot;, &quot;0701.72&quot;, &quot;0711.44&quot;, &quot;0713.43&quot;, &quot;0866.50&quot;, &quot;0874.18&quot;, &quot;0876.86&quot;,
    &quot;0879.65&quot;, &quot;0950.00&quot;, &quot;1032.75&quot;, &quot;1044.77&quot;, &quot;1057.39&quot;, &quot;1085.01&quot;, &quot;1241.79&quot;,
    &quot;1265.46&quot;, &quot;1267.05&quot;, &quot;1270.09&quot;, &quot;1340.66&quot;, &quot;1359.74&quot;, &quot;1403.06&quot;, &quot;1455.77&quot;,
    &quot;1498.74&quot;, &quot;1502.63&quot;, &quot;1573.88&quot;, &quot;1574.21&quot;, &quot;1855.07&quot;, &quot;1872.98&quot;, &quot;2251.57&quot;,
    &quot;2360.59&quot;, &quot;2379.96&quot;, &quot;2416.47&quot;, &quot;2496.00&quot;, &quot;2579.31&quot;, &quot;2596.08&quot;, &quot;2676.66&quot;,
    &quot;2859.12&quot;, &quot;3037.47&quot;, &quot;3142.06&quot;, &quot;3188.27&quot;, &quot;3207.87&quot;, &quot;3378.84&quot;, &quot;3399.71&quot;,
    &quot;3432.90&quot;, &quot;3495.90&quot;, &quot;3602.76&quot;, &quot;3919.26&quot;, &quot;3972.11&quot;];

  var generations = generationNumbers.map(num =&gt; {
    var img = new Image();
    img.src = `/assets/images/low_poly_image_generation/out/gen-${num}.png`;
    return img;
  });

  document.querySelectorAll(&apos;.slider-container&apos;).forEach(container =&gt; {
    var slider = container.querySelector(&apos;.slider&apos;);
    var slide = container.querySelector(&apos;.slide&apos;);
    var generation = container.querySelector(&apos;.generation&apos;);
    var fitness = container.querySelector(&apos;.fitness&apos;);
    var timeInSecondsElem = container.querySelector(&apos;.timeInSeconds&apos;);

    slider.max = generations.length;

    slider.oninput = function () {
      slide.src = generations[this.value - 1].src;
      var genNumber = generationNumbers[this.value - 1];
      var fitNumber = fitnessNumbers[this.value - 1];
      var timeInSecondsNumber = timeInSeconds[this.value - 1];
      generation.textContent = genNumber;
      fitness.textContent = fitNumber;
      timeInSecondsElem.textContent = timeInSecondsNumber;
    };
  });
&lt;/script&gt;

&lt;p&gt;In this post, we’re going to be using the &lt;a href=&quot;https://github.com/thomascountz/petri_dish&quot;&gt;petri_dish_lab&lt;/a&gt; gem for the task of &lt;em&gt;image reconstruction&lt;/em&gt;. Image reconstruction is a great example to use because we’ll be able to visualize the evolutionary process working over time.&lt;/p&gt;

&lt;p&gt;We’ll begin by going over what an evolutionary algorithm is and how it works. Then, we’ll take a look at the Petri Dish framework and how we can use it to implement an evolutionary algorithm. Next, we’ll define the objective of our image reconstruction problem and how we can represent it in code. Then, we’ll define the genetic operators that we’ll use to evolve the population. Finally, we’ll put it all together and see the results!&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;🧑‍💻 &lt;a href=&quot;https://github.com/thomascountz/petri_dish&quot;&gt;&lt;strong&gt;Code Here&lt;/strong&gt;&lt;/a&gt;&lt;br /&gt;
The complete code for this blog post can be found as an example in the &lt;a href=&quot;https://github.com/Thomascountz/petri_dish/tree/main/examples/low_poly_reconstruction&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;examples/low_poly_image_reconstruction&lt;/code&gt;&lt;/a&gt; directory of the &lt;a href=&quot;https://github.com/thomascountz/petri_dish&quot;&gt;Petri Dish&lt;/a&gt; repository.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;🔷 &lt;strong&gt;What is a Low-Poly Image?&lt;/strong&gt;&lt;br /&gt;
A Low-poly image is a type of 2-D image that’s created by a series of variously sized polygons. Usually, each polygon shares at least one edge with another, and they do not overlap. By using different colors and varying the total polygon count, the result is a low-resolution version of an image.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h1 id=&quot;table-of-contents-&quot;&gt;Table of Contents &lt;!-- omit in toc --&gt;&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#evolutionary-algorithm-overview&quot;&gt;Evolutionary Algorithm Overview&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#the-petri-dish-framework&quot;&gt;The Petri Dish Framework&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#understanding-the-objective&quot;&gt;Understanding the Objective&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#member-representation&quot;&gt;Member Representation&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#input-target-image&quot;&gt;Input &lt;em&gt;Target&lt;/em&gt; Image&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#member-genes&quot;&gt;Member Genes&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#reconstructed-output-image&quot;&gt;&lt;em&gt;Reconstructed&lt;/em&gt; Output Image&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#population-initialization&quot;&gt;Population Initialization&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#fitness-function&quot;&gt;Fitness Function&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#genetic-operators&quot;&gt;Genetic Operators&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#parent-selection-function&quot;&gt;Parent Selection Function&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#crossover-function&quot;&gt;Crossover Function&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#mutation-function&quot;&gt;Mutation Function&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#replacement&quot;&gt;Replacement&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#end-condition&quot;&gt;End Condition&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#putting-it-together&quot;&gt;Putting it Together&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#final-configuration&quot;&gt;Final Configuration&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#results&quot;&gt;Results&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#subjective-analysis&quot;&gt;Subjective Analysis&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#data-analysis-and-interpretation&quot;&gt;Data Analysis and Interpretation&lt;/a&gt;
        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#log-data&quot;&gt;Log Data&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#image-data&quot;&gt;Image Data&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#footnotes&quot;&gt;Footnotes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h1 id=&quot;evolutionary-algorithm-overview&quot;&gt;Evolutionary Algorithm Overview&lt;/h1&gt;

&lt;p&gt;Evolutionary algorithms are optimization algorithms that “search” through an expansive space of potential solutions. The goal of a search algorithm is to find a solution that satisfies some criteria, or &lt;em&gt;objective&lt;/em&gt;. Evolutionary algorithms are particularly good in cases where we want a solution that is “good enough,” rather than an absolute optimum. In our case, we want to find a set of polygon vertices and grayscale values that, when combined, approximate a given image. For this usecase, there’s no one “best” solution, so evolutionary algorithms are a good fit.&lt;/p&gt;

&lt;p&gt;The image reconstruction process begins by creating a bunch of images with random polygons. We’re going to use triangles, but we could technically use any shape, and we’ll stick to grayscale colors in order to make the problem a bit simpler. This collection of images is called the &lt;em&gt;population&lt;/em&gt;, and each image is called a &lt;em&gt;member&lt;/em&gt; of the population.&lt;/p&gt;

&lt;p&gt;Once we have a bunch of random guesses, we compare each member to the target image, pixel-by-pixel, to determine their likeness to the target. This &lt;em&gt;measure of likeness&lt;/em&gt; is what we refer to as their &lt;em&gt;fitness&lt;/em&gt;, and will be determined by how close each pixel is to the correct grayscale value.&lt;/p&gt;

&lt;p&gt;Based on their fitness, &lt;em&gt;parent&lt;/em&gt; members are then chosen and combined to create a new &lt;em&gt;child&lt;/em&gt; member in a process called &lt;em&gt;crossover&lt;/em&gt;. The way we select and crossover parent members is by using &lt;em&gt;selection&lt;/em&gt; and &lt;em&gt;crossover&lt;/em&gt; functions, and we cover these function (along with &lt;em&gt;mutation&lt;/em&gt; functions, which mirror the biological process of genetic mutation by injecting random changes into the child member) later. Together, these functions are called the &lt;em&gt;genetic operators&lt;/em&gt;. The exact implementation of these operators is perhaps the most important part of developing an evolutionary algorithm, and we’ll spend a lot of time on them later. If you’re familiar with machine learning, you can think of the genetic operators as &lt;em&gt;hyperparameters&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;And, just like in biological systems, the process of parent selection, child creation, and mutation repeats and repeats until we have enough new members to fill a new &lt;em&gt;population&lt;/em&gt;. If any of the new members meet our objective, we’re done! Otherwise, we’ll repeat this entire process, known as a &lt;em&gt;generation&lt;/em&gt;, until we find a member that meets the objective, or we otherwise get close enough.&lt;/p&gt;

&lt;p&gt;An evolutionary algorithm configured in this way is called a &lt;em&gt;genetic algorithm&lt;/em&gt;. There are other types of evolutionary algorithms, but they all share a similar recursive structure, shown here in Ruby-esque code:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;population&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[])&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# If population is empty or not provided, create a new population&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;population&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;POPULATION_SIZE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create_random_member&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;population&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;empty?&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Try to find a member of the population that meets the objective&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;best_member&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;population&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;member&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;objective_met?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# If a member meeting the objective is found, return it&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;best_member&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;best_member&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Otherwise, create a new generation and recursively search again&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;new_population&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;POPULATION_SIZE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;parents&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;select_parents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;population&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;child&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;crossover&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;mutate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;child&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_population&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;🔄 &lt;strong&gt;Tail Call Recursion&lt;/strong&gt;&lt;br /&gt;
Notice that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;run&lt;/code&gt; method above is recursive. Recursion isn’t always the most efficient way to implement an algorithm because each recursive call in Ruby creates a new stack frame, which is a chunk of memory that stores the state of the method. Depending on how many generations we need our algorithm to run, this could cause an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SystemStackError (stack level too deep)&lt;/code&gt; error. However, when a recursive call is the last thing that happens in a method (a method returns a call to itself), we can avoid allocating new stack frames by using a technique called &lt;em&gt;tail call optimization&lt;/em&gt;. In Ruby, this is not enabled by default, but we can enable it using the following compiler options:&lt;/p&gt;
  &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;RubyVM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;InstructionSequence&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;compile_option&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;tailcall_optimization: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;trace_instruction: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;p&gt;This prevents the stack from growing indefinitely by reusing the same stack frame for each recursive call, rather than creating a new one.&lt;sup id=&quot;fnref:ruby_tail_call_optimization&quot;&gt;&lt;a href=&quot;#fn:ruby_tail_call_optimization&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;the-petri-dish-framework&quot;&gt;The Petri Dish Framework&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/thomascountz/petri_dish&quot;&gt;Petri Dish&lt;/a&gt; framework is a Ruby gem that implements the evolutionary algorithm structure for us in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PetriDish::World.run&lt;/code&gt; method. We need to supply the members of the population, how to evaluate their fitness, the genetic operators, and when to stop. These specifics of an evolutionary algorithm are highly dependent on the problem we’re trying to solve, but the underlying structure is always the same.&lt;/p&gt;

&lt;p&gt;Here’s what a stripped down version of what the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PetriDish::World.run&lt;/code&gt; method looks like. See if you can spot the similarities to the pseudocode above:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;PetriDish&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;World&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;attr_accessor&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:metadata&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;attr_reader&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:end_condition_reached&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;members&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;configuration: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;metadata: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;end_condition_reached&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;max_generation_reached&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;max_generation_reached&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;generation_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;max_generations&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;new_members&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;population_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;parents&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parents_selection_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;members&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;child_member&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;crossover_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mutation_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;child_member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tap&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mutated_child&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;end_condition_reached&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;end_condition_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mutated_child&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;increment_generation&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end_condition_reached&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max_generation_reached&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;members: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_members&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;configuration: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;metadata: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PetriDish::World.run&lt;/code&gt; method accepts a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;members&lt;/code&gt; Array, which contains the evolving population; a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;configuration&lt;/code&gt; object, which holds the user-defined genetic operators and other configuration options; and an internally used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;metadata&lt;/code&gt; object, which holds information about the current state of the algorithm, like the current generation number.&lt;/p&gt;

&lt;p&gt;The Petri Dish framework exposes the &lt;a href=&quot;https://github.com/Thomascountz/petri_dish/blob/133be9efea42e7e3f62e01cd92a77ba03425afa4/lib/petri_dish/configuration.rb#L18-L22&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PetriDish::Configuration#configure&lt;/code&gt;&lt;/a&gt; method which takes a block of configuration options.&lt;/p&gt;

&lt;p&gt;The core configuration options that we’re interested in for this post are:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Parameter&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fitness_function&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;A function used to calculate the fitness of an individual&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parents_selection_function&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;A function used to select parents for crossover&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crossover_function&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;A function used to perform crossover between two parents&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mutation_function&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;A function used to mutate the genes of an individual&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mutation_rate&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The chance that a gene will change during mutation&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;max_generations&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The maximum number of generations to run the evolution for&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;end_condition_function&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;A function that determines whether the evolution process should stop premature of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;max_generations&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;We’ll take a look at each of these functions in detail later, but for now, let’s take a step back and define the objective of our problem.&lt;/p&gt;

&lt;h1 id=&quot;understanding-the-objective&quot;&gt;Understanding the Objective&lt;/h1&gt;

&lt;p&gt;As mentioned earlier, evolutionary algorithms work best when we define a criteria to aim towards, versus a discrete target. In our case, we are trying to replicate a given grayscale image using triangles. If we break this down in terms of drawing polygons, we want to &lt;strong&gt;find a set of triangles that, when drawn together, approximate the given image&lt;/strong&gt;. That is our &lt;em&gt;objective&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;We’ll define the triangles by their three vertices. &lt;strong&gt;Each vertex can be encoded as an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(x,y)&lt;/code&gt; coordinate and a grayscale value.&lt;/strong&gt; These are our &lt;em&gt;decision variables&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A series of these three-vertices-and-a-grayscale-value groupings is all the information we need create a low-poly image. We’ll call these groupings “points” and an Array of them will represent each members’ &lt;em&gt;genes&lt;/em&gt;. Each member of the population will have a distinct set of these points and the algorithm will optimize for a member whose points, or genes, are more &lt;em&gt;fit&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;☝️ &lt;strong&gt;Why Points instead of Triangles?&lt;/strong&gt;&lt;br /&gt;
As we’ll see later, we’ll be using what’s called &lt;a href=&quot;https://en.wikipedia.org/wiki/Delaunay_triangulation&quot;&gt;&lt;em&gt;Delaunay triangulation&lt;/em&gt;&lt;/a&gt; to create triangles from a member’s points/genes, rather than define triangles directly. Delaunay Triangulation is a technique that takes a set of points and uses them as vertices to creates a set of non-overlapping triangles. This is a creative decision as much as it is a technical one. I began by evolving triangles directly, but found that I didn’t like the results as much as when I evolved points.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Lastly, we need to define our &lt;em&gt;constraints&lt;/em&gt;, or the boundaries of the search space. In our case (mostly due to the arbitrary limit of my laptop’s processing power), &lt;strong&gt;we will limit the height and width of the image to 100x100 pixels and the color space to 8-bit grayscale&lt;/strong&gt;. Independently, &lt;strong&gt;we will also limit each members’ genes to be 100 points&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;🗺️ &lt;strong&gt;Quantifying the Search Space&lt;/strong&gt;&lt;br /&gt;
With each point each having an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(x,y)&lt;/code&gt; coordinate range of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(0..100, 0..100)&lt;/code&gt; and a grayscale value range of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0..255&lt;/code&gt;, each point can be in one of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;101 * 101 * 256 = 2,626,816&lt;/code&gt; possible states (roughly speaking). And, since each member has 100 of these as its genes, this leads to a search space of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(101 * 101 * 256)**100&lt;/code&gt; dimensions, which is an enormously large, but in practice, quite small for an evolutionary algorithm.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now that we’ve taken a look at the objective, decision variables, and constraints at a high level, let’s take a look at how we can represent them in code.&lt;/p&gt;

&lt;p&gt;To recap:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Objective&lt;/strong&gt;: Find a set of non-overlapping triangles that approximate the given image&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Decision Variables&lt;/strong&gt;: Each vertex can be encoded as an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(x,y)&lt;/code&gt; coordinate and a grayscale value&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Constraints&lt;/strong&gt;: 100x100 pixel image, 8-bit grayscale, 100 points&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;member-representation&quot;&gt;Member Representation&lt;/h1&gt;

&lt;p&gt;Evolutionary algorithms are generic in the sense that they can be applied to a wide variety of problems. Therefore, just as we would for a neural network, we must engineer, or encode, our problem into a format that can be understood by the algorithm’s architecture.&lt;/p&gt;

&lt;p&gt;We need to encode:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;#input-target-image&quot;&gt;The input &lt;em&gt;target&lt;/em&gt; image&lt;/a&gt;,&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#member-genes&quot;&gt;The &lt;em&gt;genes&lt;/em&gt; of each member of the population&lt;/a&gt;, and&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#reconstructed-output-image&quot;&gt;The &lt;em&gt;reconstructed&lt;/em&gt; output image&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;input-target-image&quot;&gt;Input &lt;em&gt;Target&lt;/em&gt; Image&lt;/h2&gt;

&lt;p&gt;For both the input and output images, we’ll use the &lt;a href=&quot;https://github.com/rmagick/rmagick&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Magick&lt;/code&gt;&lt;/a&gt; gem which provides a Ruby interface to the &lt;a href=&quot;https://imagemagick.org/&quot;&gt;ImageMagick&lt;/a&gt; image processing library.&lt;/p&gt;

&lt;p&gt;Starting with an input image path, we can read the image into memory, center crop it to a 100x100 pixel square, and then convert it to grayscale. We’ll save the image to a file for later comparison.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;rmagick&apos;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;import_target_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;output_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;image&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Magick&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Calculate crop size and coordinates for center crop&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;crop_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;columns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;min&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;crop_x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;columns&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;crop_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;crop_y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rows&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;crop_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;crop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;crop_x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;crop_y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;crop_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;crop_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;resize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;IMAGE_HEIGHT_PX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IMAGE_WIDTH_PX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;quantize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;256&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Magick&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;GRAYColorspace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s import the Ruby logo and see what it looks like:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;IMAGE_WIDTH_PX&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;IMAGE_HEIGHT_PX&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;target_image&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;import_target_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ruby_logo.png&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ruby_logo_grayscale.png&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div style=&quot;text-align:center;padding:20px;border:1px solid gray;&quot;&gt;
  &lt;img style=&quot;border: 1px solid gray&quot; src=&quot;/assets/images/low_poly_image_generation/ruby_logo_grayscale.png&quot; /&gt;
  &lt;figcaption&gt;100x100 pixel grayscale version of the Ruby logo: our target image.&lt;/figcaption&gt;
&lt;/div&gt;

&lt;p&gt;Using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Magick::Image&lt;/code&gt; class to represent our target image is arbitrary, but the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rmagick&lt;/code&gt; library provides a convenient interface for reading, writing, and importantly, comparing images pixel-by-pixel. (We’ll use this later to calculate the fitness of each member).&lt;/p&gt;

&lt;h2 id=&quot;member-genes&quot;&gt;Member Genes&lt;/h2&gt;

&lt;p&gt;When defining of our objective, we identified that the genes of each member can be represented as an Array of 100 points, each with an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(x,y)&lt;/code&gt; coordinate and a grayscale value that defines the vertices of triangles. We can encode this using a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Point&lt;/code&gt; Struct with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grayscale&lt;/code&gt; attributes.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Point&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Struct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:grayscale&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;📐 &lt;strong&gt;Why a Struct?&lt;/strong&gt;&lt;br /&gt;
A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Struct&lt;/code&gt; is a simple way to define a class with attributes. It’s a good choice here because we do not need to define any behavior on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Point&lt;/code&gt;-s; it is a data-only object. Based on my benchmarks&lt;sup id=&quot;fnref:struct_benchmarks&quot;&gt;&lt;a href=&quot;#fn:struct_benchmarks&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;, a hash would be more performant, though it’s less flexible and less explicit. A class or Ruby 3.2 &lt;a href=&quot;https://docs.ruby-lang.org/en/3.2/Data.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Data&lt;/code&gt;&lt;/a&gt; object offer no additional benefits.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- TODO: Add benchmarks --&gt;

&lt;blockquote&gt;
  &lt;p&gt;🌈 &lt;strong&gt;Why does a Point have color?&lt;/strong&gt;&lt;br /&gt;
Notice here that a the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grayscale&lt;/code&gt; attribute is defined on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Point&lt;/code&gt; Struct and not on some representation of a triangle. We’ll need a grayscale value to fill each triangle, but a triangle will be made up of three &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Point&lt;/code&gt;-s. So, which of the three &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Point&lt;/code&gt;-s’ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grayscale&lt;/code&gt; attribute do we use? We will average them. This is because, as we will see, we aren’t sure which three &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Point&lt;/code&gt;-s will be used together as the vertices of any particular triangle. Therefore, for any given triangle, we’ll average the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grayscale&lt;/code&gt; values of its three &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Point&lt;/code&gt;-s together to determine the fill color. This is only one approach to this problem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now that we have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Point&lt;/code&gt;, we can create an Array of them to form a member’s genes.&lt;/p&gt;

&lt;p&gt;The Petri Dish framework provides a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PetriDish::Member&lt;/code&gt; class, which acts as an interface to the library. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PetriDish::Member&lt;/code&gt; class holds onto the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;genes&lt;/code&gt; Array, which is then directly exposed via the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PetriDish::Member#genes&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;Here is the entire definition of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PetriDish::Member&lt;/code&gt; class:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;PetriDish&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Member&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;attr_reader&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:genes&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;genes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fitness_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
      &lt;span class=&quot;vi&quot;&gt;@fitness_function&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fitness_function&lt;/span&gt;
      &lt;span class=&quot;vi&quot;&gt;@genes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;genes&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fitness&lt;/span&gt;
      &lt;span class=&quot;vi&quot;&gt;@fitness&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@fitness_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The initializer also accepts a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fitness_function&lt;/code&gt; which is used to evaluate the fitness of a member. (We’ll define the fitness function later, but for now, it only needs to respond to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#call&lt;/code&gt; and take a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PetriDish::Member&lt;/code&gt; as its only argument).&lt;/p&gt;

&lt;p&gt;To create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PetriDish::Member&lt;/code&gt; with an Array of 100 random &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Point&lt;/code&gt;-s as its genes, we do the following:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;GRAYSCALE_RANGE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;random_member&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;genes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;IMAGE_WIDTH_PX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;IMAGE_HEIGHT_PX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;GRAYSCALE_RANGE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;no&quot;&gt;PetriDish&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;genes: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;genes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;fitness_function: &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;random_member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;genes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sample&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#&amp;lt;struct Point x=57, y=21, grayscale=235&amp;gt;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#&amp;lt;struct Point x=16, y=49, grayscale=64&amp;gt;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#&amp;lt;struct Point x=68, y=6, grayscale=210&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;reconstructed-output-image&quot;&gt;&lt;em&gt;Reconstructed&lt;/em&gt; Output Image&lt;/h2&gt;

&lt;p&gt;To turn a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PetriDish::Member&lt;/code&gt; into a low-poly image, we’ll create triangles using its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#genes&lt;/code&gt; in a process called &lt;a href=&quot;https://en.wikipedia.org/wiki/Delaunay_triangulation&quot;&gt;&lt;em&gt;Delaunay triangulation&lt;/em&gt;&lt;/a&gt;. This technique takes a set of points and creates a set of triangles such that no point is inside the circumcircle of any triangle, i.e. no triangle overlaps another. The &lt;a href=&quot;https://github.com/hendrixfan/delaunator-ruby/tree/master&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;delaunator&lt;/code&gt;&lt;/a&gt; gem is a Ruby library that performs Delaunay triangulation, and we’ll use it here instead of implementing it ourselves.&lt;/p&gt;

&lt;p&gt;Let’s define a function called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;member_to_image&lt;/code&gt; that takes a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PetriDish::Member&lt;/code&gt; and returns an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Magick::Image&lt;/code&gt; object based on the member’s genes (while also grossly violating the single-responsibility principle).&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;We begin by initializing a new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Magick::Image&lt;/code&gt; object, which is the same object type as our input image.&lt;/li&gt;
  &lt;li&gt;We then perform the Delaunay triangulation using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Delaunator.triangulate&lt;/code&gt; method and passing in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y&lt;/code&gt; values from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Point&lt;/code&gt;-s returned from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PetriDish::Member#genes&lt;/code&gt; method.&lt;/li&gt;
  &lt;li&gt;To determine the grayscale fill value, we use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grayscale&lt;/code&gt; value from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PetriDish::Member#genes&lt;/code&gt;, and average them, as we discussed earlier.&lt;/li&gt;
  &lt;li&gt;Finally, we draw each triangle onto the image using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Magick::Draw#polygon&lt;/code&gt; method.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;petri_dish&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;delaunator&apos;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;member_to_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Create a new image with a white background&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;image&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Magick&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;IMAGE_WIDTH_PX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IMAGE_HEIGHT_PX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;background_color&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;white&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Create a new draw object to draw onto the image&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;draw&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Magick&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Draw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Perform Delaunay triangulation on the points&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Delaunator.triangulate accepts a nested array of [[x1, y1], [xN, yN]]&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# coordinates and returns an array of triangle vertex indices where each&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# group of three numbers forms a triangle&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;triangles&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Delaunator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;triangulate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;genes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;triangles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each_slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Get the vertices of the triangle&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;triangle_points&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;genes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;values_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Use the average color from all three points as the fill color&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;color&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;triangle_points&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:grayscale&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# The `Magick::Draw#fill` method accepts a string representing a color in the form &quot;rgb(r, g, b)&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;draw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;rgb(&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Magick::Image#draw takes an array of vertices in the form [x1, y1,..., xN, yN]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;vertices&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;triangle_points&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;draw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;polygon&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vertices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;flatten&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;draw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;draw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s see what the result looks like if we run it a few times:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;member_to_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random_member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;member&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.png&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div style=&quot;text-align:center; margin: 10px auto 10px auto;&quot;&gt;
  &lt;img style=&quot;border:1px solid black&quot; src=&quot;/assets/images/low_poly_image_generation/random_members.png&quot; /&gt;
  &lt;figcaption&gt;Five random members of the population.&lt;/figcaption&gt;
&lt;/div&gt;

&lt;p&gt;Here, we can see the Delaunay triangulation in action. Each member has a different set of points, and therefore a different set of triangles. We can also see that the grayscale values are being averaged together to determine the fill color of each triangle.&lt;/p&gt;

&lt;p&gt;When we use an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Magick::Image&lt;/code&gt; object to represent a member of the population, we do so to easily compare it to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Magick::Image&lt;/code&gt; target object and visualize the results from the algorithm.&lt;/p&gt;

&lt;p&gt;When we use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Petridish::Member&lt;/code&gt; object to represent the same member, we do so to enable the algorithm to easily calculate and evolve new members.&lt;/p&gt;

&lt;p&gt;This type of data engineering, akin to &lt;em&gt;feature engineering&lt;/em&gt; in machine learning, is a critical step in the process of developing an evolutionary algorithm. The way we encode the problem and represent potential solutions has a large impact on the performance of the algorithm and our ability to interpret its output.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;⬜️ &lt;strong&gt;Why the White Background?&lt;/strong&gt;&lt;br /&gt;
When we create a new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Magick::Image&lt;/code&gt; object to represent a member in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;member_to_image&lt;/code&gt; method, we opt for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;white&lt;/code&gt; as the background color. This choice, although arbitrary, influences the performance of the algorithm. For instance, with the grayscale Ruby logo, whose background is largely white, starting with a white background might expedite the convergence on a solution. However, hardcoding this value may bias the algorithm towards reconstructing targets with white backgrounds. The best solution would be to make this choice configurable in order to make this parameter tunable. This is a good example of how the smallest details in the way we model a problem can impact the performance of the algorithm.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1 id=&quot;population-initialization&quot;&gt;Population Initialization&lt;/h1&gt;

&lt;p&gt;Often it is the case that completely random population initialization is a good place to start, but we can sometimes quickly see faster results if seed the population with some prior knowledge; similar to choosing a good initial set of weights for a neural network.&lt;/p&gt;

&lt;p&gt;In the case of image reconstruction, I found that random initialization lead to a lot of waste in the early generations on distributing the points across the image dimensions. To avoid this, we can instead evenly distribute the points across the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y&lt;/code&gt; dimensions of the image and then randomly assign a grayscale value to each point.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;init_member&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;PetriDish&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;genes: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;IMAGE_WIDTH_PX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
              &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;IMAGE_HEIGHT_PX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
                &lt;span class=&quot;no&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;GRAYSCALE_VALUES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sample&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
              &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;flatten&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;fitness_function: &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Let’s see what the result looks like if we run this method a few times:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;member_to_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;init_member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;member&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.png&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div style=&quot;text-align:center; margin: 10px auto 10px auto;&quot;&gt;
  &lt;img style=&quot;border:1px solid black&quot; src=&quot;/assets/images/low_poly_image_generation/init_members.png&quot; /&gt;
  &lt;figcaption&gt;Five non-random members of the population.&lt;/figcaption&gt;
&lt;/div&gt;

&lt;p&gt;We can see that the points are evenly distributed across the image dimensions, and that the grayscale values are randomly assigned. Whether or not this is a better starting point than a completely random initialization is a question of experimentation. In the case of image reconstruction and other generative tasks, a random initialization may be better because it allows the algorithm to explore the search space more creatively, though it may take longer to converge on a solution.&lt;/p&gt;

&lt;h1 id=&quot;fitness-function&quot;&gt;Fitness Function&lt;/h1&gt;

&lt;p&gt;The fitness function is a function that takes a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Member&lt;/code&gt; and returns a number that represents how well that member solves the problem. In our case, we want to know how well a member approximates the target image.&lt;/p&gt;

&lt;p&gt;Modeling a fitness function to map to the search space is often the most difficult part of developing an evolutionary algorithm. It’s also the most important part because it’s what the algorithm uses to determine which members are better than others. Two key qualities of a fitness function are &lt;em&gt;determinism&lt;/em&gt; and &lt;em&gt;discrimination&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Deterministic means that given the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Member&lt;/code&gt;, the fitness function should always return the same fitness score. This is because the fitness of a member may be evaluated multiple times during the evolutionary process, and inconsistent results could lead to unpredictable behavior.&lt;/p&gt;

&lt;p&gt;Discriminative means that the fitness function should be able to discriminate between different members of the population. That is, members with different genes should have different fitness scores. Although fitness functions do not have to be strictly discriminative, if many members have the same fitness score, the evolutionary algorithm may have a harder time deciding which members are better.&lt;/p&gt;

&lt;p&gt;Lucky for us, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rmagick&lt;/code&gt; library provides an &lt;a href=&quot;https://rmagick.github.io/image1.html#difference&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Image#difference&lt;/code&gt;&lt;/a&gt; method that fits the bill. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Image#difference&lt;/code&gt; compares two images and returns three numbers that represent how different they are: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mean_erorr_per_pixel&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;normalized_mean_error&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;normalized_maximum_error&lt;/code&gt;. We’ll use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;normalized_mean_error&lt;/code&gt; to calculate our fitness score.&lt;sup id=&quot;fnref:normalized_mean_error&quot;&gt;&lt;a href=&quot;#fn:normalized_mean_error&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;petri_dish&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;rmagick&apos;&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;calculate_fitness&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;target_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;member_image&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;member_to_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IMAGE_WIDTH_PX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IMAGE_HEIGHT_PX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# Magick::Image#difference returns a tuple of:&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# [mean error per pixel, normalized mean error, normalized maximum error]&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;difference&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;member_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Use normalized mean error&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The fitness function is a lambda that takes a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Member&lt;/code&gt; and returns a fitness score. The number is the inverse of the normalized mean error between the target image and the image generated from the member, squared. This means that the higher the fitness score, the better the member approximates the target image.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;🎛️ &lt;strong&gt;Why normalized?&lt;/strong&gt;&lt;br /&gt;
The normalized mean error is a number between 0 and 1 that represents the average difference between the grayscale values of each pixel in the target image and the member image. This puts the error of every member on the same scale. When comparing members that all have the same dimension and color scale, normalization may be moot, but sneakily, we can reuse this fitness function for other images that may have different dimensions and color scales and generally compare the performance of the algorithm across those different target images.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;🙃 &lt;strong&gt;Why the inverse?&lt;/strong&gt;&lt;br /&gt;
When the normalized mean error is inverted (i.e., 1 divided by the normalized mean error), we get a fitness score such that smaller errors (which are closer to 0 after normalization) yield larger fitness values, and larger errors yield smaller fitness values. This means the less “wrong” a member is, the higher its fitness will be.&lt;sup id=&quot;fnref:inverted-error&quot;&gt;&lt;a href=&quot;#fn:inverted-error&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;2️⃣ &lt;strong&gt;Why squared?&lt;/strong&gt;&lt;br /&gt;
In effect, this transformation makes our algorithm very sensitive to the quality of the solutions. Solutions that are closer to the target (i.e., have a larger inverted normalized mean error) are assigned significantly higher fitness scores, and are therefore more likely to be selected for reproduction in the next generation (generally true, but depending on the selection function we define later). This can potentially speed up convergence, but as always, we should be cautious of premature convergence on sub-optimal solutions if there is not enough diversity in the population.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Lastly, we define &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#calculate_fitness&lt;/code&gt; as a lambda because the Petri Dish framework requires the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fitness_function&lt;/code&gt; to respond to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#call&lt;/code&gt;. The framework will call this lambda via the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#fitness&lt;/code&gt; method on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Member&lt;/code&gt; in order to memoize and return the fitness score. We use a closure here so that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;target_image&lt;/code&gt; is available to the lambda when it’s called.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;📫 &lt;strong&gt;Closures&lt;/strong&gt;&lt;br /&gt;
&lt;em&gt;Closures&lt;/em&gt; are a powerful feature of Ruby and other languages. They allow us to define a function that can be called later, but that also has access to the variables that were in scope when the function was defined. It’s like putting a note inside an envelope for the function to open later. In our case, we want to define a function that can be called later by the Petri Dish framework, but that also has access to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;target_image&lt;/code&gt; that we defined earlier.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now that we have a way to represent a member of the population and a way to evaluate the fitness of a member, we can start to evolve the population by defining the evolutionary operators.&lt;/p&gt;

&lt;h1 id=&quot;genetic-operators&quot;&gt;Genetic Operators&lt;/h1&gt;

&lt;p&gt;The genetic operators are the functions that we use to evolve the population generation after generation—they are what allow the algorithm to navigate the vast search space of potential solutions.&lt;/p&gt;

&lt;p&gt;The operators we’ll take a look at are grouped by &lt;em&gt;selection&lt;/em&gt;, &lt;em&gt;crossover&lt;/em&gt;, &lt;em&gt;mutation&lt;/em&gt;, and &lt;em&gt;replacement&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id=&quot;parent-selection-function&quot;&gt;Parent Selection Function&lt;/h2&gt;

&lt;p&gt;Selection is the process of choosing which members of the population are the most fit and therefore should be used as &lt;em&gt;parents&lt;/em&gt; to create the next generation of &lt;em&gt;children&lt;/em&gt;. There are many different selection strategies, but for our task, we’ll use one called &lt;em&gt;fitness proportionate selection&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Also called &lt;em&gt;roulette wheel selection&lt;/em&gt; or &lt;em&gt;stochastic acceptance&lt;/em&gt;, fitness proportionate selection works by assigning each member a weighted probability of being selected as a parent, and then randomly selects parents based on those probabilities.&lt;/p&gt;

&lt;p&gt;The weighted probability assigned to each member is proportional to that member’s fitness score, which means that members with higher fitness scores are more likely to be selected.&lt;/p&gt;

&lt;p&gt;For example, let’s say we have the following &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Member&lt;/code&gt;-s with the following fitness scores:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;population&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;genes: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;fitness_function: &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;genes: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;fitness_function: &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;genes: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;fitness_function: &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To calculate the proportional fitness for each member, we divide the member’s fitness by the total fitness of the population. In our case, the total fitness is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2 + 3 + 5 = 10&lt;/code&gt;. Dividing each members’ fitness by this number gives us the proportion of the total fitness that that member contributes.&lt;/p&gt;

&lt;p&gt;If we calculate a proportional fitness for each member from our example, we get the following results:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;weights&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;population&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;member&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fitness&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;population&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:fitness&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_f&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# =&amp;gt; [0.2, 0.3, 0.5]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In our example, if we were to select multiple members using these weighted probabilities, we would expect to get the first member (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;population[0]&lt;/code&gt;) 20% of the time, the second member (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;population[1]&lt;/code&gt;) 30% of the time, and the third member (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;population[2]&lt;/code&gt;) 50% of the time.&lt;/p&gt;

&lt;p&gt;We can then use these proportional fitnesses, to &lt;em&gt;weigh&lt;/em&gt;, or &lt;em&gt;bias&lt;/em&gt; the otherwise equally random probability of each member being selected.&lt;/p&gt;

&lt;p&gt;Here’s what that looks like:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;roulette_wheel_parent_selection_function&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;members&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;population_fitness&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;members&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:fitness&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;members&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;max_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;member&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;weighted_fitness&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fitness&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;population_fitness&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_f&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;weighted_fitness&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The proportional fitness is calculated by dividing the member’s fitness by the total population fitness, like before. Then, we raise a random number between 0 and 1 to the inverse of the proportional fitness in order to bias the selection towards members with higher fitness scores. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enumerable#max_by&lt;/code&gt; method is used to select two members with the highest result from the block.&lt;/p&gt;

&lt;p&gt;The maths are a bit annoying to me personally, but nevertheless, the result of all of this is like spinning a roulette wheel where the size of each slice is proportional to the member’s fitness. The higher the fitness, the larger the slice, and the more likely the member is to be selected.&lt;/p&gt;

&lt;p&gt;Notice how the parent selection function makes no mention of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Point&lt;/code&gt;-s or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Magick::Image&lt;/code&gt;-s. This is because the selection function is generic and can be used for any problem. It only needs to know how to select members based on their fitness scores.&lt;/p&gt;

&lt;p&gt;There are many other selection methods, like simply selecting the fittest members (elite selection) or by first choosing a random subset of the population and then choosing the fittest amongst those (tournament selection).&lt;/p&gt;

&lt;p&gt;The benefit of fitness proportional selection is that it allows for a balance between &lt;em&gt;exploration&lt;/em&gt; (trying new things) and &lt;em&gt;exploitation&lt;/em&gt; (using what we know works). This is because members with lower fitness scores still have a chance of being selected, but members with higher fitness scores are more likely to be selected.&lt;/p&gt;

&lt;p&gt;This is the selection method we’ll use for our task, but it may be worth experimenting with other selection methods to see if they work better for our problem.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;👨‍👨‍👦 &lt;strong&gt;Multiple Parents&lt;/strong&gt;&lt;br /&gt;
Although most implementations of evolutionary algorithms that I’ve seen select two parents, it is possible to select more than two. In fact, the Petri Dish framework allows for any number of parents to be inside the Array returned by the selection function lambda.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;crossover-function&quot;&gt;Crossover Function&lt;/h2&gt;

&lt;p&gt;In biology, crossover is when paired chromosomes from each parent swap segments of their DNA. This creates new combinations of genes, leading to genetic diversity in offspring. In evolutionary algorithms, crossover is the process of combining the genes of parent members to create a new child member.&lt;/p&gt;

&lt;p&gt;After selecting parents, their genes, an Array of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Point&lt;/code&gt;-s in our case, are combined to create a new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Petridish::Member&lt;/code&gt;. There are many different ways to combine the genes of parents, but for our task, we’ll use a method called &lt;em&gt;random midpoint crossover&lt;/em&gt; to crossover two parents.&lt;/p&gt;

&lt;p&gt;Random midpoint crossover works by randomly selecting a midpoint in the genes of each parent and then combining the genes before and after that midpoint to create a new child. Specifically, we’ll randomly select a midpoint between &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; and the length of the genes Array, and then take the genes before that midpoint from the first parent and the genes after that midpoint from the second parent.&lt;/p&gt;

&lt;p&gt;For example, say we have two parents with the following genes:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;parent_1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PetriDish&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;genes: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# fitness_function: ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;parent_2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PetriDish&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;genes: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;125&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;150&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# fitness_function: ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If we were to perform random midpoint crossover on these parents, and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;midpoint = 1&lt;/code&gt;, we would get the following child:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;child&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PetriDish&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;genes: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# fitness_function: ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here’s our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;random_midpoint_crossover_function&lt;/code&gt; implementation for combining two parents:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;random_midpoint_crossover_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;midpoint&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;genes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;PetriDish&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;genes: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;genes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;midpoint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;genes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;midpoint&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;fitness_function: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fitness_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note again, our function returns a lambda, as required by the Petri Dish framework. The lambda takes an Array of parents and returns a new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PetriDish::Member&lt;/code&gt; with the combined genes of the parents, while also passing along the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fitness_function&lt;/code&gt; from the configuration object captured by the closure. As before, the fitness function works generically and does not need to know about &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Point&lt;/code&gt;-s or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Magick::Image&lt;/code&gt;-s.&lt;/p&gt;

&lt;p&gt;Other crossover methods include &lt;em&gt;uniform crossover&lt;/em&gt;, where each gene is randomly selected from either parent, and &lt;em&gt;majority rule crossover&lt;/em&gt;, which works well for instances of selecting more than two parents. It works by having each gene of the offspring determined by a majority vote among the parents. If the parents have equal votes, one parent is chosen randomly to determine the gene.&lt;/p&gt;

&lt;p&gt;Random midpoint crossover is a good choice for starting out for its simplicity, but again, it’s worth experimenting with other crossover methods to see if they work better for a particular problem. (In fact, its possible to use one genetic algorithm to optimize the genetic operators of another genetic algorithm, but that’s a topic for another day…).&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;👩‍🔬 &lt;strong&gt;Meta Experimentation&lt;/strong&gt;&lt;br /&gt;
This type of meta experimentation is akin to hyperparameter tuning in machine learning. A core design philosophy of the Petri Dish framework is to make it easy to experiment with different configurations in this way. By passing in different functions for the genetic operators, we can easily experiment with different configurations and observe which ones work best.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That said, for our task, random midpoint crossover works well enough, so we’ll stick with it for now.&lt;/p&gt;

&lt;h2 id=&quot;mutation-function&quot;&gt;Mutation Function&lt;/h2&gt;

&lt;p&gt;Mutation is the process of randomly changing the genes of a child member after crossover. Combining well-fit parents can get us a long way towards a great solution, but mutation is done to introduce new genetic material into the population.&lt;/p&gt;

&lt;p&gt;There are many different ways to mutate a member, but for our task, we’ll use a domain-specific implementation that I call &lt;em&gt;nudge mutation&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;🧬 &lt;strong&gt;Why mutate at all?&lt;/strong&gt;&lt;br /&gt;
Mutation is not strictly necessary for an evolutionary algorithm to work. However, mutation increases diversity and diversity is what allows the algorithm to explore the search space more thoroughly. Without mutation, the algorithm may get stuck in a local maximum, i.e. be unable to find a better solution, though one may exist. Mutation allows the algorithm to escape this local maximum and continue searching for a better solution.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If we think back to our genes as an Array of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Point&lt;/code&gt;-s, then nudge mutation is the process of randomly moving each point’s position and grayscale value by a small amount.&lt;/p&gt;

&lt;p&gt;This is done by adding a random number between &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-10&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;10&lt;/code&gt; to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y&lt;/code&gt; coordinates of each point &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clamp&lt;/code&gt;-ed between the image dimensions. And, adding a random number between &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-25&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+25&lt;/code&gt; to the grayscale value &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clamp&lt;/code&gt;-ed between &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0..255&lt;/code&gt; (i.e. valid grayscale values).&lt;/p&gt;

&lt;p&gt;If we implemented this as a lambda for the Petri Dish &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mutation_function&lt;/code&gt; configuration (RBS type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Proc[Member, Member]&lt;/code&gt;), it might look something like this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;nudge_mutation_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;mutated_genes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;genes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gene&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;no&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;clamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IMAGE_WIDTH_PX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;clamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IMAGE_HEIGHT_PX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;grayscale&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;clamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;GRAYSCALE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;GRAYSCALE_RANGE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;PetriDish&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;genes: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mutated_genes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;fitness_function: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fitness_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;However, we don’t want to mutate every gene of a child; we want to preserve qualities from the parents and strike a balance between exploration and exploitation. Therefore, we use a &lt;em&gt;mutation rate&lt;/em&gt; to determine the probability that any particular gene will be mutated.&lt;/p&gt;

&lt;p&gt;For example, if the mutation rate is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.1&lt;/code&gt;, then there is a 10% chance that a gene will be mutated.&lt;/p&gt;

&lt;p&gt;If we add the concept of a mutation rate to our mutation function, we get this:&lt;/p&gt;

&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;def nudge_mutation_function(configuration)
&lt;/span&gt;  -&amp;gt;(member) do
    mutated_genes = member.genes.dup.map do |gene|
&lt;span class=&quot;gi&quot;&gt;+      if rand &amp;lt; configuration.mutation_rate
&lt;/span&gt;        Point.new(
          (gene.x + rand(-10..10)).clamp(0, IMAGE_WIDTH_PX),
          (gene.y + rand(-10..10)).clamp(0, IMAGE_HEIGHT_PX),
          (gene.grayscale + rand(-25..25)).clamp(GRAYSCALE.min, GRAYSCALE_RANGE.max)
        )
&lt;span class=&quot;gi&quot;&gt;+      else
+        gene
+      end
&lt;/span&gt;    end
    PetriDish::Member.new(genes: mutated_genes, fitness_function: configuration.fitness_function)
  end
&lt;span class=&quot;p&quot;&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here, we use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;configuration.mutation_rate&lt;/code&gt; to determine whether or not to mutate each gene. If the random number is less than the mutation rate, we mutate the gene, otherwise, copy it over to the new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PetriDish::Member&lt;/code&gt; as-is.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;📈 &lt;strong&gt;Determining the mutation rate&lt;/strong&gt;&lt;br /&gt;
The mutation rate is a hyperparameter that can be tuned to improve the performance of the algorithm. A higher mutation rate will increase diversity, but it may also cause the algorithm to take longer to converge. A lower mutation rate will decrease diversity, but it may also cause the algorithm to get stuck in a local maximum. The mutation rate is often a good hyperparameter to tune when trying to improve the performance of an evolutionary algorithm.&lt;sup id=&quot;fnref:high-mutation-rate&quot;&gt;&lt;a href=&quot;#fn:high-mutation-rate&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Other common types of mutation strategies include &lt;em&gt;swap mutation&lt;/em&gt;, where two genes’ positions are randomly swapped, and &lt;em&gt;scramble mutation&lt;/em&gt;, where a random subset of genes are randomly shuffled. Like all other genetic operators, the best strategy for the problem at hand is highly dependent on the problem itself and is often worth experimenting with.&lt;/p&gt;

&lt;p&gt;In our case, the mutation function implementation was developed to represent the idea of nudging around the points of a polygon until the resulting image looks like the target image.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;🪲 &lt;strong&gt;Point jitter??&lt;/strong&gt;&lt;br /&gt;
You may see that in the &lt;a href=&quot;https://github.com/Thomascountz/petri_dish/blob/133be9efea42e7e3f62e01cd92a77ba03425afa4/examples/low_poly_reconstruction/low_poly_reconstruction.rb#L115-L130&quot;&gt;actual implementation&lt;/a&gt; of this method, I’ve added a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;point_jitter&lt;/code&gt; of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rand(-0.0001..0.0001)&lt;/code&gt; to each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y&lt;/code&gt; of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Point&lt;/code&gt;. This is because the particular implementation of the Delaunay algorithm would fall into a divide-by-zero error if all of the points were collinear (on exactly on the same line). This is a good example of how reality is often messier than theory, and how we must adapt our models and implementation to the problem at hand.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now that we have a way to select parents, combine their genes, and mutate the resulting child, we can start to evolve the population. The last step is to define how we’ll replace the old population with the new population.&lt;/p&gt;

&lt;h2 id=&quot;replacement&quot;&gt;Replacement&lt;/h2&gt;

&lt;p&gt;In the most simple case, we can replace the entire population with the new population, sometimes called &lt;em&gt;generational replacement&lt;/em&gt;. This often works well, but it can sometimes cause &lt;em&gt;too much&lt;/em&gt; diversity, which can cause the algorithm to take longer to converge as some of the most-fit members are lost. To prevent this, we can use a technique called &lt;em&gt;elitism&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Elitism, which I personally like to call &lt;em&gt;grandparenting&lt;/em&gt;, is the process of preserving the fittest members of the population from one generation to the next. This is done by taking the top &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; members of the population and adding them to the new population. The rest of the new population is filled with the children of the parents.&lt;/p&gt;

&lt;p&gt;In the Petri Dish framework, we tune this parameter using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;elitism_rate&lt;/code&gt; configuration value. This is the proportion of the population that is preserved through elitism. For example, if the elitism rate is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.1&lt;/code&gt;, then the top 10% of the population will be preserved through to the next generation.&lt;/p&gt;

&lt;p&gt;Other replacement strategies include &lt;em&gt;steady-state replacement&lt;/em&gt;, where only a single member of the population is replaced with a child, and &lt;em&gt;age-based replacement&lt;/em&gt;, where the oldest members of the population are replaced with children.&lt;/p&gt;

&lt;p&gt;For our image reconstruction task, we’ll use generational replacement with grandparenting set to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.05&lt;/code&gt;, or 5%.&lt;/p&gt;

&lt;h2 id=&quot;end-condition&quot;&gt;End Condition&lt;/h2&gt;

&lt;p&gt;Lastly, we need to define when the algorithm should stop. The two most common end conditions are 1) when a member of the population meets the criteria, or 2) when a certain number of generations have passed. We’ll use the latter.&lt;/p&gt;

&lt;p&gt;An &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;end_condition_function&lt;/code&gt; can otherwise be used to determine if any of the members of the population meets a particular criteria. For example, if we were trying to find a member of the population that had a fitness score of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.0&lt;/code&gt;, we could use an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;end_condition_function&lt;/code&gt;. Alternatively, we could run the algorithm for a given amount of wall time, or until the rate of improvement drops below a certain threshold.&lt;/p&gt;

&lt;p&gt;In our case, we’ll use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;max_generations&lt;/code&gt; configuration value to determine when the algorithm should stop. This is the maximum number of generations that the algorithm will run for, regardless of the fitness of the members of the population. I have found that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;5000&lt;/code&gt; generations is enough to get a good approximation of the target image, but not so many that it takes too long to run.&lt;/p&gt;

&lt;p&gt;We now have all of the pieces we need to put together our evolutionary algorithm. Let’s see how it all works!&lt;/p&gt;

&lt;h1 id=&quot;putting-it-together&quot;&gt;Putting it Together&lt;/h1&gt;

&lt;p&gt;Before we look at the results, let’s recap everything we’ve gone over.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;A genetic algorithm is a type of evolutionary algorithm that uses genetic operators to evolve a population of members towards a solution to a problem.&lt;/li&gt;
  &lt;li&gt;We start the algorithm design by identifying our objective, defining our decision variables and search space, and defining our constraints.&lt;/li&gt;
  &lt;li&gt;We then define a way to represent a member of the population and a way to evaluate the fitness of a member.
    &lt;ol&gt;
      &lt;li&gt;In our case, we represent a member of the population as an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Magick::Image&lt;/code&gt; object, and we evaluate the fitness of a member by comparing it to the target image using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Magick::Image#difference&lt;/code&gt; method.&lt;/li&gt;
      &lt;li&gt;We also represent a member of the population as a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PetriDish::Member&lt;/code&gt; object with an Array of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Point&lt;/code&gt;-s as genes, which is used by the Petri Dish framework to evolve the population.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;The genetic operators are selection, crossover, mutation, and replacement.
    &lt;ol&gt;
      &lt;li&gt;Selection is the process of choosing which members of the population are the most fit and therefore should be used as &lt;em&gt;parents&lt;/em&gt; to create the next generation of &lt;em&gt;children&lt;/em&gt;.&lt;/li&gt;
      &lt;li&gt;Crossover is the process of combining the genes of parent members to create a new child member.&lt;/li&gt;
      &lt;li&gt;Mutation is the process of randomly changing the genes of a child member after crossover.&lt;/li&gt;
      &lt;li&gt;Replacement is the process of replacing the old population with the new population.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;The end condition is the criteria that determines when the algorithm should stop.
    &lt;ol&gt;
      &lt;li&gt;In our case, we’ll use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;max_generations&lt;/code&gt; configuration value to determine when the algorithm should stop. This is the maximum number of generations that the algorithm will run for, regardless of the fitness of the members of the population.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;final-configuration&quot;&gt;Final Configuration&lt;/h2&gt;

&lt;p&gt;The following is the configuration from the &lt;a href=&quot;https://github.com/Thomascountz/petri_dish/blob/133be9efea42e7e3f62e01cd92a77ba03425afa4/examples/low_poly_reconstruction/low_poly_reconstruction.rb#L42-L56&quot;&gt;actual implementation&lt;/a&gt; and contains all of the pieces we’ve discussed so far, with the addition of a few callbacks that the framework provides.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;configuration&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;PetriDish&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;population_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mutation_rate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.05&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;elitism_rate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.05&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;max_generations&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10_000&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fitness_function&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;calculate_fitness&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;target_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parents_selection_function&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;roulette_wheel_parent_selection_function&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;crossover_function&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;random_midpoint_crossover_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mutation_function&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nudge_mutation_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# TODO: Don&apos;t pass in image dimensions, use constants instead&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;highest_fitness_callback&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;save_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;member_to_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IMAGE_WIDTH_PX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IMAGE_HEIGHT_PX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;generation_start_callback&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_generation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;generation_start_callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_generation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;end_condition_function&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;highest_fitness_callback&lt;/code&gt; is invoked when a member with the highest fitness seen so far is found. When that happens, we save the image to a file so that we can see the progress of the algorithm.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;generation_start_callback&lt;/code&gt; is invoked at the start of each generation. We use this to keep track of the progress of the algorithm to do things like name the output images.&lt;/p&gt;

&lt;p&gt;The last piece of the configuration we haven’t discussed yet is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;population_size&lt;/code&gt;. This is the number of members in the population per generation, and like all other pieces of configuration, is a hyperparameter that should be tuned to improve the performance of the algorithm. A larger population size can increase diversity, but it may also cause the algorithm to take longer to converge. A smaller population size can decrease diversity, but can be aided with a higher mutation rate and maximum number of generations, as we’ve done here.&lt;/p&gt;

&lt;h1 id=&quot;results&quot;&gt;Results&lt;/h1&gt;

&lt;p&gt;Finally, it’s time to run the algorithm and see what happens! Let’s take a subjective look at the results before we get into the numbers.&lt;/p&gt;

&lt;style&gt;
  .slider-container {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    width: 80%;
    margin: 50px auto;
    padding: 20px;
    border: var(--border-thickness) solid var(--text-color);
  }

  .data-container,
  .slideContainer {
    width: 100%;
    margin-bottom: 20px;
  }

  .slideContainer {
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .slideContainer img {
    max-width: 100%;
    height: auto;
    display: block;
    margin: 0 auto;
  }

  .slider {
    width: 100%;
  }

  .generation,
  .fitness,
  .timeInSeconds {
    font-family: var(--font-family);
  }

  .info .label {
    font-family: var(--font-family);
    text-align: right;
  }

  /* Responsive media query for devices with width greater than 768px */
  @media (min-width: 768px) {

    .data-container,
    .slideContainer {
      width: 45%;
    }
  }
&lt;/style&gt;

&lt;div class=&quot;slider-container&quot;&gt;
  &lt;div class=&quot;data-container&quot;&gt;
    &lt;div class=&quot;info&quot;&gt;
      &lt;span class=&quot;label&quot;&gt;Generation:&lt;/span&gt;
      &lt;span class=&quot;generation value&quot;&gt;0000&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;info&quot;&gt;
      &lt;span class=&quot;label&quot;&gt;Max Fitness:&lt;/span&gt;
      &lt;span class=&quot;fitness value&quot;&gt;0078.1640&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;info&quot;&gt;
      &lt;span class=&quot;label&quot;&gt;Time (seconds):&lt;/span&gt;
      &lt;span class=&quot;timeInSeconds value&quot;&gt;0001.03&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;slideContainer&quot;&gt;
    &lt;img class=&quot;slide&quot; src=&quot;/assets/images/low_poly_image_generation/out/gen-0000.png&quot; /&gt;
  &lt;/div&gt;
  &lt;input type=&quot;range&quot; min=&quot;1&quot; max=&quot;6128&quot; value=&quot;1&quot; class=&quot;slider&quot; /&gt;
&lt;/div&gt;

&lt;script&gt;
  var generationNumbers = [&quot;0000&quot;, &quot;0001&quot;, &quot;0002&quot;, &quot;0003&quot;, &quot;0004&quot;, &quot;0006&quot;, &quot;0007&quot;,
    &quot;0008&quot;, &quot;0010&quot;, &quot;0016&quot;, &quot;0019&quot;, &quot;0022&quot;, &quot;0023&quot;, &quot;0029&quot;, &quot;0034&quot;, &quot;0045&quot;, &quot;0046&quot;,
    &quot;0047&quot;, &quot;0048&quot;, &quot;0052&quot;, &quot;0057&quot;, &quot;0058&quot;, &quot;0060&quot;, &quot;0065&quot;, &quot;0070&quot;, &quot;0072&quot;, &quot;0073&quot;,
    &quot;0074&quot;, &quot;0076&quot;, &quot;0078&quot;, &quot;0087&quot;, &quot;0088&quot;, &quot;0108&quot;, &quot;0117&quot;, &quot;0120&quot;, &quot;0132&quot;, &quot;0135&quot;,
    &quot;0137&quot;, &quot;0158&quot;, &quot;0169&quot;, &quot;0176&quot;, &quot;0193&quot;, &quot;0224&quot;, &quot;0233&quot;, &quot;0254&quot;, &quot;0256&quot;, &quot;0277&quot;,
    &quot;0288&quot;, &quot;0294&quot;, &quot;0308&quot;, &quot;0358&quot;, &quot;0361&quot;, &quot;0378&quot;, &quot;0422&quot;, &quot;0429&quot;, &quot;0454&quot;, &quot;0510&quot;,
    &quot;0549&quot;, &quot;0634&quot;, &quot;0682&quot;, &quot;0708&quot;, &quot;0746&quot;, &quot;0779&quot;, &quot;0835&quot;, &quot;0851&quot;, &quot;0857&quot;, &quot;0860&quot;,
    &quot;0906&quot;, &quot;0984&quot;, &quot;1045&quot;, &quot;1102&quot;, &quot;1131&quot;, &quot;1180&quot;, &quot;1223&quot;, &quot;1241&quot;, &quot;1245&quot;, &quot;1248&quot;,
    &quot;1265&quot;, &quot;1268&quot;, &quot;1544&quot;, &quot;1558&quot;, &quot;1563&quot;, &quot;1568&quot;, &quot;1700&quot;, &quot;1855&quot;, &quot;1877&quot;, &quot;1897&quot;,
    &quot;1944&quot;, &quot;2194&quot;, &quot;2233&quot;, &quot;2236&quot;, &quot;2241&quot;, &quot;2358&quot;, &quot;2389&quot;, &quot;2461&quot;, &quot;2548&quot;, &quot;2619&quot;,
    &quot;2625&quot;, &quot;2739&quot;, &quot;2740&quot;, &quot;3194&quot;, &quot;3223&quot;, &quot;3826&quot;, &quot;3988&quot;, &quot;4016&quot;, &quot;4069&quot;, &quot;4189&quot;,
    &quot;4308&quot;, &quot;4332&quot;, &quot;4440&quot;, &quot;4684&quot;, &quot;4924&quot;, &quot;5081&quot;, &quot;5143&quot;, &quot;5170&quot;, &quot;5405&quot;, &quot;5433&quot;,
    &quot;5475&quot;, &quot;5560&quot;, &quot;5702&quot;, &quot;6124&quot;, &quot;6198&quot;];

  var fitnessNumbers = [&quot;0078.1640&quot;, &quot;0085.2235&quot;, &quot;0095.8818&quot;, &quot;0100.9292&quot;,
    &quot;0102.3121&quot;, &quot;0116.5887&quot;, &quot;0116.8058&quot;, &quot;0133.0203&quot;, &quot;0167.9346&quot;, &quot;0180.1719&quot;,
    &quot;0193.6908&quot;, &quot;0202.7385&quot;, &quot;0207.6398&quot;, &quot;0215.2024&quot;, &quot;0218.1709&quot;, &quot;0221.1855&quot;,
    &quot;0224.5201&quot;, &quot;0226.6439&quot;, &quot;0232.1746&quot;, &quot;0238.1714&quot;, &quot;0239.7016&quot;, &quot;0241.8438&quot;,
    &quot;0259.5971&quot;, &quot;0287.2607&quot;, &quot;0289.9245&quot;, &quot;0291.3394&quot;, &quot;0292.9619&quot;, &quot;0299.4971&quot;,
    &quot;0321.9478&quot;, &quot;0372.8176&quot;, &quot;0381.7194&quot;, &quot;0410.0227&quot;, &quot;0436.2229&quot;, &quot;0445.3775&quot;,
    &quot;0447.8833&quot;, &quot;0472.2888&quot;, &quot;0475.7925&quot;, &quot;0491.0803&quot;, &quot;0501.8061&quot;, &quot;0514.5392&quot;,
    &quot;0531.9147&quot;, &quot;0551.1477&quot;, &quot;0562.5769&quot;, &quot;0602.7261&quot;, &quot;0606.0258&quot;, &quot;0610.2664&quot;,
    &quot;0617.5342&quot;, &quot;0670.1177&quot;, &quot;0679.4658&quot;, &quot;0750.4697&quot;, &quot;0772.4439&quot;, &quot;0776.8642&quot;,
    &quot;0798.1546&quot;, &quot;0818.3324&quot;, &quot;0853.8499&quot;, &quot;0921.0733&quot;, &quot;0926.0791&quot;, &quot;0950.5562&quot;,
    &quot;0992.6206&quot;, &quot;1005.5427&quot;, &quot;1037.8663&quot;, &quot;1047.9896&quot;, &quot;1064.7530&quot;, &quot;1067.0050&quot;,
    &quot;1071.8805&quot;, &quot;1078.2098&quot;, &quot;1109.0748&quot;, &quot;1111.9042&quot;, &quot;1142.6594&quot;, &quot;1148.4606&quot;,
    &quot;1188.2155&quot;, &quot;1212.7286&quot;, &quot;1220.7054&quot;, &quot;1250.6114&quot;, &quot;1291.7208&quot;, &quot;1356.6018&quot;,
    &quot;1389.3510&quot;, &quot;1389.8488&quot;, &quot;1403.6130&quot;, &quot;1421.7452&quot;, &quot;1433.7339&quot;, &quot;1469.8056&quot;,
    &quot;1489.1271&quot;, &quot;1517.6637&quot;, &quot;1537.4407&quot;, &quot;1564.9784&quot;, &quot;1636.9248&quot;, &quot;1656.3737&quot;,
    &quot;1700.7393&quot;, &quot;1701.7908&quot;, &quot;1722.0287&quot;, &quot;1769.4364&quot;, &quot;1780.9038&quot;, &quot;1798.1664&quot;,
    &quot;1818.8158&quot;, &quot;1819.6638&quot;, &quot;1846.3436&quot;, &quot;1900.1823&quot;, &quot;1949.9552&quot;, &quot;2005.7937&quot;,
    &quot;2083.7737&quot;, &quot;2095.7519&quot;, &quot;2117.4079&quot;, &quot;2144.7930&quot;, &quot;2154.2111&quot;, &quot;2173.4852&quot;,
    &quot;2187.9873&quot;, &quot;2239.0811&quot;, &quot;2281.1902&quot;, &quot;2430.6313&quot;, &quot;2533.8661&quot;, &quot;2550.3649&quot;,
    &quot;2553.3816&quot;, &quot;2574.6566&quot;, &quot;2637.9835&quot;, &quot;2670.8562&quot;, &quot;2709.4564&quot;, &quot;2717.7863&quot;,
    &quot;2750.0546&quot;, &quot;2820.5955&quot;, &quot;2856.9516&quot;, &quot;2857.0822&quot;];

  var timeInSeconds = [&quot;0001.03&quot;, &quot;0001.40&quot;, &quot;0001.97&quot;, &quot;0002.66&quot;, &quot;0003.16&quot;,
    &quot;0004.18&quot;, &quot;0004.85&quot;, &quot;0005.44&quot;, &quot;0006.39&quot;, &quot;0009.99&quot;, &quot;0011.67&quot;, &quot;0013.16&quot;,
    &quot;0013.93&quot;, &quot;0017.01&quot;, &quot;0019.70&quot;, &quot;0026.67&quot;, &quot;0027.66&quot;, &quot;0028.05&quot;, &quot;0028.52&quot;,
    &quot;0031.26&quot;, &quot;0034.63&quot;, &quot;0034.95&quot;, &quot;0036.48&quot;, &quot;0040.78&quot;, &quot;0044.12&quot;, &quot;0045.81&quot;,
    &quot;0046.65&quot;, &quot;0048.06&quot;, &quot;0049.25&quot;, &quot;0050.18&quot;, &quot;0055.43&quot;, &quot;0055.84&quot;, &quot;0066.97&quot;,
    &quot;0072.30&quot;, &quot;0073.74&quot;, &quot;0082.65&quot;, &quot;0084.22&quot;, &quot;0086.36&quot;, &quot;0100.19&quot;, &quot;0107.77&quot;,
    &quot;0111.88&quot;, &quot;0121.47&quot;, &quot;0139.02&quot;, &quot;0144.80&quot;, &quot;0156.88&quot;, &quot;0158.06&quot;, &quot;0168.81&quot;,
    &quot;0174.91&quot;, &quot;0178.13&quot;, &quot;0185.46&quot;, &quot;0211.94&quot;, &quot;0213.32&quot;, &quot;0222.54&quot;, &quot;0246.35&quot;,
    &quot;0250.11&quot;, &quot;0262.99&quot;, &quot;0292.75&quot;, &quot;0313.79&quot;, &quot;0359.18&quot;, &quot;0385.10&quot;, &quot;0398.50&quot;,
    &quot;0419.48&quot;, &quot;0437.47&quot;, &quot;0468.35&quot;, &quot;0476.74&quot;, &quot;0480.18&quot;, &quot;0481.41&quot;, &quot;0507.88&quot;,
    &quot;0551.72&quot;, &quot;0586.78&quot;, &quot;0619.17&quot;, &quot;0635.71&quot;, &quot;0663.73&quot;, &quot;0687.61&quot;, &quot;0697.98&quot;,
    &quot;0700.16&quot;, &quot;0701.72&quot;, &quot;0711.44&quot;, &quot;0713.43&quot;, &quot;0866.50&quot;, &quot;0874.18&quot;, &quot;0876.86&quot;,
    &quot;0879.65&quot;, &quot;0950.00&quot;, &quot;1032.75&quot;, &quot;1044.77&quot;, &quot;1057.39&quot;, &quot;1085.01&quot;, &quot;1241.79&quot;,
    &quot;1265.46&quot;, &quot;1267.05&quot;, &quot;1270.09&quot;, &quot;1340.66&quot;, &quot;1359.74&quot;, &quot;1403.06&quot;, &quot;1455.77&quot;,
    &quot;1498.74&quot;, &quot;1502.63&quot;, &quot;1573.88&quot;, &quot;1574.21&quot;, &quot;1855.07&quot;, &quot;1872.98&quot;, &quot;2251.57&quot;,
    &quot;2360.59&quot;, &quot;2379.96&quot;, &quot;2416.47&quot;, &quot;2496.00&quot;, &quot;2579.31&quot;, &quot;2596.08&quot;, &quot;2676.66&quot;,
    &quot;2859.12&quot;, &quot;3037.47&quot;, &quot;3142.06&quot;, &quot;3188.27&quot;, &quot;3207.87&quot;, &quot;3378.84&quot;, &quot;3399.71&quot;,
    &quot;3432.90&quot;, &quot;3495.90&quot;, &quot;3602.76&quot;, &quot;3919.26&quot;, &quot;3972.11&quot;];

  var generations = generationNumbers.map(num =&gt; {
    var img = new Image();
    img.src = `/assets/images/low_poly_image_generation/out/gen-${num}.png`;
    return img;
  });

  document.querySelectorAll(&apos;.slider-container&apos;).forEach(container =&gt; {
    var slider = container.querySelector(&apos;.slider&apos;);
    var slide = container.querySelector(&apos;.slide&apos;);
    var generation = container.querySelector(&apos;.generation&apos;);
    var fitness = container.querySelector(&apos;.fitness&apos;);
    var timeInSecondsElem = container.querySelector(&apos;.timeInSeconds&apos;);

    slider.max = generations.length;

    slider.oninput = function () {
      slide.src = generations[this.value - 1].src;
      var genNumber = generationNumbers[this.value - 1];
      var fitNumber = fitnessNumbers[this.value - 1];
      var timeInSecondsNumber = timeInSeconds[this.value - 1];
      generation.textContent = genNumber;
      fitness.textContent = fitNumber;
      timeInSecondsElem.textContent = timeInSecondsNumber;
    };
  });
&lt;/script&gt;

&lt;h2 id=&quot;subjective-analysis&quot;&gt;Subjective Analysis&lt;/h2&gt;

&lt;div style=&quot;text-align:center&quot;&gt;
  &lt;img style=&quot;border: 1px solid gray; padding: 20px&quot; src=&quot;/assets/images/low_poly_image_generation/ruby_logo_result.png&quot; /&gt;
  &lt;figcaption&gt;Target image and result&lt;/figcaption&gt;
&lt;/div&gt;

&lt;p&gt;After running the algorithm for over 6000 generations over the course of an hour, I’m really pleased with the results!&lt;/p&gt;

&lt;p&gt;I’m surprised by the details the algorithm was able to replicate. For example, the curvature of the top of the ruby is really well defined, given that we’re working with only straight lines. Also, the highlights along the gem’s facets were captured well. If I squint hard enough, I have a hard time distinguishing the target image from the result!&lt;/p&gt;

&lt;p&gt;With that said, there are some points stuck in the upper left corner that are shading part of the background the wrong color, but I think it’s those points are actually contributing to the gem’s curvature that I mentioned earlier.&lt;/p&gt;

&lt;p&gt;It looks like algorithm had a lot of early success: by generation 120, we can already begin to see vast improvements! However, as the algorithm continued to run, the gains each generation appeared to diminish, which is to be expected from an evolutionary algorithm and an exponential fitness function. After about generation 2000, the algorithm was making smaller and smaller tweaks to the image.&lt;/p&gt;

&lt;div style=&quot;text-align:center; margin: 10px auto 10px auto;&quot;&gt;
  &lt;img style=&quot;border: 1px solid gray; padding: 20px&quot; src=&quot;/assets/images/low_poly_image_generation/skip_montage.png&quot; /&gt;
  &lt;figcaption&gt;Output of the &lt;code&gt;highest_fitness_callback&lt;/code&gt; calls show a progressively more refined low-poly representation&lt;/figcaption&gt;
&lt;/div&gt;

&lt;p&gt;The biggest improvements to the image after the early generations appear to be in the grayscale values, rather than the positions of the points. This makes sense because the position of the points only really matter in that the resulting triangle contributes to the grayscale value of the pixels it covers.&lt;/p&gt;

&lt;p&gt;By looking only at the images, it’s hard for me to tell how much the algorithm improved after generation 2000 and whether or not we could have stopped it there.&lt;/p&gt;

&lt;p&gt;I’m curious to see if the numbers tell a different story.&lt;sup id=&quot;fnref:subjectivity&quot;&gt;&lt;a href=&quot;#fn:subjectivity&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;h2 id=&quot;data-analysis-and-interpretation&quot;&gt;Data Analysis and Interpretation&lt;/h2&gt;

&lt;p&gt;Now that we’ve taken a subjective look at the results, let’s take an objective look at the algorithm’s performance. We’re going to take these numbers with a grain of salt: since we’ve only ran the algorithm once and with one set of configurations, there is nothing to characterize the performance against.&lt;sup id=&quot;fnref:additional_runs&quot;&gt;&lt;a href=&quot;#fn:additional_runs&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;8&lt;/a&gt;&lt;/sup&gt; Additionally, we’re going to look at the numbers from the perspective of the &lt;em&gt;algorithm’s&lt;/em&gt; performance only, rather than the performance of the Ruby code itself&lt;sup id=&quot;fnref:time_t&quot;&gt;&lt;a href=&quot;#fn:time_t&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;9&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:ruby-performance&quot;&gt;&lt;a href=&quot;#fn:ruby-performance&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;10&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;h3 id=&quot;log-data&quot;&gt;Log Data&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;08&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;04&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T19&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;28.744335&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#83090]  INFO -- : {&quot;id&quot;:&quot;869b3cc4-b37b-47ff-8beb-602aaa52c48f&quot;,&quot;generation_count&quot;:0,&quot;highest_fitness&quot;:0,&quot;elapsed_time&quot;:0.0,&quot;last_fitness_increase&quot;:0}&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;08&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;04&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T19&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;29.393945&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#83090]  INFO -- : {&quot;id&quot;:&quot;869b3cc4-b37b-47ff-8beb-602aaa52c48f&quot;,&quot;generation_count&quot;:0,&quot;highest_fitness&quot;:45.43454105775939,&quot;elapsed_time&quot;:0.65,&quot;last_fitness_increase&quot;:0}&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;08&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;04&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T19&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;29.422413&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#83090]  INFO -- : {&quot;id&quot;:&quot;869b3cc4-b37b-47ff-8beb-602aaa52c48f&quot;,&quot;generation_count&quot;:0,&quot;highest_fitness&quot;:73.14234456163621,&quot;elapsed_time&quot;:0.68,&quot;last_fitness_increase&quot;:0}&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;08&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;04&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T19&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;29.702998&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#83090]  INFO -- : {&quot;id&quot;:&quot;869b3cc4-b37b-47ff-8beb-602aaa52c48f&quot;,&quot;generation_count&quot;:0,&quot;highest_fitness&quot;:75.73263190998411,&quot;elapsed_time&quot;:0.96,&quot;last_fitness_increase&quot;:0}&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;08&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;04&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T19&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;29.775709&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#83090]  INFO -- : {&quot;id&quot;:&quot;869b3cc4-b37b-47ff-8beb-602aaa52c48f&quot;,&quot;generation_count&quot;:0,&quot;highest_fitness&quot;:78.16397715764293,&quot;elapsed_time&quot;:1.03,&quot;last_fitness_increase&quot;:0}&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;08&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;04&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T19&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;29.990462&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#83090]  INFO -- : {&quot;id&quot;:&quot;869b3cc4-b37b-47ff-8beb-602aaa52c48f&quot;,&quot;generation_count&quot;:1,&quot;highest_fitness&quot;:78.16397715764293,&quot;elapsed_time&quot;:1.25,&quot;last_fitness_increase&quot;:0}&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;08&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;04&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T19&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;30.146199&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#83090]  INFO -- : {&quot;id&quot;:&quot;869b3cc4-b37b-47ff-8beb-602aaa52c48f&quot;,&quot;generation_count&quot;:1,&quot;highest_fitness&quot;:85.22354925417734,&quot;elapsed_time&quot;:1.4,&quot;last_fitness_increase&quot;:1}&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;08&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;04&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T19&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;30.543062&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#83090]  INFO -- : {&quot;id&quot;:&quot;869b3cc4-b37b-47ff-8beb-602aaa52c48f&quot;,&quot;generation_count&quot;:2,&quot;highest_fitness&quot;:85.22354925417734,&quot;elapsed_time&quot;:1.8,&quot;last_fitness_increase&quot;:1}&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;08&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;04&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T19&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;30.711047&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#83090]  INFO -- : {&quot;id&quot;:&quot;869b3cc4-b37b-47ff-8beb-602aaa52c48f&quot;,&quot;generation_count&quot;:2,&quot;highest_fitness&quot;:95.88177285281498,&quot;elapsed_time&quot;:1.97,&quot;last_fitness_increase&quot;:2}&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;08&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;04&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T19&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;31.090201&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#83090]  INFO -- : {&quot;id&quot;:&quot;869b3cc4-b37b-47ff-8beb-602aaa52c48f&quot;,&quot;generation_count&quot;:3,&quot;highest_fitness&quot;:95.88177285281498,&quot;elapsed_time&quot;:2.35,&quot;last_fitness_increase&quot;:2}&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;08&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;04&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T19&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;31.405858&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#83090]  INFO -- : {&quot;id&quot;:&quot;869b3cc4-b37b-47ff-8beb-602aaa52c48f&quot;,&quot;generation_count&quot;:3,&quot;highest_fitness&quot;:100.92915163430119,&quot;elapsed_time&quot;:2.66,&quot;last_fitness_increase&quot;:3}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Using the logs as a data source, let’s start by looking at a plot of the highest fitness score of any population over time. The fitness was determined by the inverse of the normalized mean error between the target image and the image generated from the member, squared. The higher the fitness, the better the member approximates the target image.&lt;/p&gt;

&lt;div style=&quot;text-align:center; margin: 10px auto 10px auto;&quot;&gt;
  &lt;img style=&quot;border: 1px solid gray; padding: 20px&quot; src=&quot;/assets/images/low_poly_image_generation/highest_fitness_over_time.png&quot; /&gt;
  &lt;figcaption&gt;Fitness of the fittest member of the population over time.&lt;/figcaption&gt;
&lt;/div&gt;

&lt;p&gt;We can see that the fitness of the fittest member of each population trends heavily positive over time; somewhat linearly but with a steeper slope at the beginning. This is to be expected because the algorithm starts with a lot to gain from its initial, somewhat random, initial state, and then spends time improving on better and better results.&lt;/p&gt;

&lt;p&gt;We can also see that the trend is not monotonically increasing, i.e. it does not always increase from one time step (generation) to the next. This is because the algorithm is not guaranteed to find a better solution in each generation.&lt;/p&gt;

&lt;p&gt;We can visualize this by plotting the change in highest fitness over time, which I called &lt;em&gt;velocity&lt;/em&gt;, but can also be considered &lt;em&gt;efficiency&lt;/em&gt;.&lt;/p&gt;

&lt;div style=&quot;text-align:center; margin: 10px auto 10px auto;&quot;&gt;
  &lt;img style=&quot;border: 1px solid gray; padding: 20px&quot; src=&quot;/assets/images/low_poly_image_generation/highest_fitness_and_fitness_growth_over_generations.png&quot; /&gt;
  &lt;figcaption&gt;Velocity of the fittest member of the population over time.&lt;/figcaption&gt;
&lt;/div&gt;

&lt;p&gt;Here, we can see a lot of zero-efficiency/zero-velocity generations, where the fitness of the fittest member of the population did not change from the previous generation, specifically after about the 1000th generation. This shows us in another way that the algorithm is converging on a solution.&lt;/p&gt;

&lt;p&gt;In fact, we could use this metric to improve our end condition function, i.e. stop the algorithm when the efficiency drops below a certain threshold, rather than blindly after a certain number generations or a particular fitness score. We can also use efficiency to compare the performance of different configurations.&lt;/p&gt;

&lt;p&gt;That said, convergence on a solution doesn’t always mean that the solution is the best solution to be found, just that it’s the best so far. We should be careful not to prematurely stop the algorithm. For example, at around generation 4500, we see another significant spike in efficiency even after many hundreds of generations with little-or-no improvement.&lt;/p&gt;

&lt;h3 id=&quot;image-data&quot;&gt;Image Data&lt;/h3&gt;

&lt;p&gt;Subjectively, I thought that there was little to be gained after generation 2000, but based on the data from the logs, we know that the algorithm continued to find fitter members, almost linearly! Is generation 6198, with a fitness score of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2857.08&lt;/code&gt;, really &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;68%&lt;/code&gt; better&lt;/em&gt; than generation 2194 with a fitness score of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1700.74&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;What does “68% better” &lt;em&gt;look&lt;/em&gt; like?&lt;/p&gt;

&lt;p&gt;Let’s start by taking a look at the error across individual pixels. If we remember back to our fitness function, we know that the fitness score was determined by the mean error of each pixel, i.e. how far off the grayscale value of each pixel was from the target image. We can visualize this by plotting the grayscale value of a few random pixel over time.&lt;/p&gt;

&lt;div style=&quot;text-align:center; margin: 10px auto 10px auto;&quot;&gt;
  &lt;img style=&quot;border: 1px solid gray; padding: 20px;&quot; src=&quot;/assets/images/low_poly_image_generation/grayscale_value_of_random_pixels.png&quot; /&gt;
  &lt;figcaption&gt;Five random pixels&apos; grayscale values over time&lt;/figcaption&gt;
&lt;/div&gt;

&lt;p&gt;Here, we’re plotting the same five pixels from each member as they journey towards their target (indicated by the dashed lines) over each generation. We can observe that these pixels remain relatively stable after just a few hundred generations, and even more so after about 2500 generations. We can also notice that while some pixels hit their target dead on, others get farther away over time.&lt;/p&gt;

&lt;p&gt;Interestingly, we can see some pixels correlate to the global trends we saw in the log data earlier: pixel 4 makes a significant improvement around generation 2500, and pixel 1 makes a significant improvement around generation 4500.&lt;/p&gt;

&lt;div style=&quot;text-align:center; margin: 10px auto 10px auto;&quot;&gt;
  &lt;img style=&quot;border: 1px solid gray; padding: 20px;&quot; src=&quot;/assets/images/low_poly_image_generation/grayscale_value_of_random_neighboring_pixels.png&quot; /&gt;
  &lt;figcaption&gt;10 random neighboring pixels&apos; grayscale values over time moving towards grayscale 46&lt;/figcaption&gt;
&lt;/div&gt;

&lt;p&gt;We see a similar story when we look at 10 pixels near the center of the image that are all moving towards the same grayscale value. Looking at neighboring pixels is interesting because the algorithm is evolving the population by moving the vertices of triangles around, and therefore, the grayscale values of neighboring pixels are likely to be correlated.&lt;/p&gt;

&lt;p&gt;Again, we can see that the pixels stabilize after about 250 generations, even more so after around after 2500. And, we also clearly see the correlation to the large spike around generation 4500 that we saw in the log data. It appears that these pixels were somewhat stuck for over 1000 generations, and then suddenly jumped to their target grayscale value. Curiously, we can also see a divergence after 5500 generations, even though the overall fitness score continued to increase…&lt;/p&gt;

&lt;div style=&quot;text-align:center; margin: 10px auto 10px auto;&quot;&gt;
  &lt;img style=&quot;border: 1px solid gray; padding: 20px;&quot; src=&quot;/assets/images/low_poly_image_generation/member_differences.png&quot; /&gt;
  &lt;figcaption&gt;The differences between the target image and generations 0000, 0120, 2194, and 6198. Black pixels mean they&apos;re equal.&lt;/figcaption&gt;
&lt;/div&gt;

&lt;p&gt;If we render the differences between the target image and a sample of generations, we get the images above. In these images, the darker the pixel, the closer the grayscale value of the pixel is to the target image.&lt;/p&gt;

&lt;p&gt;This is a good way to visualize our fitness function as the algorithm is essentially trying to increase the number of black pixels and decrease the number of white pixels. We can see that the differences are, in fact, decreasing over time, but it’s still hard to see the 68% difference between generations 2194 and 6198.&lt;/p&gt;

&lt;div style=&quot;text-align:center; margin: 10px auto 10px auto;&quot;&gt;
  &lt;img style=&quot;border: 1px solid gray; padding: 20px;&quot; src=&quot;/assets/images/low_poly_image_generation/diff_gen-6198_gen-2194.png&quot; /&gt;
  &lt;figcaption&gt;The difference between generations 2194 and 6198.&lt;/figcaption&gt;
&lt;/div&gt;

&lt;p&gt;If we look at the &lt;em&gt;difference of the differences&lt;/em&gt; between generations 2194 and 6198, we see mostly dark colored pixels, indicating that the differences are small, and perhaps nearly imperceptible.&lt;/p&gt;

&lt;p&gt;This is what “68% better” looks like!&lt;/p&gt;

&lt;p&gt;Despite all the math and theory, throughout this exploration, we have aimed the power of evolutionary algorithms towards scratching the subjective search space surface of creativity and art. Algorithmic processes can produce results that are not only fascinating from a technical standpoint but also visually (and philosophically) compelling. At the end of analyzing our algorithm’s ability to near its target, we are left to contemplate how well the resulting image captures our aesthetics and creative vision.&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;Crafting a low-poly image representation is but one example of the myriad applications of evolutionary algorithms. The &lt;a href=&quot;https://github.com/Thomascountz/petri_dish/tree/main/examples&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/examples&lt;/code&gt; directory within the Petri Dish repository&lt;/a&gt; showcases additional uses, including tackling the &lt;a href=&quot;https://en.wikipedia.org/wiki/Travelling_salesman_problem&quot;&gt;Travelling Salesperson Problem&lt;/a&gt; and a more straightforward task of text generation.&lt;/p&gt;

&lt;p&gt;Despite the diverse applications, evolutionary algorithms generally follow a uniform framework, as discussed in &lt;a href=&quot;#putting-it-together&quot;&gt;Putting it Together&lt;/a&gt;. Interestingly, many of the design considerations we covered in this blog post are even more broadly applicable to the field of data engineering.&lt;/p&gt;

&lt;p&gt;If you’re interested in exploring the use of genetic algorithms for creative purposes, I can recommend:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;“The Nature of Code” by Daniel Shiffman, particularly the chapters on evolutionary algorithms.&lt;/li&gt;
  &lt;li&gt;“Generative Art” by Matt Pearson, which discusses the principles of algorithmic art creation.&lt;/li&gt;
  &lt;li&gt;The &lt;a href=&quot;https://processing.org/&quot;&gt;Processing&lt;/a&gt; community, which is rich with examples of generative art and creative coding.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And of course, feel free to checkout the code for &lt;a href=&quot;https://github.com/Thomascountz/petri_dish/&quot;&gt;Petri Dish&lt;/a&gt; on Github.&lt;/p&gt;

&lt;h1 id=&quot;footnotes&quot;&gt;Footnotes&lt;/h1&gt;
&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:why_ruby&quot;&gt;
      &lt;p&gt;For academic study, we often use a language like Python or Julia or R. For production, we use C++ or Rust. But, for &lt;em&gt;fun&lt;/em&gt;? For fun, we use Ruby ♥︎ &lt;a href=&quot;#fnref:why_ruby&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:ruby_tail_call_optimization&quot;&gt;
      &lt;p&gt;Danny Guinther’s 2015 blog posts are a fantastic resource for learning about how exactly Ruby implements tail call optimization: &lt;a href=&quot;http://blog.tdg5.com/tail-call-optimization-in-ruby-background/&quot;&gt;Tail Call Optimization in Ruby: Background&lt;/a&gt; and &lt;a href=&quot;http://blog.tdg5.com/tail-call-optimization-in-ruby-deep-dive/&quot;&gt;Tail Call Optimization in Ruby: Deep Dive&lt;/a&gt;. &lt;a href=&quot;#fnref:ruby_tail_call_optimization&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:struct_benchmarks&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://gist.github.com/Thomascountz/f9c8912c3b6aae0345f4b4d2901ec16c&quot;&gt;Hash v. Struct v. Data v. Class Benchmark&lt;/a&gt; &lt;a href=&quot;#fnref:struct_benchmarks&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:normalized_mean_error&quot;&gt;
      &lt;p&gt;The normalized mean quantization error for any single pixel in the image. This distance measure is normalized to a range between 0 and 1. It is independent of the range of red, green, and blue values in the image. &lt;a href=&quot;#fnref:normalized_mean_error&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:inverted-error&quot;&gt;
      &lt;p&gt;If we don’t invert the error, the algorithm will try to &lt;em&gt;maximize the differences&lt;/em&gt; between the member and the target image. In the case of image reconstruction, this will result in an inverted image (black is white and white is black). This isn’t what we’re going for here, but it demonstrates the different ways we can be creative with evolutionary algorithms. &lt;a href=&quot;#fnref:inverted-error&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:high-mutation-rate&quot;&gt;
      &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.1&lt;/code&gt; is somewhat of a high mutation rate, but as we’ll see later, this value was chosen because it worked well for my particular configuration of the problem. &lt;a href=&quot;#fnref:high-mutation-rate&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:subjectivity&quot;&gt;
      &lt;p&gt;Of course, this is all subjective. I’m sure there are some people who would say that the algorithm should have stopped after generation 1000, and others who would say that it should have continued to run for another 1000 generations. The measure of success in creative applications are arbitrary. That said, I think it’s important to have a way to measure the performance of the algorithm itself, and that’s what we’ll do next. &lt;a href=&quot;#fnref:subjectivity&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:additional_runs&quot;&gt;
      &lt;p&gt;Hi, Thomas here. I really wanted to finish this blog post. So as much as I want to write up my experience with tweaking the performance and hyperparameters, I’m going to leave that for another day. I hope you understand. &lt;a href=&quot;#fnref:additional_runs&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:time_t&quot;&gt;
      &lt;p&gt;We’ll use generation count as our time (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t&lt;/code&gt;) instead of wall time or CPU time because it’s a more accurate representation of the algorithm’s performance. The algorithm is not guaranteed to run at a constant speed, so using generation count as our time allows us to compare only the algorithm’s performance of different configurations. &lt;a href=&quot;#fnref:time_t&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:ruby-performance&quot;&gt;
      &lt;p&gt;Me again. CPU and memory performance tuning is an important aspect of developing evolutionary algorithms, especially considering that they are known for being resource intensive, but I’m going to leave that for another day as well. &lt;a href=&quot;#fnref:ruby-performance&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Utilizing Docker for Bundling in a Specific Ruby Environment</title>
   <link href="https://thomascountz.com/2023/07/06/utilizing-docker-for-bundling-in-a-specific-ruby-environment"/>
   <updated>2023-07-06T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2023/07/06/utilizing-docker-for-bundling-in-a-specific-ruby-environment</id>
   <summary type="html">How I&apos;m using Docker to manage operations across dozens of different Ruby environments without local installations.</summary>
   <content type="html">&lt;p&gt;One of the tasks we often find ourselves completing on Zendesk’s Core Ruby Engineering team is helping to keep a variety of projects up to date. This can involve running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle update&lt;/code&gt; across dozens of repositories, each potentially requiring a different Ruby environment. Although we’ve developed tooling to accomplish this rather efficiently, there are often edge cases that require us to work in each repository locally. However, installing so many different versions of Ruby locally can be cumbersome and time-consuming.&lt;/p&gt;

&lt;p&gt;In such scenarios, Ruby Docker images provide a lighter weight solution compared to running a project’s Dockerfile (which can contain dozens of dependencies) or spinning up our Kubernetes-based development tooling (which gives us access to an entire structured environment of all of Zendesk). Individual Ruby images allow us to run commands like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle update&lt;/code&gt; in a specific Ruby environment, such as JRuby, without needing to install that Ruby version locally.&lt;/p&gt;

&lt;p&gt;Let’s take an example of running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle install&lt;/code&gt; in a project that requires JRuby. We’ll use JRuby as an example as it’s not always as straightforward to install as CRuby.&lt;/p&gt;

&lt;p&gt;First (assuming you have Docker running locally), pull the Docker image from DockerHub for the Ruby version you need.&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker pull jruby:latest
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, we’ll run the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle install&lt;/code&gt; command within a Docker container using the JRuby image. If your project uses a private gem repository, you might need to pass in credentials. If you have these credentials stored in an environment variable locally, you can pass that to the Docker container. Docker will use the value from the local environment:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--volume&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;:/usr/src/app &lt;span class=&quot;nt&quot;&gt;--workdir&lt;/span&gt; /usr/src/app &lt;span class=&quot;nt&quot;&gt;--env&lt;/span&gt; PRIVATE_GEM_REPO_CREDS jruby:latest bundle update my_gem &lt;span class=&quot;nt&quot;&gt;--conservative&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here’s a breakdown of the command:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--rm&lt;/code&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-rm&lt;/code&gt; for short): This option removes the container after it exits.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--volume &quot;$(pwd)&quot;:/usr/src/app&lt;/code&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-v&lt;/code&gt; for short): This mounts the current directory (assumed to be your project directory) to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/src/app&lt;/code&gt; in the container.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--workdir /usr/src/app&lt;/code&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-w&lt;/code&gt; for short): This sets the working directory inside the container.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--env PRIVATE_GEM_REPO_CREDS&lt;/code&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-e&lt;/code&gt; for short): This passes the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PRIVATE_GEM_REPO_CREDS&lt;/code&gt; environment variable from your local environment to the Docker container. Replace &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PRIVATE_GEM_REPO_CREDS&lt;/code&gt; with your actual environment variable that holds the credentials for your private gem repository.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jruby:latest&lt;/code&gt;: This is the Docker image we’re using.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle update my_gem --conservative&lt;/code&gt;: This is the command we’re running inside the container. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--conservative&lt;/code&gt; flag with instruct Bundler to update as few gems as necessary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This command runs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle update&lt;/code&gt; in the context of the Docker container, using your stored credentials for authentication and modifying your local directory. Your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile.lock&lt;/code&gt; should be modified (if applicable) and any vendor-ized gems will be updated. You can now commit and push the changes.&lt;/p&gt;

&lt;p&gt;Docker’s flexibility allows us to run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle update&lt;/code&gt; in any Ruby environment, including but not limited to JRuby, without local installation, while also handling authentication via environment variables. This approach can be applied to similar scenarios where specific environments are required for package installation. It’s a streamlined, efficient alternative to more heavyweight solutions.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>A Thought on Practice</title>
   <link href="https://thomascountz.com/2022/02/09/a-thought-on-practice"/>
   <updated>2022-02-09T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2022/02/09/a-thought-on-practice</id>
   <summary type="html">A reflection on the value of practicing familiar skills to rediscover growth and inspiration.</summary>
   <content type="html">&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt; Do a thing you already know how to do.&lt;/p&gt;

&lt;p&gt;Our favorite psychologist, Mihaly Csikszentmihalyi, has taught us that Flow maneuvers between anxiety and boredom. I like to say that I err towards “anxiety” because I’m ambitious, I like to learn new things, I like a challenge. But, I like a bit of boredom too! The boredom range is where I find inspiration, curiosity, and discovery.&lt;/p&gt;

&lt;p&gt;So, where is the line between the two? When I think of boring work, I think of work that does not require gaining new knowledge or skills, and therefore does not pose a challenge. For a Ruby on Rails engineer like me, this could be setting up a new CRUD (create, read, update, and delete) operation, TDD-ing (test-driven developing) a helper method, or changing a button from blue to red. These tasks, being tasks I already know how to do, ebb towards boring. Therefore, if given these tasks, I might find myself in boredom.&lt;/p&gt;

&lt;p&gt;But, do I really &lt;em&gt;know&lt;/em&gt; how to do these tasks?&lt;/p&gt;

&lt;p&gt;It’s been so long since I’ve done them—and I’ve spent so much time scoffing at them—that figuring out how to edit CSS, writing unit tests for simple functions, and building a new simple Rails app are nearly foreign to me!&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rails new&lt;/code&gt; leaves me with analysis paralysis, stubbing a method call fills me with doubt, and working with CSS might as well be working with live electrical wires!&lt;/p&gt;

&lt;p&gt;This is because I’m out of practice.&lt;/p&gt;

&lt;p&gt;I don’t spend time doing the things I think I know how to do because I’m so focused on learning &lt;em&gt;new&lt;/em&gt; things—being pulled towards a challenge and away from “boredom.”&lt;/p&gt;

&lt;p&gt;The things that I’ve written off as boring are ripe with new potential for growth and discovery.&lt;/p&gt;

&lt;p&gt;That old &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rails new&lt;/code&gt; option that I’ve never really looked into is just as exciting as a new Rails feature! Building a simple command-line Ruby program is a brand new experience given how much I’ve learned since my first tic-tac-toe game! Front-end engineering is almost an entirely new discipline compared to when I was first starting out!&lt;/p&gt;

&lt;p&gt;Instead of chasing the dopamine rush of buying new toys, I’m cracking open my old toy box with a new appreciation.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Københavns Luftkvalitet og Cykling</title>
   <link href="https://thomascountz.com/2021/08/02/copenhagen-air"/>
   <updated>2021-08-02T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2021/08/02/copenhagen-air</id>
   <summary type="html">An exploration of Copenhagen&apos;s air quality data through the lens of urban cycling. Using Python, GeoPandas, and the Google Maps API, this analysis examines black carbon, nitrogen dioxide, and ultrafine particle measurements collected by Google Street View vehicles across the city. Includes geospatial data processing, route-based pollution exposure calculations, and visualization of air quality correlations along cycling paths.</summary>
   <content type="html">&lt;p&gt;&lt;em&gt;Dataset retrieved from: Utrecht University &amp;amp; Google, 2021, via Google Environmental Insights Explorer (August 2021)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The Jupyter notebook used to do this analysis &lt;a href=&quot;https://mybinder.org/v2/gist/Thomascountz/7ff85c4f5543fbfc2aae5a8fdfbdd586/HEAD&quot;&gt;is available here as a Binder&lt;/a&gt; and &lt;a href=&quot;https://gist.github.com/Thomascountz/7ff85c4f5543fbfc2aae5a8fdfbdd586&quot;&gt;here as a Gist&lt;/a&gt; for you to explore!&lt;/p&gt;

&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;I used to be a bicycle taxi driver, and I remember coming home after a day of work covered in soot from riding along the roads all day. It was a gross, yet kind of fascinating, experience to realize that not only was this stuff in the air covering my skin, I was also spending the day breathing it in.&lt;/p&gt;

&lt;p&gt;Due to global warming, our consciousness is ever-focused on air pollution; especially within our cities. Many cities are, therefore, attempting to reduce emissions by reducing the amount of cars on the road and increasing the amount of bikes.&lt;/p&gt;

&lt;p&gt;This is old news for cities like Copenhagen, Denmark. Their strong bike culture is only surpassed by their comprehensive bike infrastructure. So when The City of Copenhagen collaborated with Utrecht University and Google to map the city’s air pollution, I was eager to get my hands on the data.&lt;/p&gt;

&lt;p&gt;Between November 2018 and February 2020, Google strapped three different air quality sensors on their Street View vehicles and took thousands of measurements throughout the city. Today, we’re going to take a look at the data and start to answer the question: what’s the healthiest bike route between A and B, based on air quality?&lt;/p&gt;

&lt;h2 id=&quot;data-ingestion--initial-aggregation&quot;&gt;Data Ingestion &amp;amp; Initial Aggregation&lt;/h2&gt;

&lt;p&gt;The data can be found on &lt;a href=&quot;https://www.opendata.dk/city-of-copenhagen/airview&quot;&gt;Open Data DK’s website&lt;/a&gt;, and we can use the download link there to pull the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.geojson&lt;/code&gt; file directly into our project.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;https://kkkortdata.spatialsuite.dk/airview/CAV_25May2021.geojson&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;remote_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;urllib&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;urlopen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;raw_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gpd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;remote_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s take a preview of the data.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;raw_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;head&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;FID&lt;/th&gt;
      &lt;th&gt;Shape_Leng&lt;/th&gt;
      &lt;th&gt;ROAD_FID&lt;/th&gt;
      &lt;th&gt;Mixed_NO2&lt;/th&gt;
      &lt;th&gt;Mixed_UFP&lt;/th&gt;
      &lt;th&gt;Mixed_BC&lt;/th&gt;
      &lt;th&gt;geometry&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;0&lt;/td&gt;
      &lt;td&gt;58.821698&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;-999999&lt;/td&gt;
      &lt;td&gt;-999999&lt;/td&gt;
      &lt;td&gt;-999999.0&lt;/td&gt;
      &lt;td&gt;LINESTRING (12.57828 55.69392, 12.57752 55.69423)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;31.898519&lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;10&lt;/td&gt;
      &lt;td&gt;18500&lt;/td&gt;
      &lt;td&gt;0.8&lt;/td&gt;
      &lt;td&gt;LINESTRING (12.62463 55.63461, 12.62473 55.63433)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;36.276259&lt;/td&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt;9&lt;/td&gt;
      &lt;td&gt;12800&lt;/td&gt;
      &lt;td&gt;0.8&lt;/td&gt;
      &lt;td&gt;LINESTRING (12.58604 55.60184, 12.58595 55.602…&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt;40.416066&lt;/td&gt;
      &lt;td&gt;4&lt;/td&gt;
      &lt;td&gt;9&lt;/td&gt;
      &lt;td&gt;10700&lt;/td&gt;
      &lt;td&gt;0.7&lt;/td&gt;
      &lt;td&gt;LINESTRING (12.58629 55.59317, 12.58569 55.59305)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;4&lt;/td&gt;
      &lt;td&gt;32.734397&lt;/td&gt;
      &lt;td&gt;5&lt;/td&gt;
      &lt;td&gt;13&lt;/td&gt;
      &lt;td&gt;14500&lt;/td&gt;
      &lt;td&gt;1.1&lt;/td&gt;
      &lt;td&gt;LINESTRING (12.58135 55.59200, 12.58130 55.59171)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The measurements have the following units:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Black Carbon: μg/m³&lt;/li&gt;
  &lt;li&gt;Nitrogen Dioxide: μg/m³&lt;/li&gt;
  &lt;li&gt;Ultrafine Particles: particles/cm³&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Open Data DK site mentions that “some missing data points are listed in datasets as -999999”, so we’ll go ahead and remove any rows in our data that contain this value.&lt;/p&gt;

&lt;p&gt;Note that we could change this value to, let’s say, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt;, however, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; will mean something very specific when we look at the distribution later. We could also be sure not to remove the entire row if only one data point is missing, but for my initial analysis, I’m opting to more simply get rid of everything to keep all Series in the DataFrame the same dimension.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;cleaned_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;raw_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;drop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;raw_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;raw_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mixed_BC&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;999999&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;raw_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mixed_NO2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;999999&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;raw_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mixed_UFP&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;999999&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we can look at the correlation between each measurement. I &lt;em&gt;assume&lt;/em&gt; there’s a strong correlation: “where there’s high concentration of air pollution of type ‘x’, there’s probably a high concentration of air pollution of type ‘y’”, but it’s always good to check assumptions.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;cleaned_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mixed_BC&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mixed_NO2&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mixed_UFP&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;corr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;—&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Mixed_BC&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Mixed_NO2&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Mixed_UFP&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Mixed_BC&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;1.000000&lt;/td&gt;
      &lt;td&gt;0.938192&lt;/td&gt;
      &lt;td&gt;0.790519&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Mixed_NO2&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;0.938192&lt;/td&gt;
      &lt;td&gt;1.000000&lt;/td&gt;
      &lt;td&gt;0.702058&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Mixed_UFP&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;0.790519&lt;/td&gt;
      &lt;td&gt;0.702058&lt;/td&gt;
      &lt;td&gt;1.000000&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;And we can take a look at another statistic that might be interesting, the overall mean:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;cleaned_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mixed_NO2&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mixed_UFP&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mixed_BC&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Mixed_NO2       17.572950
Mixed_UFP    14280.222977
Mixed_BC         1.131806
dtype: float64
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s worth noting that these averages are similar to the ones published on &lt;a href=&quot;https://insights.sustainability.google/labs/airquality&quot;&gt;Google’s project site&lt;/a&gt;, which gives me a pretty good confidence that I’ve loaded the data correctly. The difference between my results and theirs may be that fact that I likely dropped some data while doing cleanup.&lt;/p&gt;

&lt;p&gt;Google also shows the distributions of the amounts of pollutant per road section, so we can go ahead and look at that too.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;fig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subplots&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nrows&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ncols&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;figsize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;33&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Black Carbon&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;cleaned_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mixed_BC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bins&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;xkcd:moss green&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rwidth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;legend&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Nitrogen Dioxide&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;cleaned_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mixed_NO2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bins&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;xkcd:moss green&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rwidth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;legend&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Ultrafine Particles&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;cleaned_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mixed_UFP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bins&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;xkcd:moss green&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rwidth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;legend&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/copenhagen_pollution_distribution.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now for the interesting bit: let’s take a look at a map of the data. &lt;a href=&quot;https://geopandas.org/&quot;&gt;GeoPandas&lt;/a&gt; quickly lets you plot a Series or DataFrame containing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;geometries&lt;/code&gt; that it created based on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;geojson&lt;/code&gt; data we imported.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;fig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subplots&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nrows&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ncols&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;figsize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;33&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;21&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Black Carbon&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_facecolor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;xkcd:grey&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;cleaned_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mixed_BC&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;legend&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;legend_kwds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Black Carbon&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;orientation&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;horizontal&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;YlOrRd&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Nitrogen Dioxide&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_facecolor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;xkcd:grey&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;cleaned_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mixed_NO2&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;legend&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;legend_kwds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Nitrogen Dioxide&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;orientation&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;horizontal&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;YlOrRd&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Ultrafine Particles&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_facecolor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;xkcd:grey&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;cleaned_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mixed_UFP&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;legend&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;legend_kwds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Ultrafine Particles&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;orientation&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;horizontal&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;YlOrRd&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/copenhagen_pollution_maps.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I love these maps! One, I think they’re beautiful (thank you &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;matplotlib&lt;/code&gt;!) and two, It’s fascinating that there’s no underlying map of Copenhagen here. The streets represent the data points and only the streets represented in the data are projected onto the plot/map.&lt;/p&gt;

&lt;h2 id=&quot;route-querying--data-joining&quot;&gt;Route Querying &amp;amp; Data Joining&lt;/h2&gt;

&lt;p&gt;Querying for a route is done via the Google Maps API, or more specifically, the Google Maps Python client library. In this example, we’re being sure to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bicycling&lt;/code&gt; as the mode so that Google Maps knows to optimize for bicycling routes. It would also be interesting to query for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;walking&lt;/code&gt; routes!&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;gmaps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;googlemaps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GOOGLE_DIRECTIONS_API_KEY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;starting_location&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Øste Gasvæk Teater&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ending_location&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Copenhagen Boulders&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;directions_result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gmaps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;directions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;starting_location&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ending_location&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;bicycling&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;googlemaps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exceptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ApiError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;It&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;s likely that Google cannot find one or both locations that you&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ve entered&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;directions_result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;It&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;s likely that Google cannot find one or both locations that you&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ve entered&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The JSON returned from the API has a ton of information that you might need to display the route to the user: HTML snippets, warnings/cautions, street names, and even landmarks! However, we don’t need any of that. The only thing we’re interested in is the “polyline.” &lt;a href=&quot;https://developers.google.com/maps/documentation/utilities/polylineutility?hl=de&quot;&gt;Google Polylines&lt;/a&gt; are encoded latitude/longitude pairs. This line encodes the geometries of the entire route. As an example, here is the polyline of the route we’ll look at below:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{x_sIskxkAAuFDKvAG^E?_@@mB?_BB]Lc@T_@POz@]dG{BrJyDnBu@f@Wz@o@fAo@^QdAYxBa@zCoAdGyDjDwBz@g@rAe@dAWjD_@jBOh@B`@FFHNJn@p@nB~CzAdCrBjD~AnClAxBpA~BTp@hApBPTTNLQb@cAZs@v@oBVqANe@p@{A|@kBP_@b@i@|@aAVMd@OpAk@hDsAfCaAjEcB|Ao@nB~ArBdBfCtBlCvBlFbEbDnC|CbCpB~APNNBJFl@b@LFt@C~@U|@e@zAY`AWJI|@W|Ae@DBF?`@KZCJ@DQBILUpAm@PMj@QNEjC{@b@Sd@QjA[n@NVTr@zAzA|DXhARdA`@rDZtCZzAfAbEd@~Ax@~ArAlCbFrKrAnCZl@JZVfBHt@Nb@pEtJbCdFbBvDnBhEdKtYzAlEp@fBTj@B\\Hp@`@hA`DdJx@lCt@tBj@`Br@|Bt@`CnApDDHLB`@BVf@T^b@n@`@b@bAn@h@Rr@Rd@FrAJhDJr@Jp@Vr@f@j@n@r@lAP\\Xt@\\xANxAd@nFZ`Ef@rGb@tGf@`GFf@NvATtDTfDHfAZ~Bl@rC`@tAn@rAj@v@nD`DbAv@xAbA`BvAl@j@rDjDz@v@\\RHDf@D~DcBhD{ArD_BjBy@p@QhBu@vBw@b@M|@g@Lt@r@z@fFvF|@t@jAv@z@`@n@Rp@PZDlAL`A?hAIlB]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We capture this in a variable&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;pline&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;directions_result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;overview_polyline&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;points&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And then use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;polyline&lt;/code&gt; library to decode it as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;geojson&lt;/code&gt;, or more specifically, as long/lat pairs that can be fed into a GeoPandas DataFrame:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;decoded_polyline&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;polyline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;geojson&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;route_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;DataFrame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;decoded_polyline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;columns&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;route&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gpd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;GeoDataFrame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;route_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;geometry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gpd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;points_from_xy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;route_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;route_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;crs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cleaned_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;crs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We can then use the route data to overlay onto our original map. We do this by using a spatial index built by GeoPandas using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RTree&lt;/code&gt; to quickly query based on a spatial predicate. In this case, we’re looking for all data points that are covered by a box that fits around our route.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;route_boundary = shapely.geometry.box(*route.total_bounds).buffer(0.01)
overlap = cleaned_data.iloc[cleaned_data.sindex.query(route_boundary, predicate=&apos;covers&apos;)]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;fig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subplots&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nrows&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ncols&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;figsize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;33&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;21&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_aspect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_facecolor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;xkcd:grey&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;cleaned_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mixed_UFP&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;YlOrRd&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;route&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;marker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;yellow&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;markersize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_aspect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_facecolor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;xkcd:grey&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;overlap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mixed_UFP&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;YlOrRd&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;route&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;marker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;blue&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;markersize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/copenhagen_route_overlay.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is primarily for visual reasons: to see the route placed onto the map, and to also provide a “zoomed in” version. A zoomed in version would be easier with an interactive mapping library, such as Plotly, but there’s no reason we can’t use a little bit of geometric querying to help us!&lt;/p&gt;

&lt;p&gt;Next, for what I’m really interested in: which of our air quality data points intersect with our route. Here, I’m buffering the route geometry by a tiny amount to turn it from a 1D point geometry to a 2D polygon. This is because the precision on the longitude and latitude coordinates (and thus the geometry coordinates) is so high, I often found that the points from the route and the lines from the air quality data wouldn’t intersect at all!&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;intersections&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cleaned_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;iloc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cleaned_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sindex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;query_bulk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;route&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;geometry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.0001&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;predicate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;intersects&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;fig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subplots&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nrows&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ncols&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;figsize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;33&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;21&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Black Carbon&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_facecolor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;xkcd:grey&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;intersections&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mixed_BC&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;legend&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;legend_kwds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Black Carbon&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;orientation&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;horizontal&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;YlOrRd&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Nitrogen Dioxide&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_facecolor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;xkcd:grey&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;intersections&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mixed_NO2&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;legend&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;legend_kwds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Nitrogen Dioxide&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;orientation&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;horizontal&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;YlOrRd&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Ultrafine Particles&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_facecolor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;xkcd:grey&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;intersections&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mixed_UFP&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;legend&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;legend_kwds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Ultrafine Particles&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;orientation&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;horizontal&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;YlOrRd&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/copenhagen_polluted_route.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now with this data, we can aggregate the air quality data filtered by the route to get some meaningful statistics like how many times more polluted is our route compared to the average:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;intersections&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mixed_NO2&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mixed_UFP&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mixed_BC&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cleaned_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mixed_NO2&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mixed_UFP&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mixed_BC&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Mixed_NO2    1.637403
Mixed_UFP    1.432781
Mixed_BC     1.518163
dtype: float64
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Or what’s the maximum average exposure along the route:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;intersections&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mixed_NO2&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mixed_UFP&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mixed_BC&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Mixed_NO2       52.0
Mixed_UFP    38500.0
Mixed_BC         3.0
dtype: float64
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;

&lt;p&gt;The Jupyter notebook used to do this analysis &lt;a href=&quot;https://mybinder.org/v2/gist/Thomascountz/7ff85c4f5543fbfc2aae5a8fdfbdd586/HEAD&quot;&gt;is available here as a Binder&lt;/a&gt; and &lt;a href=&quot;https://gist.github.com/Thomascountz/7ff85c4f5543fbfc2aae5a8fdfbdd586&quot;&gt;here as a Gist&lt;/a&gt; for you to explore!&lt;/p&gt;

&lt;p&gt;Some interesting ideas:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Exposure amounts based on time. The Google Maps API returns a Polyline and duration for each part of the trip!&lt;/li&gt;
  &lt;li&gt;Using machine learning to fill in gaps in data, what about those &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-999999&lt;/code&gt; data points or roads that aren’t represented at all?&lt;/li&gt;
  &lt;li&gt;Are there correlations between this data and any other data available at https://opendata.dk? Housing prices? Bike parking?&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>Sargable Queries &amp; MUL Indexes; or Why My Query is Slow</title>
   <link href="https://thomascountz.com/2021/07/09/sargable-queries-and-mysql-text-indexing"/>
   <updated>2021-07-09T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2021/07/09/sargable-queries-and-mysql-text-indexing</id>
   <summary type="html">My discovery of &quot;sargability&quot; in SQL queries, told through the lens of debugging a slow ActiveRecord query in a MySQL database.</summary>
   <content type="html">&lt;p&gt;You’re on Ops. Debugging &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Error&lt;/code&gt;-s in Invoicing Rails app, as usual. They’re stored in MySQL, and accessed through ActiveRecord and some helper methods. They’ve already been updated with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status_message&lt;/code&gt;, so now it’s time to dig in and investigate.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;001&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;column_names&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;backtrace&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;status_message&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;created_at&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;updated_at&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;runtime_environment&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;error_type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;ticket_id&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The sun beams through your window on another record-high heat wave afternoon, but you’re cool as a cucumber: you brought your sunglasses and a tall glass of trusty H2O.&lt;/p&gt;

&lt;p&gt;Let’s do this!&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where_message_like&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;NoMethodError: undfined method `is_approved_by_finance?&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\\&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&apos; for nil:NilClass&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;…&lt;/p&gt;

&lt;p&gt;…&lt;/p&gt;

&lt;p&gt;You check the time… it’s been over 45 seconds.&lt;/p&gt;

&lt;p&gt;…&lt;/p&gt;

&lt;p&gt;…maybe you shouldn’t have run this in the console??&lt;/p&gt;

&lt;p&gt;…&lt;/p&gt;

&lt;p&gt;Another 23.7 seconds pass and you decide to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CTRL-C&lt;/code&gt; your way out of there before you cause a production incident. You decide to just pull the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Error&lt;/code&gt; IDs from the JIRA ticket like a pleb. Suddenly the heat wave feels even hotter.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;But this is nagging you. I mean, what gives?? Why doesn’t this method ever just do the thing it says on the tin? Sure, if we use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.no_status&lt;/code&gt; scope (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WHERE status_message IS NOT NULL&lt;/code&gt;) it seems to work, but why can’t we get simple text search to work on this column? Sure there are a lot (a lot = &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;8612441&lt;/code&gt;) of rows, but I feel like I’ve seen text searching work before. Should we switch to &lt;a href=&quot;https://www.postgresql.org/docs/13/textsearch.html&quot;&gt;Postgres&lt;/a&gt;?&lt;/p&gt;

&lt;h2 id=&quot;sargable--non-sargable&quot;&gt;Sargable &amp;amp; Non-sargable&lt;/h2&gt;
&lt;p&gt;Sargable is a gate keep-y portmanteau (&amp;lt;- also gate keep-y. A portmanteau is a word that smashes together the sounds and meaning of two words, like spoon + fork = spork) for “search argument-able.” We use the term to describe the ability of the database’s SQL optimizer to leverage an index. There are a few things that determine a query’s sargability (not sure if that’s a word), but a big one to look out for is using wildcards (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%&lt;/code&gt;) when doing text searches.&lt;/p&gt;

&lt;p&gt;Let’s look at the implementation of our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;where_message_like&lt;/code&gt; method to see if we might be running into something like this.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;001&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where_message_like&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;NoMethodError: undefined method `is_approved_by_finance?&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\\&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&apos; for nil:NilClass&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;SELECT `errors`.* FROM `errors`  WHERE (message like &apos;%NoMethodError: undefined method `is_approved_by_finance?&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&apos;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; for nil:NilClass%&apos;)&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Aha, so we have a wildcard (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%&lt;/code&gt;) on either end of string in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WHERE&lt;/code&gt; clause:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;s1&quot;&gt;&apos;%NoMethodError: undefined method `is_approved_by_finance?&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt; for nil:NilClass%&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To understand why this is a problem, let’s pretend you’re the SQL query optimizer. Let’s say that I ask you to find all of the words in the dictionary that contain the substring &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entries&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;word&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;LIKE&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;%get%&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You’ll have to scan through &lt;em&gt;every&lt;/em&gt; word (row) in the dictionary (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;entries&lt;/code&gt; table) to find all of the instances:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
- GETaway
- fidGETy
- veGETal
- exeGETe
- nugGETy
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If, instead, I asked you to get all of the words in the dictionary that &lt;em&gt;begin&lt;/em&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get&lt;/code&gt;, you could just flip to the correct page and give me all the words! In this way, the dictionary being in alphabetical order is a type of index:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entries&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;word&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;LIKE&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;get%&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
- GETaway
- GETable
- GETting
- GETters
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;“But, Thomas,” I hear you asking, “what if we want to do a substring search rather than search from the beginning of the string?”&lt;/p&gt;

&lt;p&gt;That’s a great question! For our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Error&lt;/code&gt; scenario, that’s seldom the case, but there are more intentional indexes we &lt;em&gt;could&lt;/em&gt; use to get us a more robust sub-string search, like &lt;a href=&quot;https://dev.mysql.com/doc/refman/5.6/en/fulltext-search.html&quot;&gt;MySQL’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FULLTEXT&lt;/code&gt;&lt;/a&gt; or &lt;a href=&quot;https://www.postgresql.org/docs/9.5/datatype-textsearch.html&quot;&gt;Postgres’ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ts_vector&lt;/code&gt;&lt;/a&gt;. It’s worth noting that full-text indexes aren’t magic, and your tables and queries have to change to support them.&lt;/p&gt;

&lt;p&gt;For our example, we don’t have these things readily at our disposal, so we’ll continue with the assumption that we’re searching for a substring at the &lt;em&gt;beginning&lt;/em&gt; of the column’s contents.&lt;/p&gt;

&lt;h2 id=&quot;text--varchar&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TEXT&lt;/code&gt; &amp;amp; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VARCHAR&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;So now, let’s see this sargable business in action by looking at the query plans!&lt;/p&gt;

&lt;p&gt;First the non-sargable, double wildcard (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%&lt;/code&gt;), query:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;002&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;explain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where_message_like&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;NoMethodError: undefined method `is_approved_by_finance?&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\\&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&apos; for nil:NilClass&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;EXPLAIN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ms&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;no&quot;&gt;EXPLAIN&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`errors`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`errors`&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;like&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;%NoMethodError: undefined method `is_approved_by_finance?\&apos; for nil:NilClass%&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+----+-------------+----------------+------+---------------+------+---------+------+---------+-------------+&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;select_type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;          &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;possible_keys&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key_len&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ref&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rows&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Extra&lt;/span&gt;       &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+----+-------------+----------------+------+---------------+------+---------+------+---------+-------------+&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;  &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;SIMPLE&lt;/span&gt;      &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;errors&lt;/span&gt;         &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ALL&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NULL&lt;/span&gt;          &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NULL&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6739497&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Using&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+----+-------------+----------------+------+---------------+------+---------+------+---------+-------------+&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.00&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then the sargable, search from the beginning of the contents, query:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;003&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;explain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;message LIKE &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&apos;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;NoMethodError: undefined method `is_approved_by_finance?&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\\&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&apos; for nil:NilClass%&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&apos;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;EXPLAIN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ms&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;no&quot;&gt;EXPLAIN&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`errors`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`errors`&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;LIKE&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;NoMethodError: undefined method `is_approved_by_finance?\&apos; for nil:NilClass%&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+----+-------------+----------------+------+---------------+------+---------+------+---------+-------------+&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;select_type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;          &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;possible_keys&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key_len&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ref&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rows&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Extra&lt;/span&gt;       &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+----+-------------+----------------+------+---------------+------+---------+------+---------+-------------+&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;  &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;SIMPLE&lt;/span&gt;      &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;errors&lt;/span&gt;         &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ALL&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NULL&lt;/span&gt;          &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NULL&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6739497&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Using&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+----+-------------+----------------+------+---------------+------+---------+------+---------+-------------+&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.00&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Oh crap. What gives?! They look exactly the same! Why isn’t MySQL optimizing!? We sarged(?) it!&lt;/p&gt;

&lt;p&gt;Unfortunately for us, our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;message&lt;/code&gt; doesn’t have an index. Further, the column is a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TEXT&lt;/code&gt; column, and (given its extremely varied and undefined length) if we want to add an index, MySQL &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/create-index.html&quot;&gt;requires us to specify a &lt;em&gt;prefix length&lt;/em&gt;&lt;/a&gt;, which tells MySQL how much of the &lt;em&gt;beginning&lt;/em&gt; of the string should be indexed. Further further, only the InnoDB tables can be indexed this way. (Our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;errors&lt;/code&gt; table uses the InnoDB engine, so no problem there).&lt;/p&gt;

&lt;h2 id=&quot;how-it-looks-with-an-index&quot;&gt;How it looks with an index&lt;/h2&gt;
&lt;p&gt;Even though our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;message&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TEXT&lt;/code&gt; column doesn’t have an index, our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ticket_id&lt;/code&gt; column does:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;004&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;explain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;errors&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;EXPLAIN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2.6&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ms&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;no&quot;&gt;EXPLAIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;errors&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+---------------------+--------------+------+-----+---------+----------------+&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Field&lt;/span&gt;               &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Type&lt;/span&gt;         &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Null&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Default&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Extra&lt;/span&gt;          &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+---------------------+--------------+------+-----+---------+----------------+&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;                  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;      &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NO&lt;/span&gt;   &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PRI&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NULL&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;auto_increment&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;             &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;         &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;YES&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;     &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NULL&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;                &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;backtrace&lt;/span&gt;           &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;         &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;YES&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;     &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NULL&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;                &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status_message&lt;/span&gt;      &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;YES&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MUL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NULL&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;                &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt;          &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;datetime&lt;/span&gt;     &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;YES&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;     &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NULL&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;                &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;updated_at&lt;/span&gt;          &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;datetime&lt;/span&gt;     &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;YES&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;     &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NULL&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;                &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;runtime_environment&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;         &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;YES&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;     &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NULL&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;                &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error_type&lt;/span&gt;          &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;YES&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MUL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NULL&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;                &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ticket_id&lt;/span&gt;           &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;YES&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MUL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NULL&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;                &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+---------------------+--------------+------+-----+---------+----------------+&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rows&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.00&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ticket_id&lt;/code&gt; column is of type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;varchar(255)&lt;/code&gt;, which means we have no problem putting an index on there. And that’s exactly what we did! In our case, it uses a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MUL&lt;/code&gt; (or “Multiple”) key, meaning that its value is used at the beginning of a non-unique key (multiple records can have the same index).&lt;/p&gt;

&lt;p&gt;Let’s look at how our knowledge of sargable wildcard queries plays a role with this type of column.&lt;/p&gt;

&lt;p&gt;First the non-sargable, double wildcard (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%&lt;/code&gt;), query:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;005&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;explain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;ticket_id LIKE \&apos;%A00057440\&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;EXPLAIN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ms&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;no&quot;&gt;EXPLAIN&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`errors`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`errors`&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticket_id&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;LIKE&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;%A00057440%&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+----+-------------+----------------+------+---------------+------+---------+------+---------+-------------+&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;select_type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;          &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;possible_keys&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key_len&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ref&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rows&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Extra&lt;/span&gt;       &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+----+-------------+----------------+------+---------------+------+---------+------+---------+-------------+&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;  &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;SIMPLE&lt;/span&gt;      &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;errors&lt;/span&gt;         &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ALL&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NULL&lt;/span&gt;          &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NULL&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8612441&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Using&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+----+-------------+----------------+------+---------------+------+---------+------+---------+-------------+&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.00&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then the sargable, search from the beginning of the contents, query:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;006&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;explain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;ticket_id LIKE \&apos;%A00057440\&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;EXPLAIN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.2&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ms&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;no&quot;&gt;EXPLAIN&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`errors`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`errors`&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticket_id&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;LIKE&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;A00057440%&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+----+-------------+----------------+-------+--------------------------------------+--------------------------------------+---------+------+------+-----------------------+&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;select_type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;          &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;type&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;possible_keys&lt;/span&gt;                        &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;                                  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key_len&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ref&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rows&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Extra&lt;/span&gt;                 &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+----+-------------+----------------+-------+--------------------------------------+--------------------------------------+---------+------+------+-----------------------+&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;  &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;SIMPLE&lt;/span&gt;      &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;errors&lt;/span&gt;         &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;range&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index_errors_on_ticket_id&lt;/span&gt;            &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index_errors_on_ticket_id&lt;/span&gt;            &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;768&lt;/span&gt;     &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;  &lt;span class=&quot;mi&quot;&gt;214&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Using&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;condition&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+----+-------------+----------------+-------+--------------------------------------+--------------------------------------+---------+------+------+-----------------------+&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.00&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Aha! In the first query plan, MySQL tells us it will scan through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ALL&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;8612441&lt;/code&gt; rows &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Using where&lt;/code&gt;. The second, sargable query plan tells us that it will only need to scan through a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;range&lt;/code&gt; of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;214&lt;/code&gt; by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Using index condition&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;Wow… a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%&lt;/code&gt; can make a huge difference.&lt;/p&gt;

&lt;p&gt;I’ll leave it as an exercise for the reader to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SET profiling = 1;&lt;/code&gt; to see the real cost. &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/show-profile.html&quot;&gt;See documentation here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;back-on-ops&quot;&gt;Back on Ops&lt;/h2&gt;
&lt;p&gt;Ahhh, the sun is finally starting to set. We haven’t finished (or really even begun) our investigation, but we’re a little wiser about MySQL’s indexing patterns.&lt;/p&gt;

&lt;p&gt;How can we use this knowledge to make our jobs a little easier and beat the heat?&lt;/p&gt;

&lt;p&gt;I guess the first thing to know is that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;where_message_like&lt;/code&gt; could take a very long time if there are no other &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WHERE&lt;/code&gt; conditions. This is because it’s a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TEXT&lt;/code&gt; column with no index; but even if it had an index, using a wildcard at the front of our search string isn’t doing us any favors.&lt;/p&gt;

&lt;p&gt;Also, it’s nice to know that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ticket_id&lt;/code&gt; column &lt;em&gt;is&lt;/em&gt; indexed, and if we want to search, we can use a sargable query to get our results much faster! Having a consistent format for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ticket_id&lt;/code&gt; might be something to consider; if we can deduce what the beginning of the string is, finding all of the relevant records could be super efficient!&lt;/p&gt;

&lt;p&gt;Lastly, if we did want to index the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;message&lt;/code&gt; column, we have a few options:&lt;/p&gt;

&lt;p&gt;We could migrate it from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TEXT&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VARCHAR&lt;/code&gt;. The advantage there is gaining access to an easy index, but we give up the &lt;a href=&quot;https://stackoverflow.com/questions/25300821/difference-between-varchar-and-text-in-mysq0l&quot;&gt;benefits of choosing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TEXT&lt;/code&gt;&lt;/a&gt; in the first place.&lt;/p&gt;

&lt;p&gt;We could use a &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/create-index.html#create-index-column-prefixes&quot;&gt;column prefix key&lt;/a&gt; by specifying a prefix limit. That might get us some good bang for our buck if we use sargable queries.&lt;/p&gt;

&lt;p&gt;Or, we could invest in a &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/create-index.html#create-index-fulltext&quot;&gt;full-text index&lt;/a&gt; and really leverage what InnoDB can do! However, this will require us to alter our table a bit more and adjust our query patterns. Whether or not this plays nice with Rails is another question we’d need to answer.&lt;/p&gt;

&lt;p&gt;Nevertheless, it’s 5:00 p.m. (4:56 p.m., but who’s counting), so it’s time to call it a day and grab the last few rays of sun down by the water.&lt;/p&gt;

&lt;p&gt;Before you &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;⌘-Q&lt;/code&gt; out of Slack, you remember something. “…didn’t Brianna say that they were going to look at this issue??”&lt;/p&gt;

&lt;p&gt;You refresh JIRA:&lt;/p&gt;

&lt;p&gt;“Status: Done.”&lt;/p&gt;

&lt;p&gt;You give a shout out to Brianna and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;⌘-SHIFT-Q&lt;/code&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Rocket Lab Telemetry From Video</title>
   <link href="https://thomascountz.com/2021/05/15/rocket-lab-telemetry-from-video"/>
   <updated>2021-05-15T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2021/05/15/rocket-lab-telemetry-from-video</id>
   <summary type="html">Extracting rocket telemetry data from a YouTube live stream using Google Cloud Video Intelligence API&apos;s OCR capabilities. This project walks through video ingestion, text detection across thousands of frames, JSON data wrangling with jq, and Ruby-based data cleanup to produce velocity and altitude datasets from Rocket Lab&apos;s Electron launch anomaly.</summary>
   <content type="html">&lt;h2 id=&quot;the-anomaly&quot;&gt;The Anomaly&lt;/h2&gt;

&lt;p&gt;Early today, &lt;a href=&quot;https://www.rocketlabusa.com/&quot;&gt;Rocket Lab&lt;/a&gt; launched their 20th Electron rocket, &lt;a href=&quot;https://www.rocketlabusa.com/missions/completed-missions/running-out-of-toes/&quot;&gt;Running out of Toes&lt;/a&gt; from their Launch Complex 1 	Mahia Peninsula in New Zealand.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;On one of our toughest days, our team operated with professionalism and worked swiftly to ensure the anomaly was managed safely. Our team is resilient, and our top priority remains to safely and reliably return to flight for our customers. We will learn from this, and we’ll be back on the pad again.
—&lt;a href=&quot;https://www.rocketlabusa.com/about-us/updates/rocket-lab-experiences-anomaly-during-launch/&quot;&gt;Peter Beck, Rocket Lab founder and chief executive&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Unfortunately, the mission ended in failure right around T+00:02:30 shortly after main engine cutoff and second stage separation. In the live stream, the second stage appears to ignite and then quickly cutoff.&lt;/p&gt;

&lt;p&gt;Rocket Lab &lt;a href=&quot;https://www.rocketlabusa.com/about-us/updates/rocket-lab-experiences-anomaly-during-launch/&quot;&gt;released a statement&lt;/a&gt; confirming that the anomaly had lead to the failure of the mission, but not yet releasing any preliminary data that might point to the cause of the failure, of course, pending the investigation.&lt;/p&gt;

&lt;p&gt;Days like today are a magnet for armchair engineers…&lt;/p&gt;

&lt;h2 id=&quot;the-data&quot;&gt;The Data&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/rocket-lab-stream.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is a single frame from the live stream right at T-00:00:00. On the right hand side, you can see that they helpfully show some telemetry: flight time, speed, and altitude, along with the video feed itself, and some stages or “gates” on the left hand side.&lt;/p&gt;

&lt;p&gt;When wanting to figure out what happened, besides the video, this was all the data we in the peanut gallery had.&lt;/p&gt;

&lt;p&gt;So, I did what any sane person would do and started going frame by frame to record the telemetry by hand in a spreadsheet. After I realized that the area of interest was about three minutes and thirty second long, and the stream was at 30 frames/second, I was looking at transcribing thousands of frames! Given that the data wasn’t really updating every single frame, the number was likely to be a lot lower, but still… The band of DIY rocket scientists were waiting; this couldn’t take all day!!&lt;/p&gt;

&lt;p&gt;I pulled the video down using &lt;a href=&quot;https://youtube-dl.org/&quot;&gt;youtube-dl&lt;/a&gt; just in case the video was taken down from Youtube. Rocket Lab isn’t known to do this, but SpaceX, for example, de-lists their videos and they can be difficult to find.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;youtube-dl https://www.youtube.com/watch?v&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Zw3sIUyfSfc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;google-video-intelligence-api&quot;&gt;Google Video Intelligence API&lt;/h2&gt;
&lt;p&gt;Luckily, this is the exact kind of job that neural networks are suited for. In fact, the first algorithms you learn about are ones used for &lt;a href=&quot;https://machinelearningmastery.com/handwritten-digit-recognition-using-convolutional-neural-networks-python-keras/&quot;&gt;handwritten digit recognition&lt;/a&gt;. These weren’t even handwritten, so the job could end up being quite easy! One challenge was that we’re dealing with thousands of frames, not just a single image. My personal toolkit doesn’t support analyzing video, so I reached for &lt;a href=&quot;https://cloud.google.com/video-intelligence/docs/text-detection&quot;&gt;Google Cloud Video Intelligence API &lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This service uses OCR (Optical Character Recognition) to detect and extract text from video. Perfect!&lt;/p&gt;

&lt;p&gt;After configuring and deploying the resources in my Google Cloud account. I uploaded the video to Google Cloud Storage and called the REST API&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;request.json&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;inputUri&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;gs://thomascountz/rocket-lab-20-launch-anomaly.mp4&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;features&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;TEXT_DETECTION&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;videoContext&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;textDetectionConfig&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;languageHints&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;en-US&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; POST &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Authorization: Bearer &quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;gcloud auth application-default print-access-token&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Content-Type: application/json; charset=utf-8&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; @request.json &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  https://videointelligence.googleapis.com/v1/videos:annotate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;data-cleanuppresentation&quot;&gt;Data Cleanup/Presentation&lt;/h2&gt;
&lt;p&gt;What came back was exciting and a &lt;em&gt;little&lt;/em&gt; unexpected. I guess I didn’t know what to expect (it’s not like I had read any documentation or anything).&lt;/p&gt;

&lt;p&gt;Firstly, the API responds with a job name that then you can later use to query for the results and other metadata. I used that name to send another API request and I got back results almost instantly. The entire job took maybe 2 second to complete and I got something back like this:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;projects/REDACTED/locations/us-west1/operations/REDACTED&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;metadata&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;@type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type.googleapis.com/google.cloud.videointelligence.v1.AnnotateVideoProgress&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;annotationProgress&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;inputUri&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/thomascountz/rocket-lab-20-launch-anomaly.mp4&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;progressPercent&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;startTime&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2021-05-15T15:39:55.442511Z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;updateTime&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2021-05-15T15:41:17.361940Z&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;done&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;response&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;@type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type.googleapis.com/google.cloud.videointelligence.v1.AnnotateVideoResponse&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;annotationResults&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;inputUri&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/thomascountz/rocket-lab-20-launch-anomaly.mp4&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;segment&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;startTimeOffset&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;0s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;endTimeOffset&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;218.100s&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;textAnnotations&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2612&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;segments&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
              &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;segment&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;startTimeOffset&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;98.400s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;endTimeOffset&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;98.500s&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;confidence&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;frames&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;rotatedBoundingBox&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;vertices&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;x&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.7729167&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;y&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.05277778&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;snip...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s a heavily truncated version, but the things I noticed were:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The result was so big, I was having trouble exploring it all at once.&lt;/li&gt;
  &lt;li&gt;The data was not in order by frame count the way a video is. This makes sense since OCR acts on a single image and Google Cloud most likely parallelized the work for each image before collecting it into a single result (fan-out/fan-in).&lt;/li&gt;
  &lt;li&gt;There wasn’t an obvious way to tell which frames contained the data I cared about (time, speed, altitude) and for each frame, it wasn’t entirely clear which bit of text mapped to which data point (altitude and speed are both numbers).&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;1-the-results-were-big&quot;&gt;1. The results were big&lt;/h3&gt;
&lt;p&gt;Ha! 1116879 lines of unminified JSON never hurt anyone! Although my computer choked on opening the response in something like vim, I used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jq&lt;/code&gt; to begin to explore the data.&lt;/p&gt;

&lt;p&gt;After getting an understanding for the shape, I actually “trimmed” of the pieces of data I didn’t care about by mapping through each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;textAnnotation&lt;/code&gt; and only keeping the text found and the start and stop timestamps. (This ended up making my life more difficult… More on this later.)&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jq &lt;span class=&quot;s1&quot;&gt;&apos;.response.annotationResults[0].textAnnotations[] |= { text: .text, start: .segments[0].segment.startTimeOffset, end: .segments[0].segment.endTimeOffset }&apos;&lt;/span&gt; response.json &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; response_trim.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This produced something a little more manageable:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;projects/REDACTED/locations/us-west1/operations/REDACTED&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;metadata&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;@type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type.googleapis.com/google.cloud.videointelligence.v1.AnnotateVideoProgress&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;annotationProgress&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;inputUri&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/thomascountz/rocket-lab-20-launch-anomaly.mp4&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;progressPercent&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;startTime&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2021-05-15T15:39:55.442511Z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;updateTime&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2021-05-15T15:41:17.361940Z&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;done&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;response&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;@type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type.googleapis.com/google.cloud.videointelligence.v1.AnnotateVideoResponse&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;annotationResults&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;inputUri&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/rocket_lab_001/end-to-end-flight.mp4&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;segment&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;startTimeOffset&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;0s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;endTimeOffset&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;218.100s&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;textAnnotations&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2612&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;start&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;98.400s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;end&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;98.500s&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;3467&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;start&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;110.300s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;end&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;110.300s&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;1362&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;start&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;73s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;end&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;73.100s&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2392&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;start&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;94.800s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;end&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;94.900s&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2079&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;start&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;89.300s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;end&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;89.300s&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;snip&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;2-data-out-of-order&quot;&gt;2. Data out of order&lt;/h3&gt;
&lt;p&gt;With the timestamps in place, the data being out of order was no longer an issue. I easily parsed the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;start&lt;/code&gt; value of each object into a float, and then sorted in ascending order.&lt;/p&gt;

&lt;p&gt;Note that this was the time in seconds based on the video I uploaded to cloud storage which was only about three minutes of the hour+ Youtube stream.&lt;/p&gt;

&lt;h3 id=&quot;3-knowing-what-the-text-means-for-each-timestamp&quot;&gt;3. Knowing what the text means for each timestamp&lt;/h3&gt;

&lt;p&gt;Now that things were sorted (pun intended), I had to then understand what each of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text&lt;/code&gt; values represented. For this, I moved to Ruby. (I told you that this was brute force data munging and worst practices. I hope you didn’t come here thinking I knew what I was doing).&lt;/p&gt;

&lt;p&gt;In Ruby, I read in the trimmed file as a hash, removed the metadata, converted everything to floats, integers, and symbols, and grouped by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;start&lt;/code&gt; timestamp.&lt;/p&gt;

&lt;p&gt;Yes, there can be, and were, many annotations that started at the same time. Not all of the data on the video was updated in sync, especially because the flight time was only updated once per second, but the telemetry data was updated more often. (Thanks for the high fidelity Rocket Lab!).&lt;/p&gt;

&lt;p&gt;I ended up with a hash that looked like this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ...snip...&lt;/span&gt;
  &lt;span class=&quot;mf&quot;&gt;205.6&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;7687&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;205.6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;205.6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;T+// OO:03:18&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;205.6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;206.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt;
  &lt;span class=&quot;mf&quot;&gt;205.7&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;7661&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;205.7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;206.1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt;
  &lt;span class=&quot;mf&quot;&gt;206.3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;7677&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;206.3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;206.3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt;
  &lt;span class=&quot;mf&quot;&gt;206.4&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;110.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;206.4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;207.8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;7689&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;206.4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;206.7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt;
  &lt;span class=&quot;mf&quot;&gt;206.6&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;T+// OO:03:19&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;206.6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;207.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt;
  &lt;span class=&quot;mf&quot;&gt;206.8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;7676&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;206.8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;206.8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt;
  &lt;span class=&quot;mf&quot;&gt;207.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;7656&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;207.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;207.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt;
  &lt;span class=&quot;mf&quot;&gt;207.1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;7647&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;207.1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;207.2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt;
  &lt;span class=&quot;mf&quot;&gt;207.3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;7649&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;207.3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;207.3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt;
  &lt;span class=&quot;mf&quot;&gt;207.4&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;7657&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;207.4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;207.4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt;
  &lt;span class=&quot;mf&quot;&gt;207.5&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;7669&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;207.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;207.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}]&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ...snip...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Just taking a look at the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:text&lt;/code&gt; values, we can see some integers, floats, and a string that starts with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;T+// &lt;/code&gt;; this formatting happens to map to the speed, altitude, and flight time, respectively.&lt;/p&gt;

&lt;p&gt;Remember earlier when I used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jq&lt;/code&gt; to delete a bunch of data that I thought I didn’t need? It turns out that that data contained the &lt;em&gt;bounding boxes&lt;/em&gt; for each annotation, that is for each piece of text found, the results give you four Cartesian coordinates that correspond to the x and y pixel locations of four corners of a square that describe &lt;em&gt;where&lt;/em&gt; in the frame the text was found. In other words, instead of relying on each data point coincidentally having a different format, I could have used the bounding boxes to query for each data point in a given frame. (Worst. Practices. I literally warned you about this…).&lt;/p&gt;

&lt;p&gt;I ran the hash through an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;each&lt;/code&gt; block that will make any programmer cry and ended up with four values for each frame: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;frame&lt;/code&gt;, which represents the frame in seconds, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;time&lt;/code&gt;, or flight time, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;speed&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;altitude&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;frame_data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;frame_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;frame_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;//&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;frame_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;frame_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;frame: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;time: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;speed: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;altitude: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Looking at this feels embarrassing, but you know what? It worked!&lt;/p&gt;

&lt;p&gt;Here’s the same slice of data from above:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ...snip...&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:frame&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;205.6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;T+// OO:03:18&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:speed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;7687&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:altitude&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:frame&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;205.7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:speed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;7661&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:altitude&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:frame&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;206.3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:speed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;7677&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:altitude&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:frame&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;206.4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:speed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;7689&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:altitude&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;110.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:frame&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;206.6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;T+// OO:03:19&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:speed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:altitude&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:frame&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;206.8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:speed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;7676&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:altitude&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:frame&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;207.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:speed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;7656&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:altitude&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:frame&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;207.1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:speed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;7647&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:altitude&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:frame&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;207.3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:speed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;7649&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:altitude&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:frame&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;207.4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:speed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;7657&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:altitude&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:frame&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;207.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:speed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;7669&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:altitude&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ...snip...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, all I had to do was clean up the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;T+//&lt;/code&gt; from the timestamp and ship this off to a CSV!&lt;/p&gt;

&lt;h2 id=&quot;the-results&quot;&gt;The Results&lt;/h2&gt;

&lt;p&gt;You can view the entire dataset on Google Sheets &lt;a href=&quot;https://docs.google.com/spreadsheets/d/1E59qpqNH_o3J20Fbkiy0ad3iUB_BJCvQ3-0sxsXnkRM/edit#gid=667080816&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/rocket-lab-velocity-data.png&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;/assets/images/rocket-lab-altitude-data.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is kind of where my armchair engineering stopped. I mean, I don’t really know much about rockets, I just love data! (I also love rockets, but I just don’t know enough to make guesses as to what happened). I shared this data with my rocket-knowing friends to see if they could make anything of it.&lt;/p&gt;

&lt;p&gt;What I do know is that things shouldn’t have ended around T+00:03:30 and that the green line (velocity) should have kept going up, but around T+00:02:30, right around main engine cut off and stage separation, it started going down and stayed down.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This was a fun project from a bad day. It really illustrates to me the power of motivation. This isn’t too dissimilar to my day job, but at work, I can’t just spin up cloud resources and the scale of the data is too large to try to manipulate in memory. That said, it was really fun to try to dive head first into a problem and come out on the other side having learned more about Google Cloud, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jq&lt;/code&gt;, rocket trajectories, and basic data manipulation.&lt;/p&gt;

&lt;p&gt;If I were to approach this problem again, I would keep the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rotatedBoundingBox&lt;/code&gt; data and use it to identify the meaning of the data. SpaceX’s Falcon 9 streams, for example, contain two readouts each of speed and altitude, one for the first and second stages. It would be impossible to distinguish between the two on format alone.&lt;/p&gt;

&lt;p&gt;Secondly, I’d get the data in SQL database right away (or at least store it somewhere where I can use a SQL-like API). Maybe I should up my data game, but my brain likes to think in SQL for problems like these; having to think procedurally in a loop was unintuitive for me.&lt;/p&gt;

&lt;p&gt;Thanks for reading! Let me know what you think or if you have any questions. Would you be interested in having raw telemetry data formatted and presented after a launch stream? I’m certainly impressed by the quality of the data coming through! See if you can spot MAX-Q in the velocity graph!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Hexo - Schematic Capture, Mechanical Design, &amp; PCB Layout</title>
   <link href="https://thomascountz.com/2021/05/13/schematic-mechanics-layout"/>
   <updated>2021-05-13T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2021/05/13/schematic-mechanics-layout</id>
   <summary type="html">A walkthrough of transforming a circuit design into a manufacturable PCB using KiCAD EDA. Covers schematic capture with electrical rules checking, mechanical design constraints for a wearable pendant form factor, custom footprint creation from datasheets, and PCB layout including trace routing and component placement for a hexagonal LED die.</summary>
   <content type="html">&lt;p&gt;&lt;img src=&quot;/assets/images/hexo_schematic_final.png&quot; alt=&quot;Final revision of Hexo&apos;s schematic&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Pictured above is the schematic for Hexo’s circuit. Like I mentioned earlier, reading and interpreting a schematic is a creative process akin to jazz… although in this case it is highly precise. Not only is this a graphical representation meant to aid human designers, each component and connection is backed by a computer model which can do things like check for output pin conflicts and missed connections, in a process called &lt;em&gt;electrical rules checking&lt;/em&gt;. I use a free open source tool called &lt;a href=&quot;https://www.kicad.org/&quot;&gt;KiCAD EDA&lt;/a&gt; (EDA: electrical design automation) for this work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schematic capture&lt;/strong&gt; is about the process of encoding a circuit into an EDA. KiCAD has a very large library of components, but also has tools for building new components, cataloging &lt;em&gt;datasheets&lt;/em&gt; (technical document and specification produced by component manufacturers), and exporting files to use in SPICE simulations.&lt;/p&gt;

&lt;p&gt;After schematic capture, I moved onto &lt;strong&gt;mechanical design.&lt;/strong&gt; All I knew was that I wanted Hexo to be worn as a pendant on a necklace. In electronics projects, mechanical design is concerned primarily with the &lt;em&gt;shape&lt;/em&gt; of the PCB as it’s often mounted without an enclosure or case. For Hexo, the PCB itself would be exposed as the final form factor and I was inspired by game pieces from one of my husband and my (mine?) favorite games, &lt;em&gt;Eclipse&lt;/em&gt;, pictured below. Eclipse is a sci-fi based game and besides the size and shape of its pieces, I was inspired by the futuristic and mysterious vibe of the game’s design.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/eclipse-pieces.jpg&quot; alt=&quot;Geometric boardgame pieces&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The mechanical design was constrained primarily by the size of the battery I planned to use (or rather the size of the mechanical clip that holds the battery) and the user experience of pressing a small button and reading the numbers. The important constraints limited how &lt;em&gt;small&lt;/em&gt; Hexo could be because I wanted Hexo to be as small as possible (because I thought it would look cooler).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/hexo_board_shape.jpg&quot; alt=&quot;Paper design of Hexo&apos;s PCB outline shape&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Other than that, the form factor was left to my aesthetic sense and what was feasibly manufacturable (these constraints were helpfully available on the manufacturer’s website).&lt;/p&gt;

&lt;p&gt;I went with a 30mm x 30mm square that had three corners cut away at 45º angles because… I thought it would look cooler (sensing a theme?) &lt;strong&gt;and&lt;/strong&gt; because the final surface area was within the tolerances of the manufacturer’s cheapest options. (Definitely cooler).&lt;/p&gt;

&lt;p&gt;Finally, the &lt;strong&gt;PCB Layout&lt;/strong&gt;, or the process by which you plan where to mount each component on the final board, was all that was left.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/bare_hexo_pcbs.jpg&quot; alt=&quot;Bare PCBs after being delivered&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/2021/05/13/prototype-design-simulation&quot;&gt;During prototyping&lt;/a&gt;, we used the THT (through-hole technology) components that had &lt;em&gt;leads&lt;/em&gt; that allow them to “plug-into” a breadboard. For PCBs, we can also use SMT (surface mount technology) components that, instead of having &lt;em&gt;leads&lt;/em&gt; that go into a hole, they have &lt;em&gt;pins&lt;/em&gt; that are soldered to &lt;em&gt;pads&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;These pads are where the electrical connection between the component pins and the rest of the circuit is made. Taken together, a group of pads make up a component’s &lt;em&gt;footprint&lt;/em&gt; or the overall shape and dimensions of the electrical connections between the component and the PCB.&lt;/p&gt;

&lt;p&gt;In the image above, you can see the various component footprints on the bare Hexo PCBs before they were assembled.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/battery_clip_footprint.png&quot; alt=&quot;Datasheet page for a battery clip&apos;s PCB footprint&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The battery clip datasheet, pictured above, shows the only footprint I had to design by hand in CAD for Hexo (the other components used standard footprints that were pre-designed in KiCAD). The footprint is a relatively simple three square design, but it was a lot of fun to get practice at taking specs and turning them into something that would leave the page.&lt;/p&gt;

&lt;p&gt;You can see the battery clip’s footprint as the three green squares in the layout diagram below. Note that the diagram is showing both the front and the back of Hexo at the same time and that the battery clip was mounted on the back.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/hexo_icon.png&quot; alt=&quot;PCB layout of Hexo&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The final large technical constraint, after the board shape and the component footprints, is the management of electrical &lt;em&gt;traces&lt;/em&gt;, or the copper “wires” that run (unseen) through the board and connect everything together.&lt;/p&gt;

&lt;p&gt;Traces are the things that connect the LED to the battery and the battery to the switch, for example. The way this is done has to do with the manufacturing process, which is step 4, so I can’t go into it here, but I will leave you &lt;a href=&quot;https://www.youtube.com/watch?v=ljOoGyCso8s&quot;&gt;this video link&lt;/a&gt; if you’re interested.&lt;/p&gt;

&lt;p&gt;There’s an art to PCB layout and there are tools to help make it easier. Like a lot of design, there’s a mix of technical and aesthetic attitudes that lead to a PCB being designed one way or another. When you begin the PCB layout process, you have, what is literally called, a &lt;em&gt;rats nest&lt;/em&gt; of traces and components that need to be untangled, taking into account electrical requirements, interference, manufacturability, etc.&lt;/p&gt;

&lt;p&gt;Now that Hexo was designed, and (nearly) every decision that could be made, was made, it was time to send the board to the manufacturer. After which, you’ll realize that you made a tiny mistake and hope that either the manufacturer will notice and fix it, or that it was tiny enough to have no impact.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Hexo - Prototyping, Design, Simulation</title>
   <link href="https://thomascountz.com/2021/05/13/prototype-design-simulation"/>
   <updated>2021-05-13T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2021/05/13/prototype-design-simulation</id>
   <summary type="html">Building an electronic six-sided die from concept to prototype. This post covers breadboard prototyping, pen-and-paper circuit analysis using Ohm&apos;s and Kirchhoff&apos;s laws, and SPICE simulation with LTspice and Falstad. Includes modifying a 555 timer roulette circuit from 9V/10-LED to 3V/6-LED, with transient analysis of LED timing behavior.</summary>
   <content type="html">&lt;p&gt;&lt;img src=&quot;/assets/images/hexo_gif.gif&quot; alt=&quot;Hexo cycling through six LEDs and landing on number four&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Pictured above is a printed circuit board or PCB. Dubbed Hexo, a play on the prefix &lt;em&gt;hexa-&lt;/em&gt;, meaning &lt;em&gt;having six&lt;/em&gt;, it’s an electrically random “six-sided” die. I like to call it “electrically random,” because that sounds cool, and because the LED which lights up at the end is an output of a function whose inputs are voltages and time. When you push the button, Hexo cycles through its six LED, slower and slower, until eventually leaving just one LED on.&lt;/p&gt;

&lt;p&gt;I built Hexo because I wanted to learn how I should go about bringing something into existence… something inanimate, that is. What I’m left with is a kind of systematic process that I hope will allow me to build bigger and more ambitious projects in the future!&lt;/p&gt;

&lt;p&gt;This is what I’ve discovered so far; electronics projects like this can be broken down into the following steps:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Requirements Gathering&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Prototyping, Analysis, &amp;amp; Simulation&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Schematic Capture, Mechanical Design, &amp;amp; PCB Layout&lt;/li&gt;
  &lt;li&gt;Manufacture &amp;amp; Parts Ordering&lt;/li&gt;
  &lt;li&gt;Assembly&lt;/li&gt;
  &lt;li&gt;Validation, Testing, &amp;amp; Integration&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this post, I want to share with you my experience with prototyping, design, and simulation, the steps centered around implementing functional design. So grab a cup of tea or coffee and let’s geek out!&lt;/p&gt;

&lt;h2 id=&quot;prototyping-analysis--simulation&quot;&gt;Prototyping, Analysis, &amp;amp; Simulation&lt;/h2&gt;

&lt;p&gt;I didn’t design Hexo’s primary circuitry; the design came from &lt;a href=&quot;http://www.555-timer-circuits.com/roulette.html&quot;&gt;555 Timer Circuits’ Roulette&lt;/a&gt; schematic and the many derivatives thereof. With a few very minor modifications, I was able to turn their 9-volt, 10 LED design into a 3-volt, 6 LED design with a similar effect. To read more about that circuit and how it works, you can read my writeup on Hackaday &lt;a href=&quot;https://hackaday.io/project/178420-hexo/log/190931-how-the-555-roulette-circuit-works&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With that said, in order for me to understand &lt;em&gt;how&lt;/em&gt; to modify the circuit and to measure its performance, I had to build prototypes, do some pen-and-paper analysis, and (although perhaps a bit overkill), employ the use of circuit simulation tools. These things all happened in a happy nonlinear feedback loop during multiple iterations.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;prototyping&lt;/strong&gt;, I use these things called &lt;em&gt;breadboards&lt;/em&gt; (also known as solderless breadboards, it got its name from the old practice of using nails hammered into a cutting board to wire up temporary circuits), pictured below. A breadboard allows you to quickly “plug in” a bunch of &lt;em&gt;through-hole&lt;/em&gt; (or &lt;em&gt;THT&lt;/em&gt;, through-hole technology) components and test out designs.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/breadboard.jpg&quot; alt=&quot;Solderless breadboard&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Below, you can see a few THT components next to their equivalent SMT (surface mount technology) counterparts, which are often used on PCBs because of their small size. You can probably guess why it’s easier to prototype with through-hole components, though they aren’t only used for prototyping!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/components.jpg&quot; alt=&quot;Through-hold components are shown to be much larger than surface-mount components&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Prototyping in this way allows you to take the circuit off of the page and place it in your hands in order to answer questions like: “does this even work?”, and “is it supposed work like that?”, and “ugh… why isn’t this working?”, usually followed by, “oh wow, how am I such an idiot?” (I put in the LED backwards… again.)&lt;/p&gt;

&lt;p&gt;Having a small country’s worth of components sitting around is the only way I’ve seen electronics hobbyists get through prototyping. (That’s why a lot of us join a &lt;a href=&quot;https://wiki.hackerspaces.org/List_of_Hacker_Spaces&quot;&gt;hackerspace&lt;/a&gt;, a physical place where geeks unite and stockpile their trove of parts and tools). Before I set off on my electronics journey, I invested quite a bit in building out a workbench and I have more than enough to share!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/thomas_at_workbench.jpg&quot; alt=&quot;Me prototyping at my workbench&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If prototyping is taking a circuit off of the page, then &lt;strong&gt;circuit analysis&lt;/strong&gt; is… writing more stuff on the page? Analysis allows you to understand the mathematical theory behind the circuit. Equations derived from the Laws of Ohm and Kirchhoff allow us to understand &lt;em&gt;idealized&lt;/em&gt; circuits, that is, how the design would function in a vacuum without interference, resistive wires, and perfect electron flow. Combined with higher-level patterns, one can begin to read circuit diagrams, called &lt;em&gt;schematics&lt;/em&gt;, like lead sheets and fake books in the world of jazz or design patterns in the world of code writing.&lt;/p&gt;

&lt;p&gt;Having not formally studied electrical engineering, this was one of the more exciting parts of this build. I &lt;span style=&quot;text-decoration:underline;&quot;&gt;love&lt;/span&gt; seeing theory on the page become measurable and observable in real life. This is the part where we use math to affect decisions about design. It turned out that even though I was switching from using a 9-volt battery to a 3-volt battery, I didn’t need to change any of the other components in the circuit.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/analysis_on_paper.jpg&quot; alt=&quot;Sketches of electrical circuits with current and voltage calculations&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Lastly, &lt;strong&gt;simulation&lt;/strong&gt;. Using CAD (computer aided design) software allowed me to more easily and efficiently do circuit analysis; particularly with circuits that contained &lt;em&gt;integrated circuits&lt;/em&gt;, or the little black things that look like microchips.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/screenshot_of_ltspice.png&quot; alt=&quot;Screenshot of transient analysis in LTSpice&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is where I learned about SPICE: Simulation Program with Integrated Circuit Emphasis, and its variants like LTspice, pictured above.&lt;/p&gt;

&lt;p&gt;SPICE uses, what I’ve only seen described as, an ASCII-based declarative language to define component models, &lt;em&gt;netlists&lt;/em&gt; (how components are connected), and initial condition directives to calculate the electrical state of a given circuit. In Hexo’s case, I used a &lt;em&gt;transient analysis&lt;/em&gt;, which allowed me to observe the circuit’s behavior over time. This was particularly useful given that the LEDs would blink, and “blinking” is a function of time.&lt;/p&gt;

&lt;p&gt;Pictured above, the top half of the image shows the state of each of the &lt;em&gt;ten&lt;/em&gt; LEDs, in that particular Hexo iteration, plotted over time. Taken as a whole, you can see how the rate of flashing (defined by the &lt;em&gt;period&lt;/em&gt; and &lt;em&gt;duty cycle&lt;/em&gt; of the square wave) begins to slow or stretch out over time as the LEDs blink and cycle slower and slower, before ending with only one remaining lit.&lt;/p&gt;

&lt;p&gt;I’m really excited about SPICE. I’m not sure where it sits in the professional electronics industry, but there’s not a lot of documentation for us mere hobbyists. I hope that we, as a maker community, can work together to change that.&lt;/p&gt;

&lt;p&gt;A more intuitive type of circuit simulation can be done using the Falstad Circuit Simulator.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/screenshot_of_falstad.png&quot; alt=&quot;Screenshot of transient analysis in LTSpice&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In fact, if you want to play with Hexo, you can take a look at the simulation with Falstad’s free browser-based app &lt;a href=&quot;https://www.falstad.com/circuit/circuitjs.html?ctz=CQAgzCAMB0mQjCeAWaAmMBWAbGMB2TSfZTNTADhG2xEzsjoFMBaeeAKAHcRlHG+vKpx6CWYNL0YsKjNBwAuIKm1pj4taYgrQAnCgrJd2MhnJgVMSBSInjuyGgrZruJHBABnAA4BLAMasACZMAGYAhgCuADYKHABOICw4SRqpYLSqUO5wuZAJSWjIqU6FxSzIVIx4efme6bT4kmwZSNhVSCAR0Z5M3FJJxgMy-AWClQNDjPB5HP4NSU0LWfyweTO1m7mSMGDIaITk8Pj4FDaQ2EVQHABuqYapZMvwT-yT2auY-SxXbE-qr36AMkYim33gDz+zQhxTBPBamRezV+SOu8JRpR+5VR+Xh8FabEx+MRgPR2NoKjS8jJqUyKRWBQRJUpBKW02+vwmWNp1w09HgpSuaCFxQpel0EslG3WQMYQ0EM2KuKQpRSQxS+XmgnIamqaDe8DWW028FY+CgRusyHgEpweDONAO5qsCFlIDMZXdEjRCw0LNoElGeNagc9ofyiW5Hu54d4+Fq-SGAumpWTPoV+oGxPT03+cm9ypjmcE4bdHu1Gv6Nuawl0zUEyurjxBWlJ7quMPbxVLPGF3czTZ7IHVtCTkCVbsVWczyoVrQVbYFwieSbbq8kSdays3uqQW8TtCnCvHPqbFWmdebPp1dFoB0kld7SxS96SDf6r-PSEvX+VN7O4CtABhadkMbAPEO4H9loIYznM36SJ2g4zkgrBUJoawiAh4DFnqQbDoeuEqqMGgQHgFJUFgkgAfqmCIJo2T0G8s7eksyDelC6YcaiZ44v0GRUEsr5sh+SxCUsQ6vqGfY4SRlzgE8aZUUgDySCEEQxAoLDREwQSdMxsCcBokgCUgyDdu07rWO6IDqVEsTabp+kfIZHDGeADx+h5xQempYT2VpOl6YgBmuu5fAXi2cgvrZ-maY5wUfBaYXydad5pGl7qyDZdnxUFzmrClILekUAjemAJ5+RpDn5SFLmujwplKZZKATo1nmZhYxRebOHhphF2Gzmk-VUmkLGIcWJXvokUmdU8sbIPG-GKRB81wY1ln6pRllDqZobKbtqnlRB63ebJ4A7adA03gNQ63Z1J34ZlTgCGkd1vbheFcSZJ7sSZV3lcWH34UySxMgyQA&quot;&gt;here&lt;/a&gt;. Although Falstad’s models are less realistic in a few ways, the drag-and-drop interface, the current flow visualizations, and real-time interactivity helped me build up a more intuitive understanding of Hexo’s design.&lt;/p&gt;

&lt;p&gt;From bench, to paper, to screen. Back and forth, I’ve iterated and studied Hexo’s design until I felt confident that the circuit would work after I tweaked it a bit. I imagine a similar, if not more rigorous, process would exist for building an original design as well.&lt;/p&gt;

&lt;p&gt;Now that we’ve experimented with prototypes and understood the theory, it’s time to design the physical thing!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Communication Superhighways</title>
   <link href="https://thomascountz.com/2021/03/13/communication-superhighways"/>
   <updated>2021-03-13T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2021/03/13/communication-superhighways</id>
   <summary type="html">&lt;p&gt;Good communication in an emergency is vital: What is happening? Who is leading? Where are we meeting? Et al. Just like firefighters, who use highways instead of local roads to get to a burning building more quickly, our teams should leverage communication highways to respond efficiently in the face of an emergency.&lt;/p&gt;

&lt;p&gt;This communication infrastructure isn’t just logistical: you may have a dedicated chat channel, video conference URL, or a paging system already. Designing communication superhighways is also about investing in our workplace environment and ultimately in each other.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Cognition and emotion are tightly intertwined, which means that [we] must design with both in mind. (Norman, 2013).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;How we feel about our work with one another will greatly influence our ability to work together when things are difficult—if I don’t feel comfortable or confident communicating about our work when there isn’t an emergency, how do you think we’ll get along when there is one?&lt;/p&gt;

&lt;p&gt;In practice, this looks like investing in maintaining a healthy workplace environment, team collaboration, transparency, accountability, and responsible incentives. Going into each of these is beyond the scope of this essay—instead, I’ll leave you with this: ethically, we should all be working towards keeping our team dynamic positive and healthy, but capitalistic incentives being what they are, we could all do well to remember that those team outings really do help your bottom line.&lt;/p&gt;

&lt;p&gt;Norman, D. A. (2013). The design of everyday things. MIT Press.&lt;/p&gt;
</summary>
   <content type="html">&lt;p&gt;Good communication in an emergency is vital: What is happening? Who is leading? Where are we meeting? Et al. Just like firefighters, who use highways instead of local roads to get to a burning building more quickly, our teams should leverage communication highways to respond efficiently in the face of an emergency.&lt;/p&gt;

&lt;p&gt;This communication infrastructure isn’t just logistical: you may have a dedicated chat channel, video conference URL, or a paging system already. Designing communication superhighways is also about investing in our workplace environment and ultimately in each other.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Cognition and emotion are tightly intertwined, which means that [we] must design with both in mind. (Norman, 2013).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;How we feel about our work with one another will greatly influence our ability to work together when things are difficult—if I don’t feel comfortable or confident communicating about our work when there isn’t an emergency, how do you think we’ll get along when there is one?&lt;/p&gt;

&lt;p&gt;In practice, this looks like investing in maintaining a healthy workplace environment, team collaboration, transparency, accountability, and responsible incentives. Going into each of these is beyond the scope of this essay—instead, I’ll leave you with this: ethically, we should all be working towards keeping our team dynamic positive and healthy, but capitalistic incentives being what they are, we could all do well to remember that those team outings really do help your bottom line.&lt;/p&gt;

&lt;p&gt;Norman, D. A. (2013). The design of everyday things. MIT Press.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Crafting an Invitation to Think</title>
   <link href="https://thomascountz.com/2021/03/08/invitation-to-think"/>
   <updated>2021-03-08T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2021/03/08/invitation-to-think</id>
   <summary type="html">&lt;p&gt;Foreshadowing: signaling an outcome of chapter eight in the middle of chapter one, i.e. the original spoiler. Although, akin to the mean-spirited handiwork of a Reddit troll, wielded gracefully, foreshadowing helps an author build tension, introduce indirection, and drive home a butterfly-inducing denouement.&lt;/p&gt;

&lt;p&gt;In non-fiction, we are afforded a similar device: we can build a compelling narrative or stack ever increasing layers of complexity onto a topic to help elevate our reader to a plane of inevitability. At this point, we want our readers to say, “Ah! Yes! This is so obvious!”&lt;/p&gt;

&lt;p&gt;If we over-articulate the point, however, our audience disengages; like continually begging a friend to show up to your (virtual) party after they’ve already said yes. We want to posit an idea just enough to engage a reader’s curiosity and compel them to have thoughts of their own—the aim is to craft an invitation to think.&lt;/p&gt;
</summary>
   <content type="html">&lt;p&gt;Foreshadowing: signaling an outcome of chapter eight in the middle of chapter one, i.e. the original spoiler. Although, akin to the mean-spirited handiwork of a Reddit troll, wielded gracefully, foreshadowing helps an author build tension, introduce indirection, and drive home a butterfly-inducing denouement.&lt;/p&gt;

&lt;p&gt;In non-fiction, we are afforded a similar device: we can build a compelling narrative or stack ever increasing layers of complexity onto a topic to help elevate our reader to a plane of inevitability. At this point, we want our readers to say, “Ah! Yes! This is so obvious!”&lt;/p&gt;

&lt;p&gt;If we over-articulate the point, however, our audience disengages; like continually begging a friend to show up to your (virtual) party after they’ve already said yes. We want to posit an idea just enough to engage a reader’s curiosity and compel them to have thoughts of their own—the aim is to craft an invitation to think.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Repeated Signals Wear Grooves</title>
   <link href="https://thomascountz.com/2021/03/01/repeated-signals-wear-grooves"/>
   <updated>2021-03-01T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2021/03/01/repeated-signals-wear-grooves</id>
   <summary type="html">&lt;p&gt;In &lt;em&gt;The Design of Everyday Things&lt;/em&gt;, Don Norman shares an anecdote about “signifiers,” which are the design elements that communicate the purpose, structure, and operation of an object or system. The anecdote was about a wall onto which someone placed an empty bottle. Over time, more and more empty bottles showed up on this wall:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;…as soon as one person discovers it can be used to dispose of empty drink containers, the discarded container becomes a signifier, telling others that it is permissible to discard their items there. (Norman, 2013).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a powerful message about the implicit, and often unconscious, signals we send one another on our teams.&lt;/p&gt;

&lt;p&gt;For example, the boss may say “don’t feel like you have to work late,” but if they and other members of your team do, the implicit signal soon becomes louder than words.&lt;/p&gt;

&lt;p&gt;This doesn’t always have to be negative, exhibiting healthy team customs, norms, and rituals also has the happy effect of instilling positive behaviors, too. If everyone is always on time to meetings, it can soon become a happy groove worn into the fabric of the team.&lt;/p&gt;

&lt;p&gt;These grooves are worn in over time for better and for worse, however they aren’t a drop-in replacement for explicit team structures and boundaries. Signals help to reinforce healthy team habits and deter unhealthy ones, but they shouldn’t be relied upon to establish them.&lt;/p&gt;

&lt;p&gt;Norman, D. A. (2013). The design of everyday things. MIT Press.&lt;/p&gt;
</summary>
   <content type="html">&lt;p&gt;In &lt;em&gt;The Design of Everyday Things&lt;/em&gt;, Don Norman shares an anecdote about “signifiers,” which are the design elements that communicate the purpose, structure, and operation of an object or system. The anecdote was about a wall onto which someone placed an empty bottle. Over time, more and more empty bottles showed up on this wall:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;…as soon as one person discovers it can be used to dispose of empty drink containers, the discarded container becomes a signifier, telling others that it is permissible to discard their items there. (Norman, 2013).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a powerful message about the implicit, and often unconscious, signals we send one another on our teams.&lt;/p&gt;

&lt;p&gt;For example, the boss may say “don’t feel like you have to work late,” but if they and other members of your team do, the implicit signal soon becomes louder than words.&lt;/p&gt;

&lt;p&gt;This doesn’t always have to be negative, exhibiting healthy team customs, norms, and rituals also has the happy effect of instilling positive behaviors, too. If everyone is always on time to meetings, it can soon become a happy groove worn into the fabric of the team.&lt;/p&gt;

&lt;p&gt;These grooves are worn in over time for better and for worse, however they aren’t a drop-in replacement for explicit team structures and boundaries. Signals help to reinforce healthy team habits and deter unhealthy ones, but they shouldn’t be relied upon to establish them.&lt;/p&gt;

&lt;p&gt;Norman, D. A. (2013). The design of everyday things. MIT Press.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Arduino Tone for ESP32</title>
   <link href="https://thomascountz.com/2021/02/21/arduino-tone-for-esp32"/>
   <updated>2021-02-21T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2021/02/21/arduino-tone-for-esp32</id>
   <summary type="html">&lt;p&gt;Arduino has a built-in &lt;a href=&quot;https://www.arduino.cc/reference/en/language/functions/advanced-io/tone/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tone()&lt;/code&gt;&lt;/a&gt; library which allows you to send a PWM &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;frequency&lt;/code&gt; at 50% duty cycle to a specific &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pin&lt;/code&gt; in order to generate a tone on a piezoelectric buzzer with an optional &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;duration&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;tone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;frequency&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;tone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;frequency&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This functionality is &lt;a href=&quot;https://github.com/espressif/arduino-esp32/issues/980&quot;&gt;famously&lt;/a&gt; &lt;a href=&quot;https://github.com/espressif/arduino-esp32/issues/1720&quot;&gt;unavailable&lt;/a&gt; in Espressif’s &lt;a href=&quot;https://github.com/espressif/arduino-esp32&quot;&gt;arduino-esp32&lt;/a&gt; library and members of the community have found various &lt;a href=&quot;https://github.com/lbernstone/Tone&quot;&gt;work-arounds&lt;/a&gt; such as using the native &lt;a href=&quot;https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/ledc.html&quot;&gt;LED Control&lt;/a&gt; functions to generate PWM signals.&lt;/p&gt;

&lt;p&gt;However, looking more closely at &lt;a href=&quot;https://github.com/espressif/arduino-esp32&quot;&gt;arduino-esp32&lt;/a&gt; library, not only has Espressif provided a clean API for generating tones, they’ve provided an interface for generating specific PWM frequencies for specific notes on the chromatic scale in different octaves.&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ledcWriteNote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;note_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;note&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;octave&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint16_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;noteFrequencyBase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;//   C        C#       D        Eb       E        F       F#        G       G#        A       Bb        B&lt;/span&gt;
      &lt;span class=&quot;mi&quot;&gt;4186&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;mi&quot;&gt;4435&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;mi&quot;&gt;4699&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;mi&quot;&gt;4978&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;mi&quot;&gt;5274&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;mi&quot;&gt;5588&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;mi&quot;&gt;5920&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;mi&quot;&gt;6272&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;mi&quot;&gt;6645&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;mi&quot;&gt;7040&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;mi&quot;&gt;7459&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;mi&quot;&gt;7902&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;octave&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;note&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_MAX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;noteFreq&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;noteFrequencyBase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;note&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;octave&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ledcWriteTone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;noteFreq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Although not directly compatible with Arduino’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tone()&lt;/code&gt;, the function provides a dedicated interface for producing named frequencies out of the box via the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;note_t&lt;/code&gt; type.&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;NOTE_C&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_Cs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_D&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_Eb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_E&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_F&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_Fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_Gs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_Bb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_B&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_MAX&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;note_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;example-usage&quot;&gt;Example Usage&lt;/h2&gt;
&lt;p&gt;I’ll take an example from &lt;a href=&quot;https://github.com/lbernstone&quot;&gt;@lbernstone&lt;/a&gt;’s &lt;a href=&quot;https://github.com/lbernstone/Tone&quot;&gt;Tone32&lt;/a&gt; library. Their library is a great solution for providing cross-compatibility between code written for an Arduino and code written for an ESP32.&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;tone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_C4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;noTone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;tone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_D4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;noTone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;tone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_E4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;noTone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;tone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_F4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;noTone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;tone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_G4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;noTone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;tone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_A4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;noTone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;tone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_B4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;noTone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this example, a major C scale is played while holding each note for 0.5 seconds. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BUZZER_CHANNEL&lt;/code&gt; argument is optional. In the case of an ESP32, there are 16 PWM channels which can generate independent waveforms which need to be explicitly assigned to any PWM-capable pin.&lt;/p&gt;

&lt;p&gt;Here is the equivalent scale programmed using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ledcWriteNote()&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ledcAttachPin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ledcWriteNote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_C&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ledcWriteNote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_D&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ledcWriteNote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_E&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ledcWriteNote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_F&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ledcWriteNote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ledcWriteNote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ledcWriteNote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_B&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ledcDetachPin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The tradeoff here is that this is not cross-compatible with an Arduino, however, it means not having to import an external library or having to define note frequencies yourself. Similar to Arduino’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tone()&lt;/code&gt;, each signal is produced at 50% duty cycle. Under the hood, both implementations are calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ledcWriteTone&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;https://randomnerdtutorials.com/esp32-pwm-arduino-ide/&lt;/li&gt;
  &lt;li&gt;https://github.com/espressif/arduino-esp32/issues/1720&lt;/li&gt;
  &lt;li&gt;https://community.platformio.org/t/tone-not-working-on-espressif32-platform/7587&lt;/li&gt;
&lt;/ul&gt;
</summary>
   <content type="html">&lt;p&gt;Arduino has a built-in &lt;a href=&quot;https://www.arduino.cc/reference/en/language/functions/advanced-io/tone/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tone()&lt;/code&gt;&lt;/a&gt; library which allows you to send a PWM &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;frequency&lt;/code&gt; at 50% duty cycle to a specific &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pin&lt;/code&gt; in order to generate a tone on a piezoelectric buzzer with an optional &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;duration&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;tone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;frequency&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;tone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;frequency&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This functionality is &lt;a href=&quot;https://github.com/espressif/arduino-esp32/issues/980&quot;&gt;famously&lt;/a&gt; &lt;a href=&quot;https://github.com/espressif/arduino-esp32/issues/1720&quot;&gt;unavailable&lt;/a&gt; in Espressif’s &lt;a href=&quot;https://github.com/espressif/arduino-esp32&quot;&gt;arduino-esp32&lt;/a&gt; library and members of the community have found various &lt;a href=&quot;https://github.com/lbernstone/Tone&quot;&gt;work-arounds&lt;/a&gt; such as using the native &lt;a href=&quot;https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/ledc.html&quot;&gt;LED Control&lt;/a&gt; functions to generate PWM signals.&lt;/p&gt;

&lt;p&gt;However, looking more closely at &lt;a href=&quot;https://github.com/espressif/arduino-esp32&quot;&gt;arduino-esp32&lt;/a&gt; library, not only has Espressif provided a clean API for generating tones, they’ve provided an interface for generating specific PWM frequencies for specific notes on the chromatic scale in different octaves.&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ledcWriteNote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;note_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;note&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;octave&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint16_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;noteFrequencyBase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;//   C        C#       D        Eb       E        F       F#        G       G#        A       Bb        B&lt;/span&gt;
      &lt;span class=&quot;mi&quot;&gt;4186&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;mi&quot;&gt;4435&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;mi&quot;&gt;4699&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;mi&quot;&gt;4978&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;mi&quot;&gt;5274&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;mi&quot;&gt;5588&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;mi&quot;&gt;5920&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;mi&quot;&gt;6272&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;mi&quot;&gt;6645&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;mi&quot;&gt;7040&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;mi&quot;&gt;7459&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;mi&quot;&gt;7902&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;octave&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;note&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_MAX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;noteFreq&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;noteFrequencyBase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;note&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;octave&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ledcWriteTone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;noteFreq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Although not directly compatible with Arduino’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tone()&lt;/code&gt;, the function provides a dedicated interface for producing named frequencies out of the box via the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;note_t&lt;/code&gt; type.&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;NOTE_C&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_Cs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_D&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_Eb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_E&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_F&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_Fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_Gs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_Bb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_B&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_MAX&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;note_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;example-usage&quot;&gt;Example Usage&lt;/h2&gt;
&lt;p&gt;I’ll take an example from &lt;a href=&quot;https://github.com/lbernstone&quot;&gt;@lbernstone&lt;/a&gt;’s &lt;a href=&quot;https://github.com/lbernstone/Tone&quot;&gt;Tone32&lt;/a&gt; library. Their library is a great solution for providing cross-compatibility between code written for an Arduino and code written for an ESP32.&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;tone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_C4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;noTone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;tone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_D4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;noTone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;tone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_E4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;noTone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;tone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_F4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;noTone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;tone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_G4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;noTone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;tone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_A4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;noTone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;tone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_B4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;noTone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this example, a major C scale is played while holding each note for 0.5 seconds. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BUZZER_CHANNEL&lt;/code&gt; argument is optional. In the case of an ESP32, there are 16 PWM channels which can generate independent waveforms which need to be explicitly assigned to any PWM-capable pin.&lt;/p&gt;

&lt;p&gt;Here is the equivalent scale programmed using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ledcWriteNote()&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ledcAttachPin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ledcWriteNote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_C&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ledcWriteNote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_D&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ledcWriteNote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_E&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ledcWriteNote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_F&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ledcWriteNote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ledcWriteNote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ledcWriteNote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUZZER_CHANNEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOTE_B&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ledcDetachPin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The tradeoff here is that this is not cross-compatible with an Arduino, however, it means not having to import an external library or having to define note frequencies yourself. Similar to Arduino’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tone()&lt;/code&gt;, each signal is produced at 50% duty cycle. Under the hood, both implementations are calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ledcWriteTone&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;https://randomnerdtutorials.com/esp32-pwm-arduino-ide/&lt;/li&gt;
  &lt;li&gt;https://github.com/espressif/arduino-esp32/issues/1720&lt;/li&gt;
  &lt;li&gt;https://community.platformio.org/t/tone-not-working-on-espressif32-platform/7587&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>SQL Injection Overview</title>
   <link href="https://thomascountz.com/2020/10/24/sql-injection"/>
   <updated>2020-10-24T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2020/10/24/sql-injection</id>
   <summary type="html">&lt;p&gt;&lt;img src=&quot;/assets/images/sql_injection/sql-injection-owasp.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;An SQL Injection &lt;strong&gt;occurs when untrusted input is used directly in the construction of an SQL query&lt;/strong&gt;. This attack is commonly executed by introducing a meta character (such as a comment) into a data plane in such a way that allows an attacker to add commands to the control plane. Essentially, when building a SQL query from user input, an attacker can insert SQL instructions that cause the application to behave in unintended ways.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;SQL Injection flaws are introduced when software developers create dynamic database queries that include user supplied input.&lt;/p&gt;

  &lt;p&gt;—&lt;a href=&quot;https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html&quot;&gt;OWASP SQL Injection Prevention Cheat Sheet&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;authentication-form-example&quot;&gt;Authentication Form Example&lt;/h2&gt;

&lt;p&gt;Let’s take a login form that requires a user enter a username and password to log in.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/sql_injection/login-long-password.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In the happy-path case, a user enters in their details, presses “Login,” and a request is sent to a server where we take what the user enters, search the database for that user, and then log that user into our web application.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# SQL Injection Vulnerable Pseudocode&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# -- snip --&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ORM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;SELECT * 
    FROM users 
    WHERE username = &apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;username&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&apos;
    AND password = &apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;password&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&apos;;&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# -- snip -- &lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;log_in_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Using a tool like &lt;a href=&quot;http://sqlfiddle.com&quot;&gt;SQL Fiddle&lt;/a&gt;, you can see how a query like this will behave in PostgreSQL 9.6. Here’s is the schema and seed data for our example:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;        &lt;span class=&quot;nb&quot;&gt;INT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt;   &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;VARCHAR&lt;/span&gt;           &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;VARCHAR&lt;/span&gt;           &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;sarah&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;good-password&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;thomas&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;password123&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;patrice&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;md5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;palm-kumquat-futon-padden&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After the user submits the username:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;thomas
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and password:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;password123
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;the resulting query will look like this:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;thomas&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;password123&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and the following results will be returned:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;| id | username |    password |
|----|----------|-------------|
|  2 |  tcountz | password123 |
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As expected, our backend code then takes this result, and calls the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;log_in_user()&lt;/code&gt; function which authorizes the user to access certain parts of the application.&lt;/p&gt;

&lt;h3 id=&quot;authentication-attack&quot;&gt;Authentication Attack&lt;/h3&gt;

&lt;p&gt;The vulnerability here will allow an attacker to log in as any user.&lt;/p&gt;

&lt;p&gt;Because user input is interpolated directly into the SQL query, we can have the server execute arbitrary SQL statements. Let’s take a look at an example that would allow us to log into any user, given we have their username.&lt;/p&gt;

&lt;p&gt;After the user submits the username:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;patrice&apos;; --
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and a blank (or arbitrary) password, the resulting query looks like this&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;patrice&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;--&apos; AND password=&apos;&amp;lt;anything&amp;gt;&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and the results:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;| id | username |                         password |
|----|----------|----------------------------------|
|  3 |  patrice | 26e9053a783f364d949b4e400dd2f68c |
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, our application, again, takes the 0th results (this time the user &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;patrice&lt;/code&gt;) and logs them in.&lt;/p&gt;

&lt;h4 id=&quot;what-happened&quot;&gt;What Happened?&lt;/h4&gt;

&lt;p&gt;Even though we hashed Patrice’s password, our query was vulnerable to interpreting the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;;&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--&lt;/code&gt; PostgreSQL meta characters.&lt;/p&gt;

&lt;p&gt;Firstly, the single quote: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;&lt;/code&gt;, ends the string (or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VARCHAR&lt;/code&gt;) that we’re searching for; in this case the username &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;patrice&lt;/code&gt;. Next, the semi-colon &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;;&lt;/code&gt; represents the end of the SQL statement. Finally, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--&lt;/code&gt; represents a comment and tells PostgreSQL to ignore everything that comes after.&lt;/p&gt;

&lt;p&gt;This effectively makes our query look like this:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;patrice&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Which, as we’ve seen, and as we expect, returns the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;patrice&lt;/code&gt; user and logs them in. Now our attacker has been authenticated and has access to Patrice’s account!&lt;/p&gt;

&lt;p&gt;This combination: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;; --&lt;/code&gt;, and others like it, show up often in SQL injection attacks and it works by prematurely ending a SQL statement.&lt;/p&gt;

&lt;h3 id=&quot;data-destruction-attack&quot;&gt;Data Destruction Attack&lt;/h3&gt;

&lt;p&gt;In the example above, the 0th row of the results returned from the query will be passed into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;log_in_user()&lt;/code&gt; function, but the scope of this attack vector isn’t limited to logging in.&lt;/p&gt;

&lt;p&gt;As an example of how we can attack the server to execute &lt;em&gt;any&lt;/em&gt; SQL, take this example where we destroy the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;users&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;If we enter a username of:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&apos;; DROP TABLE users; --
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and a blank (or arbitrary) password, the resulting query looks like this&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DROP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;--&apos; AND password=&apos;&amp;lt;anything&amp;gt;&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The user-facing effect of this query might not tell us exactly what has happened, but a developer might see something like this show up in the logs:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ERROR: relation &quot;users&quot; does not exist
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;what-happened-1&quot;&gt;What Happened?&lt;/h4&gt;

&lt;p&gt;Similar to the first attack, we’ve cut the original query short and this time, we’ve injected our own query to drop the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;users&lt;/code&gt; table.&lt;/p&gt;

&lt;h2 id=&quot;prevention&quot;&gt;Prevention&lt;/h2&gt;

&lt;h3 id=&quot;parameterized-queries&quot;&gt;Parameterized Queries&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;The use of prepared statements with variable binding (aka parameterized queries) is how all developers should first be taught how to write database queries.&lt;/p&gt;

  &lt;p&gt;Parameterized queries force the developer to first define all the SQL code, and then pass in each parameter to the query later. This coding style allows the database to distinguish between code and data, regardless of what user input is supplied.&lt;/p&gt;

  &lt;p&gt;— &lt;a href=&quot;https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html&quot;&gt;OWASP SQL Injection Prevention Cheat Sheet&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Our code was vulnerable because we use string interpolation to build an SQL statement directly from user input. Instead, we should “parameterize” our query by using whatever tools our language gives us to separate the data plane (input) from the control plane (SQL). This is the idea of using variable binding (placing user input into a type of variable) with prepared statements (the rest of the SQL that we don’t want the user to be able to alter).&lt;/p&gt;

&lt;p&gt;The way to code this varies depending on the language you’re working with, so check out the OWASP SQL Injection Prevention Cheat Sheet section on &lt;a href=&quot;https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.md#defense-option-1-prepared-statements-with-parameterized-queries&quot;&gt;parameterized queries&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;other-defenses&quot;&gt;Other Defenses&lt;/h3&gt;

&lt;p&gt;Another defense against SQL injection are stored procedures, which are predefined SQL statements stored in the data table. These procedures can have parameters and can effectively be similar to constructs from different languages. Read more &lt;a href=&quot;https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.md#defense-option-2-stored-procedures&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To read more about allow-listing or escaping user input, see the rest of the &lt;a href=&quot;https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html&quot;&gt;OWASP SQL Injection Prevention Cheat Sheet&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Research SQL injection prevention for your ORM/database
    &lt;ul&gt;
      &lt;li&gt;e.g. &lt;a href=&quot;https://guides.rubyonrails.org/security.html#sql-injection&quot;&gt;https://guides.rubyonrails.org/security.html#sql-injection&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Aim to separate queries and data (parameterized queries)
    &lt;ul&gt;
      &lt;li&gt;e.g. &lt;a href=&quot;https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Query_Parameterization_Cheat_Sheet.md&quot;&gt;https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Query_Parameterization_Cheat_Sheet.md&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Consider SQL Injection when reviewing code&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;code-review&quot;&gt;Code Review&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Aim to validate user input by testing type, length, format, and range.&lt;/li&gt;
  &lt;li&gt;Avoid building SQL statements directly from user input.&lt;/li&gt;
  &lt;li&gt;Implement multiple layers of validation.&lt;/li&gt;
  &lt;li&gt;Avoid concatenating user input that is not validated; this is the primary point of entry for script injection.&lt;/li&gt;
  &lt;li&gt;You should review all code that calls execute(), exe(), and any SQL calls or commands that can call out outside resources or the command line.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;example-code-snippets&quot;&gt;Example Code Snippets&lt;/h2&gt;

&lt;h3 id=&quot;rubyactiverecord&quot;&gt;Ruby/ActiveRecord&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# SQL Injection Vulnerable Ruby/ActiveRecord&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# -- snip --&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;username = &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:username&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; AND &quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;password = &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# -- snip --&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# SQL Injection Safe Ruby/ActiveRecord&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# -- snip --&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;username: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:username&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;password: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# -- snip --&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;elixirecto&quot;&gt;Elixir/Ecto&lt;/h3&gt;

&lt;div class=&quot;language-elixir highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# SQL Injection Vulnerable Elixir/Ecto&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# -- snip --&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sd&quot;&gt;&quot;&quot;&quot;
  SELECT *
  FROM users
  WHERE username = \&apos;#{params[&quot;username&quot;]}\&apos;
  AND password = \&apos;#{params[&quot;pasword&quot;]}\&apos;;
&quot;&quot;&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Ecto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Adapters&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;MyApp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Repo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:rows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# -- snip --&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-elixir highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# SQL Injection Safe Elixir/Ecto&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# -- snip --&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sd&quot;&gt;&quot;&quot;&quot;
  SELECT * 
  FROM users 
  WHERE username = $1 
  AND password = $2;
&quot;&quot;&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Ecto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Adapters&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;MyApp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Repo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:rows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# -- snip --&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;java&quot;&gt;Java&lt;/h3&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// SQL Injection Vulnerable Java&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// -- snip --&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getParameter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getParameter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;SELECT * FROM users WHERE username = &quot;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; AND password = &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;Statement&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;statement&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createStatement&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;statement&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;executeQuery&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// -- snip --&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// SQL Injection Safe Java&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// -- snip --&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getParameter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getParameter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;SELECT * FROM users WHERE username = ? AND password = ?&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;PreparedStatement&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pstmt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;prepareStatement&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pstmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pstmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pstmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;executeQuery&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// -- snip --&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/OWASP/railsgoat/wiki/R5-A1-SQL-Injection-Concatentation&quot;&gt;https://github.com/OWASP/railsgoat/wiki/R5-A1-SQL-Injection-Concatentation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html&quot;&gt;https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.websec.ca/kb/sql_injection&quot;&gt;https://www.websec.ca/kb/sql_injection&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://owasp.org/www-community/attacks/SQL_Injection&quot;&gt;https://owasp.org/www-community/attacks/SQL_Injection&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.websec.ca/kb/sql_injection&quot;&gt;https://www.websec.ca/kb/sql_injection&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/sql_injection/exploits_of_a_mom.png&quot; alt=&quot;https://xkcd.com/327/&quot; /&gt;&lt;/p&gt;
</summary>
   <content type="html">&lt;p&gt;&lt;img src=&quot;/assets/images/sql_injection/sql-injection-owasp.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;An SQL Injection &lt;strong&gt;occurs when untrusted input is used directly in the construction of an SQL query&lt;/strong&gt;. This attack is commonly executed by introducing a meta character (such as a comment) into a data plane in such a way that allows an attacker to add commands to the control plane. Essentially, when building a SQL query from user input, an attacker can insert SQL instructions that cause the application to behave in unintended ways.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;SQL Injection flaws are introduced when software developers create dynamic database queries that include user supplied input.&lt;/p&gt;

  &lt;p&gt;—&lt;a href=&quot;https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html&quot;&gt;OWASP SQL Injection Prevention Cheat Sheet&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;authentication-form-example&quot;&gt;Authentication Form Example&lt;/h2&gt;

&lt;p&gt;Let’s take a login form that requires a user enter a username and password to log in.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/sql_injection/login-long-password.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In the happy-path case, a user enters in their details, presses “Login,” and a request is sent to a server where we take what the user enters, search the database for that user, and then log that user into our web application.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# SQL Injection Vulnerable Pseudocode&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# -- snip --&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ORM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;SELECT * 
    FROM users 
    WHERE username = &apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;username&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&apos;
    AND password = &apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;password&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&apos;;&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# -- snip -- &lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;log_in_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Using a tool like &lt;a href=&quot;http://sqlfiddle.com&quot;&gt;SQL Fiddle&lt;/a&gt;, you can see how a query like this will behave in PostgreSQL 9.6. Here’s is the schema and seed data for our example:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;        &lt;span class=&quot;nb&quot;&gt;INT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt;   &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;VARCHAR&lt;/span&gt;           &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;VARCHAR&lt;/span&gt;           &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;sarah&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;good-password&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;thomas&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;password123&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;patrice&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;md5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;palm-kumquat-futon-padden&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After the user submits the username:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;thomas
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and password:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;password123
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;the resulting query will look like this:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;thomas&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;password123&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and the following results will be returned:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;| id | username |    password |
|----|----------|-------------|
|  2 |  tcountz | password123 |
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As expected, our backend code then takes this result, and calls the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;log_in_user()&lt;/code&gt; function which authorizes the user to access certain parts of the application.&lt;/p&gt;

&lt;h3 id=&quot;authentication-attack&quot;&gt;Authentication Attack&lt;/h3&gt;

&lt;p&gt;The vulnerability here will allow an attacker to log in as any user.&lt;/p&gt;

&lt;p&gt;Because user input is interpolated directly into the SQL query, we can have the server execute arbitrary SQL statements. Let’s take a look at an example that would allow us to log into any user, given we have their username.&lt;/p&gt;

&lt;p&gt;After the user submits the username:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;patrice&apos;; --
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and a blank (or arbitrary) password, the resulting query looks like this&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;patrice&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;--&apos; AND password=&apos;&amp;lt;anything&amp;gt;&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and the results:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;| id | username |                         password |
|----|----------|----------------------------------|
|  3 |  patrice | 26e9053a783f364d949b4e400dd2f68c |
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, our application, again, takes the 0th results (this time the user &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;patrice&lt;/code&gt;) and logs them in.&lt;/p&gt;

&lt;h4 id=&quot;what-happened&quot;&gt;What Happened?&lt;/h4&gt;

&lt;p&gt;Even though we hashed Patrice’s password, our query was vulnerable to interpreting the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;;&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--&lt;/code&gt; PostgreSQL meta characters.&lt;/p&gt;

&lt;p&gt;Firstly, the single quote: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;&lt;/code&gt;, ends the string (or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VARCHAR&lt;/code&gt;) that we’re searching for; in this case the username &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;patrice&lt;/code&gt;. Next, the semi-colon &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;;&lt;/code&gt; represents the end of the SQL statement. Finally, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--&lt;/code&gt; represents a comment and tells PostgreSQL to ignore everything that comes after.&lt;/p&gt;

&lt;p&gt;This effectively makes our query look like this:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;patrice&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Which, as we’ve seen, and as we expect, returns the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;patrice&lt;/code&gt; user and logs them in. Now our attacker has been authenticated and has access to Patrice’s account!&lt;/p&gt;

&lt;p&gt;This combination: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;; --&lt;/code&gt;, and others like it, show up often in SQL injection attacks and it works by prematurely ending a SQL statement.&lt;/p&gt;

&lt;h3 id=&quot;data-destruction-attack&quot;&gt;Data Destruction Attack&lt;/h3&gt;

&lt;p&gt;In the example above, the 0th row of the results returned from the query will be passed into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;log_in_user()&lt;/code&gt; function, but the scope of this attack vector isn’t limited to logging in.&lt;/p&gt;

&lt;p&gt;As an example of how we can attack the server to execute &lt;em&gt;any&lt;/em&gt; SQL, take this example where we destroy the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;users&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;If we enter a username of:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&apos;; DROP TABLE users; --
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and a blank (or arbitrary) password, the resulting query looks like this&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DROP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;--&apos; AND password=&apos;&amp;lt;anything&amp;gt;&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The user-facing effect of this query might not tell us exactly what has happened, but a developer might see something like this show up in the logs:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ERROR: relation &quot;users&quot; does not exist
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;what-happened-1&quot;&gt;What Happened?&lt;/h4&gt;

&lt;p&gt;Similar to the first attack, we’ve cut the original query short and this time, we’ve injected our own query to drop the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;users&lt;/code&gt; table.&lt;/p&gt;

&lt;h2 id=&quot;prevention&quot;&gt;Prevention&lt;/h2&gt;

&lt;h3 id=&quot;parameterized-queries&quot;&gt;Parameterized Queries&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;The use of prepared statements with variable binding (aka parameterized queries) is how all developers should first be taught how to write database queries.&lt;/p&gt;

  &lt;p&gt;Parameterized queries force the developer to first define all the SQL code, and then pass in each parameter to the query later. This coding style allows the database to distinguish between code and data, regardless of what user input is supplied.&lt;/p&gt;

  &lt;p&gt;— &lt;a href=&quot;https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html&quot;&gt;OWASP SQL Injection Prevention Cheat Sheet&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Our code was vulnerable because we use string interpolation to build an SQL statement directly from user input. Instead, we should “parameterize” our query by using whatever tools our language gives us to separate the data plane (input) from the control plane (SQL). This is the idea of using variable binding (placing user input into a type of variable) with prepared statements (the rest of the SQL that we don’t want the user to be able to alter).&lt;/p&gt;

&lt;p&gt;The way to code this varies depending on the language you’re working with, so check out the OWASP SQL Injection Prevention Cheat Sheet section on &lt;a href=&quot;https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.md#defense-option-1-prepared-statements-with-parameterized-queries&quot;&gt;parameterized queries&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;other-defenses&quot;&gt;Other Defenses&lt;/h3&gt;

&lt;p&gt;Another defense against SQL injection are stored procedures, which are predefined SQL statements stored in the data table. These procedures can have parameters and can effectively be similar to constructs from different languages. Read more &lt;a href=&quot;https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.md#defense-option-2-stored-procedures&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To read more about allow-listing or escaping user input, see the rest of the &lt;a href=&quot;https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html&quot;&gt;OWASP SQL Injection Prevention Cheat Sheet&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Research SQL injection prevention for your ORM/database
    &lt;ul&gt;
      &lt;li&gt;e.g. &lt;a href=&quot;https://guides.rubyonrails.org/security.html#sql-injection&quot;&gt;https://guides.rubyonrails.org/security.html#sql-injection&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Aim to separate queries and data (parameterized queries)
    &lt;ul&gt;
      &lt;li&gt;e.g. &lt;a href=&quot;https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Query_Parameterization_Cheat_Sheet.md&quot;&gt;https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Query_Parameterization_Cheat_Sheet.md&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Consider SQL Injection when reviewing code&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;code-review&quot;&gt;Code Review&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Aim to validate user input by testing type, length, format, and range.&lt;/li&gt;
  &lt;li&gt;Avoid building SQL statements directly from user input.&lt;/li&gt;
  &lt;li&gt;Implement multiple layers of validation.&lt;/li&gt;
  &lt;li&gt;Avoid concatenating user input that is not validated; this is the primary point of entry for script injection.&lt;/li&gt;
  &lt;li&gt;You should review all code that calls execute(), exe(), and any SQL calls or commands that can call out outside resources or the command line.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;example-code-snippets&quot;&gt;Example Code Snippets&lt;/h2&gt;

&lt;h3 id=&quot;rubyactiverecord&quot;&gt;Ruby/ActiveRecord&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# SQL Injection Vulnerable Ruby/ActiveRecord&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# -- snip --&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;username = &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:username&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; AND &quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;password = &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# -- snip --&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# SQL Injection Safe Ruby/ActiveRecord&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# -- snip --&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;username: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:username&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;password: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# -- snip --&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;elixirecto&quot;&gt;Elixir/Ecto&lt;/h3&gt;

&lt;div class=&quot;language-elixir highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# SQL Injection Vulnerable Elixir/Ecto&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# -- snip --&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sd&quot;&gt;&quot;&quot;&quot;
  SELECT *
  FROM users
  WHERE username = \&apos;#{params[&quot;username&quot;]}\&apos;
  AND password = \&apos;#{params[&quot;pasword&quot;]}\&apos;;
&quot;&quot;&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Ecto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Adapters&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;MyApp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Repo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:rows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# -- snip --&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-elixir highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# SQL Injection Safe Elixir/Ecto&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# -- snip --&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sd&quot;&gt;&quot;&quot;&quot;
  SELECT * 
  FROM users 
  WHERE username = $1 
  AND password = $2;
&quot;&quot;&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Ecto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Adapters&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;MyApp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Repo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:rows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# -- snip --&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;java&quot;&gt;Java&lt;/h3&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// SQL Injection Vulnerable Java&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// -- snip --&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getParameter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getParameter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;SELECT * FROM users WHERE username = &quot;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; AND password = &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;Statement&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;statement&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createStatement&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;statement&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;executeQuery&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// -- snip --&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// SQL Injection Safe Java&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// -- snip --&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getParameter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getParameter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;SELECT * FROM users WHERE username = ? AND password = ?&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;PreparedStatement&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pstmt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;prepareStatement&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pstmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pstmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pstmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;executeQuery&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// -- snip --&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/OWASP/railsgoat/wiki/R5-A1-SQL-Injection-Concatentation&quot;&gt;https://github.com/OWASP/railsgoat/wiki/R5-A1-SQL-Injection-Concatentation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html&quot;&gt;https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.websec.ca/kb/sql_injection&quot;&gt;https://www.websec.ca/kb/sql_injection&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://owasp.org/www-community/attacks/SQL_Injection&quot;&gt;https://owasp.org/www-community/attacks/SQL_Injection&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.websec.ca/kb/sql_injection&quot;&gt;https://www.websec.ca/kb/sql_injection&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/sql_injection/exploits_of_a_mom.png&quot; alt=&quot;https://xkcd.com/327/&quot; /&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Dare to be Good Enough—You&apos;ll be Happier for it</title>
   <link href="https://thomascountz.com/2020/10/06/dare-to-be-good-enough"/>
   <updated>2020-10-06T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2020/10/06/dare-to-be-good-enough</id>
   <summary type="html">&lt;p&gt;&lt;em&gt;Originally published on &lt;a href=&quot;https://8thlight.com/blog/thomas-countz/2020/10/06/dare-to-be-good-enough.html&quot;&gt;8th Light’s blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As I sit down to write, I noticed a tug at the back of my neck—a tension forming as I begin editing my words obsessively. It’s a habit of writing characterized by my attempts at achieving the best possible outcome. “I know that I can write well,” I tell myself, “I’ve just got to strike the right tempo, tone, voice…”&lt;/p&gt;

&lt;p&gt;I often have the same sensation when I’m writing code. The giants of our industry, whose shoulders I stand upon, tell me all sorts of things that should be true about my code—the way it reads, the way it’s coupled to other parts of a system, and the amount of complexity it’s allowed to have—it’s all qualified and quantified.&lt;/p&gt;

&lt;p&gt;This tension, of balancing all of the small discrete decisions that crop up when implementing a software feature, causes me to try to painstakingly eke out the “correct” way to do things. It’s as if when faced with a choice, I scan through an endless tree of outcomes trying to game out what my best option would be. “Do I extract a shared setup for these tests, or do I duplicate it for each assertion?” “Under what circumstance should I choose the builder pattern over a factory method?” “Should this method be called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;is_ready&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;is_prepared&lt;/code&gt;?” The list goes on and on.&lt;/p&gt;

&lt;p&gt;If this sounds familiar to you, you’re not alone. People who tackle decision-making in this way are called &lt;em&gt;maximizers&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;In order to determine their optimal decision outcome, maximizers feel compelled to examine each and every alternative available, which is often infeasible in reality due to the limitations in human cognition (Roets, et al., 2012).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In theory, maximizers make the best choices, but research shows that, in practice, maximizers aren’t ever sure that the choices they make &lt;em&gt;are&lt;/em&gt; the best choices, and it’s this “not knowing” that leads to &lt;em&gt;analysis paralysis&lt;/em&gt;, and ultimately, regret.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Satisficers&lt;/em&gt;, on the other hand, are happy with whatever “good enough” choice they make:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Several options may fall within a satisficer‘s threshold for acceptability, providing greater flexibility and latitude in achieving a desired decision outcome. As soon as [they encounter] a good enough option, the satisficer can easily ignore the addition of new choices to the decision domain (Peng, 2013).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Satisficers are pragmatists at their core; the solution that works is the solution they commit to.&lt;/p&gt;

&lt;p&gt;This way of decision-making is embedded in the fabric of agile methodologies, which gives us permission to make small incremental changes over time, rather than grasping for the perfect solution from the start. From this, we know that iterative design can help us build tighter feedback loops, deliver value to stakeholders sooner, and ultimately keep our development reactive and responsive to the needs of our customers.&lt;/p&gt;

&lt;p&gt;Our projects’ bottom line isn’t the only beneficiary to thinking like a satisficer. Even though the research is still out over whether maximizers or satisficers achieve objectively better decision-making (Peng, 2013), what is clear is that satisficers are &lt;em&gt;happier&lt;/em&gt;. In fact, even if a better option occurs to them later, satisficers are less likely to experience regret (Schwartz et al., 2002).&lt;/p&gt;

&lt;p&gt;If you find yourself like me, trying to game out the best future outcomes based on limited knowledge and with bounded cognition, remember the ways of the satisficers. Make a good enough decision today, and remain flexible and forgiving in order to make another good enough decision tomorrow. Of course, we all have metaphorical hills on which we may choose to die, but choose your metaphorical battles wisely and dare to be good enough.&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;p&gt;Roets, A., Schwartz, B., &amp;amp; Guan, Y. (2012). The tyranny of choice: a cross-cultural investigation of satisficing effects on well-being. Judgment and Decision Making.&lt;/p&gt;

&lt;p&gt;Peng, Starry. “Maximizing and Satisficing in Decision-Making Dyads.” ScholarlyCommons, 2013, &lt;a href=&quot;https://repository.upenn.edu/cgi/viewcontent.cgi?referer=&amp;amp;httpsredir=1&amp;amp;article=1101&amp;amp;context=wharton_research_scholars&quot;&gt;https://repository.upenn.edu/cgi/viewcontent.cgi?referer=&amp;amp;httpsredir=1&amp;amp;article=1101&amp;amp;context=wharton_research_scholars&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Schwartz, B., Ward, A., Monterosso, J.,Lyubomirsky, S., White, K., &amp;amp; Lehman, D. R. (2002). Maximizing versus satisficing: happiness is a matter of choice. Journal of personality and social psychology.&lt;/p&gt;

&lt;p&gt;Agile Alliance. “What is Incremental Development?”, 2019, &lt;a href=&quot;https://www.agilealliance.org/glossary/incremental-development&quot;&gt;https://www.agilealliance.org/glossary/incremental-development&lt;/a&gt;&lt;/p&gt;
</summary>
   <content type="html">&lt;p&gt;&lt;em&gt;Originally published on &lt;a href=&quot;https://8thlight.com/blog/thomas-countz/2020/10/06/dare-to-be-good-enough.html&quot;&gt;8th Light’s blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As I sit down to write, I noticed a tug at the back of my neck—a tension forming as I begin editing my words obsessively. It’s a habit of writing characterized by my attempts at achieving the best possible outcome. “I know that I can write well,” I tell myself, “I’ve just got to strike the right tempo, tone, voice…”&lt;/p&gt;

&lt;p&gt;I often have the same sensation when I’m writing code. The giants of our industry, whose shoulders I stand upon, tell me all sorts of things that should be true about my code—the way it reads, the way it’s coupled to other parts of a system, and the amount of complexity it’s allowed to have—it’s all qualified and quantified.&lt;/p&gt;

&lt;p&gt;This tension, of balancing all of the small discrete decisions that crop up when implementing a software feature, causes me to try to painstakingly eke out the “correct” way to do things. It’s as if when faced with a choice, I scan through an endless tree of outcomes trying to game out what my best option would be. “Do I extract a shared setup for these tests, or do I duplicate it for each assertion?” “Under what circumstance should I choose the builder pattern over a factory method?” “Should this method be called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;is_ready&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;is_prepared&lt;/code&gt;?” The list goes on and on.&lt;/p&gt;

&lt;p&gt;If this sounds familiar to you, you’re not alone. People who tackle decision-making in this way are called &lt;em&gt;maximizers&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;In order to determine their optimal decision outcome, maximizers feel compelled to examine each and every alternative available, which is often infeasible in reality due to the limitations in human cognition (Roets, et al., 2012).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In theory, maximizers make the best choices, but research shows that, in practice, maximizers aren’t ever sure that the choices they make &lt;em&gt;are&lt;/em&gt; the best choices, and it’s this “not knowing” that leads to &lt;em&gt;analysis paralysis&lt;/em&gt;, and ultimately, regret.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Satisficers&lt;/em&gt;, on the other hand, are happy with whatever “good enough” choice they make:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Several options may fall within a satisficer‘s threshold for acceptability, providing greater flexibility and latitude in achieving a desired decision outcome. As soon as [they encounter] a good enough option, the satisficer can easily ignore the addition of new choices to the decision domain (Peng, 2013).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Satisficers are pragmatists at their core; the solution that works is the solution they commit to.&lt;/p&gt;

&lt;p&gt;This way of decision-making is embedded in the fabric of agile methodologies, which gives us permission to make small incremental changes over time, rather than grasping for the perfect solution from the start. From this, we know that iterative design can help us build tighter feedback loops, deliver value to stakeholders sooner, and ultimately keep our development reactive and responsive to the needs of our customers.&lt;/p&gt;

&lt;p&gt;Our projects’ bottom line isn’t the only beneficiary to thinking like a satisficer. Even though the research is still out over whether maximizers or satisficers achieve objectively better decision-making (Peng, 2013), what is clear is that satisficers are &lt;em&gt;happier&lt;/em&gt;. In fact, even if a better option occurs to them later, satisficers are less likely to experience regret (Schwartz et al., 2002).&lt;/p&gt;

&lt;p&gt;If you find yourself like me, trying to game out the best future outcomes based on limited knowledge and with bounded cognition, remember the ways of the satisficers. Make a good enough decision today, and remain flexible and forgiving in order to make another good enough decision tomorrow. Of course, we all have metaphorical hills on which we may choose to die, but choose your metaphorical battles wisely and dare to be good enough.&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;p&gt;Roets, A., Schwartz, B., &amp;amp; Guan, Y. (2012). The tyranny of choice: a cross-cultural investigation of satisficing effects on well-being. Judgment and Decision Making.&lt;/p&gt;

&lt;p&gt;Peng, Starry. “Maximizing and Satisficing in Decision-Making Dyads.” ScholarlyCommons, 2013, &lt;a href=&quot;https://repository.upenn.edu/cgi/viewcontent.cgi?referer=&amp;amp;httpsredir=1&amp;amp;article=1101&amp;amp;context=wharton_research_scholars&quot;&gt;https://repository.upenn.edu/cgi/viewcontent.cgi?referer=&amp;amp;httpsredir=1&amp;amp;article=1101&amp;amp;context=wharton_research_scholars&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Schwartz, B., Ward, A., Monterosso, J.,Lyubomirsky, S., White, K., &amp;amp; Lehman, D. R. (2002). Maximizing versus satisficing: happiness is a matter of choice. Journal of personality and social psychology.&lt;/p&gt;

&lt;p&gt;Agile Alliance. “What is Incremental Development?”, 2019, &lt;a href=&quot;https://www.agilealliance.org/glossary/incremental-development&quot;&gt;https://www.agilealliance.org/glossary/incremental-development&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Specify Bundler Version</title>
   <link href="https://thomascountz.com/2020/09/18/specify-bundler-version"/>
   <updated>2020-09-18T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2020/09/18/specify-bundler-version</id>
   <summary type="html">&lt;p&gt;If you’re like me, you may have versions of both Bundler 1 and Bundler 2 installed on your system. This can make it difficult to manage different codebases.&lt;/p&gt;

&lt;p&gt;Say one requires an older version, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.13.6&lt;/code&gt;. You can run the following command to install it,&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gem install bundler -v 1.13.6
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;but when you install gems,&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bundle install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;you might end up seeing something like&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;BUNDLED WITH
   2.1.4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile.lock&lt;/code&gt; file… which is &lt;em&gt;not&lt;/em&gt; the same as bundling with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.13.6&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can tell Bundler that you’d like to use a specific version of Bundler by specifying the exact version number before the command you wish to run. Like so:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bundle _x.x.x_ install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To install gems for your project using Bundler &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.13.6&lt;/code&gt;, you can use this command to force Bundler to use the correct version:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bundle _1.13.6_ install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And you should see&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;BUNDLED WITH
   1.13.6
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;at the end of your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile.lock&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Hooray!&lt;/p&gt;
</summary>
   <content type="html">&lt;p&gt;If you’re like me, you may have versions of both Bundler 1 and Bundler 2 installed on your system. This can make it difficult to manage different codebases.&lt;/p&gt;

&lt;p&gt;Say one requires an older version, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.13.6&lt;/code&gt;. You can run the following command to install it,&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gem install bundler -v 1.13.6
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;but when you install gems,&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bundle install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;you might end up seeing something like&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;BUNDLED WITH
   2.1.4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile.lock&lt;/code&gt; file… which is &lt;em&gt;not&lt;/em&gt; the same as bundling with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.13.6&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can tell Bundler that you’d like to use a specific version of Bundler by specifying the exact version number before the command you wish to run. Like so:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bundle _x.x.x_ install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To install gems for your project using Bundler &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.13.6&lt;/code&gt;, you can use this command to force Bundler to use the correct version:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bundle _1.13.6_ install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And you should see&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;BUNDLED WITH
   1.13.6
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;at the end of your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile.lock&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Hooray!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Types of Notetaking</title>
   <link href="https://thomascountz.com/2020/08/30/types-of-notetaking"/>
   <updated>2020-08-30T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2020/08/30/types-of-notetaking</id>
   <summary type="html">&lt;p&gt;Earlier this week, my pair and I struck upon something interesting, and I stopped to take notes.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Every time I see you taking notes, you’re using a new method!&lt;/p&gt;

  &lt;p&gt;— Pair&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;She’s not wrong. Over the past several years, I’ve tried many different ways of capturing my thoughts: yellow legal pads, &lt;a href=&quot;https://basecamp.com&quot;&gt;Basecamp&lt;/a&gt;, &lt;a href=&quot;https://notion.so&quot;&gt;Notion&lt;/a&gt;, &lt;a href=&quot;https://github.com/vimwiki/vimwiki&quot;&gt;Vimwiki&lt;/a&gt;, &lt;a href=&quot;https://orgmode.org/&quot;&gt;Org Mode&lt;/a&gt;, &lt;a href=&quot;https://tyke.app/&quot;&gt;Tyke&lt;/a&gt;, index cards, &lt;a href=&quot;https://www.gitbook.com/&quot;&gt;Gitbook&lt;/a&gt;, blogging with &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; and &lt;a href=&quot;https://medium.com&quot;&gt;Medium&lt;/a&gt;, Slack/Emails to self, &lt;a href=&quot;https://www.gingerlabs.com/&quot;&gt;Notability&lt;/a&gt;, and voice notes. I didn’t switch note taking styles because I liked to try new things, I switched because I hadn’t found a note-taking tool that fit all of my needs. I’ve researched and tried different note taking systems like &lt;a href=&quot;https://gettingthingsdone.com/&quot;&gt;Getting Things Done&lt;/a&gt; and &lt;a href=&quot;https://www.buildingasecondbrain.com/&quot;&gt;Building a Second Brain&lt;/a&gt;, but as soon as a system or tool caused me to think more about note taking and less about what I was taking notes on, I struggled to keep focused and motivated.&lt;/p&gt;

&lt;p&gt;I realized that I should drop all of that and let my intuition guide how I record information best. It turns out that trying to force everything I wanted to write down into one system caused me to feel frustrated and confused. For some things,  &lt;a href=&quot;https://github.com/vimwiki/vimwiki&quot;&gt;Vimwiki&lt;/a&gt; was perfect! For others, it felt clunky and difficult. A pocketbook was awesome for a while, then suddenly I felt like I could never keep things straight.&lt;/p&gt;

&lt;p&gt;Once I stopped fighting this battle, I found that there were three types of notes that I was taking and therefore there were three different tools/methods I could use to take them.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;div style=&quot;text-align: center;&quot;&gt;
  &lt;h2&gt;
    Contents
  &lt;/h2&gt;
    &lt;p&gt;&lt;a href=&quot;#actionable-todos&quot;&gt;Actionable TODOs&lt;/a&gt;&lt;/p&gt;
    &lt;p&gt;&lt;a href=&quot;#in-situ-documentation&quot;&gt;In Situ Documentation&lt;/a&gt;&lt;/p&gt;
    &lt;p&gt;&lt;a href=&quot;#long-form-reference&quot;&gt;Long-form Reference&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;actionable-todos&quot;&gt;Actionable TODOs&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Buzz. Buzz.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I check my phone. It’s a text from my husband:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;If you stop by the grocery store, I just used the last few onions. Can you grab some?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;TODOs and short-term tasks come at me from all directions and at any time. Old me would just try to remember everything, but I’ve realized just how much bandwidth and brain cycles that takes up, so I’ve taken to writing these things down. I write down things that are “actionable,” and are “obvious when they’re done,” like “take out trash,” or “fill out form DL-80.”&lt;/p&gt;

&lt;p&gt;My criteria for a TODO tool:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Readily available&lt;/strong&gt; — I need access to my TODO list at all times. I don’t know when I’ll have the opportunity to check something off my list and I don’t know where I’ll be when I need to add a new task.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Low friction&lt;/strong&gt; — Capturing a TODO needs to be a quick activity so that I can continue on with my day. Having to also schedule a reminder, add a category, a priority value, and a description, before I could “save” a task limited my willingness to commit to writing down my TODOs&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Non-ceremoneous&lt;/strong&gt; — similar to being low friction, I don’t want any other features tied to my TODO notes besides creating a task and marking as task a finished. For example, one TODO tool generated a weekly chart displaying how many tasks I completed compared to last week. I started re-structuring my TODOs to effect these outcomes and meant that I was no longer focusing on the tasks themselves.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;my-solution-a-passport-sized-notebook-and-pen&quot;&gt;My solution: a passport-sized notebook and pen&lt;/h3&gt;

&lt;p&gt;I can carry this notebook and pen most places with me, and I’m un-apologetic about what I write in it. I mix work-related things with personal task, and I scribble quick “…can you write down this number…” notes, too. Even though it’s for “tasks,” I occasionally find myself taking temporal notes like, “finished reading Convenience Store Woman” and ideas like “write blog post about note taking.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A pocketbook is an always-charged smartphone.&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;in-situ-documentation&quot;&gt;In Situ Documentation&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;Do you remember what we ended up deciding on Friday?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In a world of synchronous-first communication, after an hours long meeting, the last thing I want to do is write down everything we just talked about. More than that, after an hours long meeting, the last &lt;em&gt;last&lt;/em&gt; thing I want to do is feel like we forgot everything we talked about.&lt;/p&gt;

&lt;p&gt;Sometimes the meetings I’m in are a bit chaotic and cover a lot of ground. (Often more like &lt;a href=&quot;https://en.wikipedia.org/wiki/Mob_programming&quot;&gt;mob programming&lt;/a&gt; than a meeting). In these cases, I’ve found that rapid-fire note taking has allowed me to both 1) keep a reference of what we may have decided and 2) free up my brain to keep listening.&lt;/p&gt;

&lt;p&gt;Contemporaneous Work Notes look like this for me:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Draft-zero&lt;/strong&gt; — I think of these notes as casting a very wide net. I scribe almost continuously and more often than not, half of it is garbage. With that said, I need a tool that can help me create private unedited drafts that allow me to hone the 50% later.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Adaptable&lt;/strong&gt; — I might be taking notes during a meeting, while pairing, during code review, etc. I need a system that adapts to however my brain feels like organizing information in the moment. Besides a date, these notes can end up looking very different: a doddle here, a line reference there, a TODO might sneak in—who knows?!&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Disposible&lt;/strong&gt; — These notes depreciate in value over time. Ideally, I reference these notes shortly after taking them in order to formalize something that may need to be shared. When I’m done, the in situ notes shouldn’t contain any important information that isn’t captured in a different form elsewhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;my-solution-yellow-legal-pad-and-mechanical-pencil&quot;&gt;My solution: yellow legal pad and mechanical pencil*&lt;/h3&gt;

&lt;p&gt;Yellow legal pads are beloved (and hated) by many for their accessibility and ease of use, see: &lt;a href=&quot;https://www.legalaffairs.org/issues/May-June-2005/scene_snider_mayjun05.msp&quot;&gt;The Illustrious History of the Yellow Legal Pad&lt;/a&gt;. I fell in love with them because they feel limitless. I found that when taking notes in beautiful leather-bound $40 notebook, I became a bit too precious about what I would write in it and began to edit my notes before I put pencil to paper! On the contrary, yellow legal pads are cheap, thin, and always open. I prefer a mechanical pencil because I feel as though I can write cleaner lines with a 0.5mm lead than I can with an equally fine pen. Also, I &lt;em&gt;do&lt;/em&gt; erase things sometimes, especially if I’m trying to capture a concept in an illustration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A yellow legal pad is a private portable whiteboard.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;*In an effort to minimize my impact on the environment, I’ve moved to using &lt;a href=&quot;https://www.gingerlabs.com/&quot;&gt;Notability&lt;/a&gt; on the iPad with an Apple Pencil or folding yellow legal pad pages into four—using one section per day and both sides of the page.&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;long-form-reference&quot;&gt;Long-Form Reference&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;CUT TO&lt;/strong&gt;: Thomas is, yet again, digging through the pages of &lt;a href=&quot;https://shop.jcoglan.com/building-git/&quot;&gt;Building Git by James Coglan&lt;/a&gt; to remember how Ruby’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;open3&lt;/code&gt; module exposes child processes to IO streams.&lt;/p&gt;

&lt;p&gt;I love diving deep into different topics, often topics that I may not directly visit or use again in my day-to-day work. Take &lt;a href=&quot;https://www.thomascountz.com/tag/machine-learning/&quot;&gt;machine learning&lt;/a&gt; for example. When I first studied it, I learned quite a lot! But, just a few months later, when I revisited data science, I felt as though I was back to square one. Not only can I have notes to come back to, I’ve noticed that my comprehension increases when I’m able to write new information down in my own words.&lt;/p&gt;

&lt;p&gt;My solution to avoid this type of thing is to take long-form reference notes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Reference-able&lt;/strong&gt; — As you might be able to guess, these notes have to be notes that I can come back to and understand. That means that these aren’t draft-zero notes, they’re well crafted and continuously editable as I learn and discover new things. They’re written with the reader in mind, even on the first pass.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Structured&lt;/strong&gt; — These notes should be both hierarchical and linkable. If I taking notes on material that is related to notes I’ve taken in the past, I want to connect them somehow. This helps with referencing things later, but also helps as I’m editing.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Pleasurable&lt;/strong&gt; — It’s important to me that these notes be enjoyable to write and to read. This means the ergonomics have to be comfortable and familiar and help me to focus on the content, rather than the styling.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;my-solution-gitbookpersonal-wiki&quot;&gt;My solution: &lt;a href=&quot;https://www.gitbook.com/&quot;&gt;Gitbook&lt;/a&gt;/personal wiki&lt;/h3&gt;

&lt;p&gt;This is a new venture for me, but you can find the beginnings of this journey at &lt;a href=&quot;https://research.thomascountz.com&quot;&gt;research.thomascountz.com&lt;/a&gt;. Gitbook has switch from self-hosted to a SAAS offering in the past few years, but still offer free services for individuals. I enjoy Gitbook because the online editor is intuitive and comfortable, and all of my notes are backed by git/Github, which means exportability. Also, I like that these notes are public. They’re not quite a blog post, but being able to link directly to specific notes is helpful when sharing ideas.&lt;/p&gt;

&lt;p&gt;I’ve also taken to recording personal project documentation in wiki format. Even when I’m working alone, I prefer to write asynchronous documentation as though I’m together with a team. These project docs help me link thoughts and ideas together that would otherwise decay over time as other priorities come into focus.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A personal wiki is the brain’s external hard drive.&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;your-notes-your-way&quot;&gt;Your Notes, Your Way!&lt;/h2&gt;

&lt;p&gt;It’s with this combination of a small notebook, yellow legal pad, and personal wiki that I declutter my brain and keep focused. What’s your preferred note-taking method/tool? Do you have one that fits all your needs or are you like me: different tools for different situations? I’d love to hear your thoughts!&lt;/p&gt;
</summary>
   <content type="html">&lt;p&gt;Earlier this week, my pair and I struck upon something interesting, and I stopped to take notes.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Every time I see you taking notes, you’re using a new method!&lt;/p&gt;

  &lt;p&gt;— Pair&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;She’s not wrong. Over the past several years, I’ve tried many different ways of capturing my thoughts: yellow legal pads, &lt;a href=&quot;https://basecamp.com&quot;&gt;Basecamp&lt;/a&gt;, &lt;a href=&quot;https://notion.so&quot;&gt;Notion&lt;/a&gt;, &lt;a href=&quot;https://github.com/vimwiki/vimwiki&quot;&gt;Vimwiki&lt;/a&gt;, &lt;a href=&quot;https://orgmode.org/&quot;&gt;Org Mode&lt;/a&gt;, &lt;a href=&quot;https://tyke.app/&quot;&gt;Tyke&lt;/a&gt;, index cards, &lt;a href=&quot;https://www.gitbook.com/&quot;&gt;Gitbook&lt;/a&gt;, blogging with &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; and &lt;a href=&quot;https://medium.com&quot;&gt;Medium&lt;/a&gt;, Slack/Emails to self, &lt;a href=&quot;https://www.gingerlabs.com/&quot;&gt;Notability&lt;/a&gt;, and voice notes. I didn’t switch note taking styles because I liked to try new things, I switched because I hadn’t found a note-taking tool that fit all of my needs. I’ve researched and tried different note taking systems like &lt;a href=&quot;https://gettingthingsdone.com/&quot;&gt;Getting Things Done&lt;/a&gt; and &lt;a href=&quot;https://www.buildingasecondbrain.com/&quot;&gt;Building a Second Brain&lt;/a&gt;, but as soon as a system or tool caused me to think more about note taking and less about what I was taking notes on, I struggled to keep focused and motivated.&lt;/p&gt;

&lt;p&gt;I realized that I should drop all of that and let my intuition guide how I record information best. It turns out that trying to force everything I wanted to write down into one system caused me to feel frustrated and confused. For some things,  &lt;a href=&quot;https://github.com/vimwiki/vimwiki&quot;&gt;Vimwiki&lt;/a&gt; was perfect! For others, it felt clunky and difficult. A pocketbook was awesome for a while, then suddenly I felt like I could never keep things straight.&lt;/p&gt;

&lt;p&gt;Once I stopped fighting this battle, I found that there were three types of notes that I was taking and therefore there were three different tools/methods I could use to take them.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;div style=&quot;text-align: center;&quot;&gt;
  &lt;h2&gt;
    Contents
  &lt;/h2&gt;
    &lt;p&gt;&lt;a href=&quot;#actionable-todos&quot;&gt;Actionable TODOs&lt;/a&gt;&lt;/p&gt;
    &lt;p&gt;&lt;a href=&quot;#in-situ-documentation&quot;&gt;In Situ Documentation&lt;/a&gt;&lt;/p&gt;
    &lt;p&gt;&lt;a href=&quot;#long-form-reference&quot;&gt;Long-form Reference&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;actionable-todos&quot;&gt;Actionable TODOs&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Buzz. Buzz.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I check my phone. It’s a text from my husband:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;If you stop by the grocery store, I just used the last few onions. Can you grab some?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;TODOs and short-term tasks come at me from all directions and at any time. Old me would just try to remember everything, but I’ve realized just how much bandwidth and brain cycles that takes up, so I’ve taken to writing these things down. I write down things that are “actionable,” and are “obvious when they’re done,” like “take out trash,” or “fill out form DL-80.”&lt;/p&gt;

&lt;p&gt;My criteria for a TODO tool:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Readily available&lt;/strong&gt; — I need access to my TODO list at all times. I don’t know when I’ll have the opportunity to check something off my list and I don’t know where I’ll be when I need to add a new task.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Low friction&lt;/strong&gt; — Capturing a TODO needs to be a quick activity so that I can continue on with my day. Having to also schedule a reminder, add a category, a priority value, and a description, before I could “save” a task limited my willingness to commit to writing down my TODOs&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Non-ceremoneous&lt;/strong&gt; — similar to being low friction, I don’t want any other features tied to my TODO notes besides creating a task and marking as task a finished. For example, one TODO tool generated a weekly chart displaying how many tasks I completed compared to last week. I started re-structuring my TODOs to effect these outcomes and meant that I was no longer focusing on the tasks themselves.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;my-solution-a-passport-sized-notebook-and-pen&quot;&gt;My solution: a passport-sized notebook and pen&lt;/h3&gt;

&lt;p&gt;I can carry this notebook and pen most places with me, and I’m un-apologetic about what I write in it. I mix work-related things with personal task, and I scribble quick “…can you write down this number…” notes, too. Even though it’s for “tasks,” I occasionally find myself taking temporal notes like, “finished reading Convenience Store Woman” and ideas like “write blog post about note taking.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A pocketbook is an always-charged smartphone.&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;in-situ-documentation&quot;&gt;In Situ Documentation&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;Do you remember what we ended up deciding on Friday?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In a world of synchronous-first communication, after an hours long meeting, the last thing I want to do is write down everything we just talked about. More than that, after an hours long meeting, the last &lt;em&gt;last&lt;/em&gt; thing I want to do is feel like we forgot everything we talked about.&lt;/p&gt;

&lt;p&gt;Sometimes the meetings I’m in are a bit chaotic and cover a lot of ground. (Often more like &lt;a href=&quot;https://en.wikipedia.org/wiki/Mob_programming&quot;&gt;mob programming&lt;/a&gt; than a meeting). In these cases, I’ve found that rapid-fire note taking has allowed me to both 1) keep a reference of what we may have decided and 2) free up my brain to keep listening.&lt;/p&gt;

&lt;p&gt;Contemporaneous Work Notes look like this for me:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Draft-zero&lt;/strong&gt; — I think of these notes as casting a very wide net. I scribe almost continuously and more often than not, half of it is garbage. With that said, I need a tool that can help me create private unedited drafts that allow me to hone the 50% later.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Adaptable&lt;/strong&gt; — I might be taking notes during a meeting, while pairing, during code review, etc. I need a system that adapts to however my brain feels like organizing information in the moment. Besides a date, these notes can end up looking very different: a doddle here, a line reference there, a TODO might sneak in—who knows?!&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Disposible&lt;/strong&gt; — These notes depreciate in value over time. Ideally, I reference these notes shortly after taking them in order to formalize something that may need to be shared. When I’m done, the in situ notes shouldn’t contain any important information that isn’t captured in a different form elsewhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;my-solution-yellow-legal-pad-and-mechanical-pencil&quot;&gt;My solution: yellow legal pad and mechanical pencil*&lt;/h3&gt;

&lt;p&gt;Yellow legal pads are beloved (and hated) by many for their accessibility and ease of use, see: &lt;a href=&quot;https://www.legalaffairs.org/issues/May-June-2005/scene_snider_mayjun05.msp&quot;&gt;The Illustrious History of the Yellow Legal Pad&lt;/a&gt;. I fell in love with them because they feel limitless. I found that when taking notes in beautiful leather-bound $40 notebook, I became a bit too precious about what I would write in it and began to edit my notes before I put pencil to paper! On the contrary, yellow legal pads are cheap, thin, and always open. I prefer a mechanical pencil because I feel as though I can write cleaner lines with a 0.5mm lead than I can with an equally fine pen. Also, I &lt;em&gt;do&lt;/em&gt; erase things sometimes, especially if I’m trying to capture a concept in an illustration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A yellow legal pad is a private portable whiteboard.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;*In an effort to minimize my impact on the environment, I’ve moved to using &lt;a href=&quot;https://www.gingerlabs.com/&quot;&gt;Notability&lt;/a&gt; on the iPad with an Apple Pencil or folding yellow legal pad pages into four—using one section per day and both sides of the page.&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;long-form-reference&quot;&gt;Long-Form Reference&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;CUT TO&lt;/strong&gt;: Thomas is, yet again, digging through the pages of &lt;a href=&quot;https://shop.jcoglan.com/building-git/&quot;&gt;Building Git by James Coglan&lt;/a&gt; to remember how Ruby’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;open3&lt;/code&gt; module exposes child processes to IO streams.&lt;/p&gt;

&lt;p&gt;I love diving deep into different topics, often topics that I may not directly visit or use again in my day-to-day work. Take &lt;a href=&quot;https://www.thomascountz.com/tag/machine-learning/&quot;&gt;machine learning&lt;/a&gt; for example. When I first studied it, I learned quite a lot! But, just a few months later, when I revisited data science, I felt as though I was back to square one. Not only can I have notes to come back to, I’ve noticed that my comprehension increases when I’m able to write new information down in my own words.&lt;/p&gt;

&lt;p&gt;My solution to avoid this type of thing is to take long-form reference notes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Reference-able&lt;/strong&gt; — As you might be able to guess, these notes have to be notes that I can come back to and understand. That means that these aren’t draft-zero notes, they’re well crafted and continuously editable as I learn and discover new things. They’re written with the reader in mind, even on the first pass.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Structured&lt;/strong&gt; — These notes should be both hierarchical and linkable. If I taking notes on material that is related to notes I’ve taken in the past, I want to connect them somehow. This helps with referencing things later, but also helps as I’m editing.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Pleasurable&lt;/strong&gt; — It’s important to me that these notes be enjoyable to write and to read. This means the ergonomics have to be comfortable and familiar and help me to focus on the content, rather than the styling.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;my-solution-gitbookpersonal-wiki&quot;&gt;My solution: &lt;a href=&quot;https://www.gitbook.com/&quot;&gt;Gitbook&lt;/a&gt;/personal wiki&lt;/h3&gt;

&lt;p&gt;This is a new venture for me, but you can find the beginnings of this journey at &lt;a href=&quot;https://research.thomascountz.com&quot;&gt;research.thomascountz.com&lt;/a&gt;. Gitbook has switch from self-hosted to a SAAS offering in the past few years, but still offer free services for individuals. I enjoy Gitbook because the online editor is intuitive and comfortable, and all of my notes are backed by git/Github, which means exportability. Also, I like that these notes are public. They’re not quite a blog post, but being able to link directly to specific notes is helpful when sharing ideas.&lt;/p&gt;

&lt;p&gt;I’ve also taken to recording personal project documentation in wiki format. Even when I’m working alone, I prefer to write asynchronous documentation as though I’m together with a team. These project docs help me link thoughts and ideas together that would otherwise decay over time as other priorities come into focus.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A personal wiki is the brain’s external hard drive.&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;your-notes-your-way&quot;&gt;Your Notes, Your Way!&lt;/h2&gt;

&lt;p&gt;It’s with this combination of a small notebook, yellow legal pad, and personal wiki that I declutter my brain and keep focused. What’s your preferred note-taking method/tool? Do you have one that fits all your needs or are you like me: different tools for different situations? I’d love to hear your thoughts!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>FastBook Chapter 4 Questions &amp; Notes</title>
   <link href="https://thomascountz.com/2020/05/09/fastbook-ch4-questions"/>
   <updated>2020-05-09T00:00:00+00:00</updated>
   <id>https://thomascountz.com/2020/05/09/fastbook-ch4-questions</id>
   <summary type="html">&lt;p&gt;&lt;a href=&quot;https://www.fast.ai/&quot;&gt;Fastai&lt;/a&gt;, known for it’s MOOCs, is working on a book, &lt;a href=&quot;https://github.com/fastai/Fastbook&quot;&gt;Fastbook&lt;/a&gt; to go along with their new MOOC starting July 2020. In my eagerness, I’ve been going through the draft of the book (linked above, though they may remove it after publication) and have been coding alongside on &lt;a href=&quot;https://www.kaggle.com/thomascountz&quot;&gt;Kaggle&lt;/a&gt;. At the end of each chapter of the book is a list of questions for the reader/students to answer. I’ve found these questions to be rigorous and useful to deepen my understanding.&lt;/p&gt;

&lt;p&gt;This blog post might not be useful to anyone besides myself, but I want to keep my answers to these questions as a reference somewhere other than a Kaggle notebook.&lt;/p&gt;

&lt;h3 id=&quot;how-is-a-greyscale-image-represented-on-a-computer-how-about-a-color-image&quot;&gt;How is a greyscale image represented on a computer? How about a color image?&lt;/h3&gt;

&lt;p&gt;A greysale image is represented by a matrix/rank 2 tensor/grid of pixels with a value between 0 and 255. Color images are a rank 3 tensor, height and width along two dimensions and third dimension representing values between 0 and 255 for redness, greenness, and blueness.&lt;/p&gt;

&lt;h3 id=&quot;how-are-the-files-and-folders-in-the-mnist_sample-dataset-structured-why&quot;&gt;How are the files and folders in the MNIST_SAMPLE dataset structured? Why?&lt;/h3&gt;

&lt;p&gt;There are two directories: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/train&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/valid&lt;/code&gt;, which each contain two directories: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/3&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/7&lt;/code&gt;. Inside those directories are images of the respective digit. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;train/&lt;/code&gt; directory contains the majority of images to be used to train a model likewise the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/valid&lt;/code&gt; directory contains images to validate a model. There’s also a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;labels.csv&lt;/code&gt; file which likely contains a mapping between file names and digit label.&lt;/p&gt;

&lt;h3 id=&quot;explain-how-the-pixel-similarity-approach-to-classifying-digits-works&quot;&gt;Explain how the “pixel similarity” approach to classifying digits works.&lt;/h3&gt;

&lt;p&gt;The idea behind this approach was to look at all of the images for a given digit and compute the average value for each pixel across all images. Then, by comparing an unlabeled digit to that “average” digit, we could determine how similar or dissimilar the unknown image was to the known average.&lt;/p&gt;

&lt;h3 id=&quot;what-is-a-list-comprehension-create-one-now-that-selects-odd-numbers-from-a-list-and-doubles-them&quot;&gt;What is a list comprehension? Create one now that selects odd numbers from a list and doubles them.&lt;/h3&gt;

&lt;p&gt;It’s a way of mapping over an enumerable object in Python&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;what-is-a-rank-3-tensor&quot;&gt;What is a “rank 3 tensor”?&lt;/h3&gt;

&lt;p&gt;A rank 3 tensor is a tensor with three dimensions, such as a color photo.&lt;/p&gt;

&lt;h3 id=&quot;what-is-the-difference-between-tensor-rank-and-shape-how-do-you-get-the-rank-from-the-shape&quot;&gt;What is the difference between tensor rank and shape? How do you get the rank from the shape?&lt;/h3&gt;

&lt;p&gt;A tensor’s rank is equivalent to the number of dimensions a tensor has. It’s shape is how many values exist in each dimension. Because the shape tells us the number of values per dimension, we can determine the number of values returned to determine the rank.&lt;/p&gt;

&lt;h3 id=&quot;what-are-rmse-and-l1-norm&quot;&gt;What are RMSE and L1 norm?&lt;/h3&gt;

&lt;p&gt;RMSE, or root mean square error is the average difference of the prediction and the label squared and then 2nd rooted?&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;mse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yhat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yhat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sqrt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;L1 norm is the average of the absolute value of the difference between y and yhat&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;l1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yhat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yhat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;how-can-you-apply-a-calculation-on-thousands-of-numbers-at-once-many-thousands-of-times-faster-than-a-python-loop&quot;&gt;How can you apply a calculation on thousands of numbers at once, many thousands of times faster than a Python loop?&lt;/h3&gt;

&lt;p&gt;Broadcasting.&lt;/p&gt;

&lt;h3 id=&quot;create-a-3x3-tensor-or-array-containing-the-numbers-from-1-to-9-double-it-select-the-bottom-right-4-numbers&quot;&gt;Create a 3x3 tensor or array containing the numbers from 1 to 9. Double it. Select the bottom right 4 numbers.&lt;/h3&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;reshape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;arrange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;what-is-broadcasting&quot;&gt;What is broadcasting?&lt;/h3&gt;

&lt;p&gt;Broadcasting is traditionally used to support computation between two unequal tensor with the side effect of not needing to copy any data. This side effect can be taken advantage of for applying calculation to two tensors even if they are equal in rank.&lt;/p&gt;

&lt;p&gt;This happens implicitly in Pytorch when operating of two vectors, for example:&lt;/p&gt;

&lt;p&gt;Non-broadcasting: slow&lt;/p&gt;
&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;zip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Broadcasting: fast&lt;/p&gt;
&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;are-metrics-generally-calculated-using-the-training-set-or-the-validation-set-why&quot;&gt;Are metrics generally calculated using the training set, or the validation set? Why?&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;A metric is a function that measures quality of the model’s predictions using the validation set, and will be printed at the end of each epoch. —Fastbook, Ch. 4&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We use a metric to judge our network’s ability to accurately predict outputs for data is hasn’t seen before.&lt;/p&gt;

&lt;h3 id=&quot;what-is-sgd&quot;&gt;What is SGD?&lt;/h3&gt;

&lt;p&gt;Gradient Descent is the process by which we update the weights/parameters/coefficients of our model in order to minimize the loss function. By taking the gradient/derivative of our loss function, we can determine how a change in our parameters would result in a change to the output. We can use that gradient to update our weights in proportion to the learning rate. Stochastic just means measure loss and update our weights in in “batches.” Some definitions say that “Stochastic” means we calculate and update for the entire training set.&lt;/p&gt;

&lt;h3 id=&quot;why-does-sgd-use-mini-batches&quot;&gt;Why does SGD use mini batches?&lt;/h3&gt;

&lt;p&gt;Updating parameters after every training example takes a long time and can mean sporadic jumps in parameters after each example as the network tries to optimize for each individual example. Waiting until after going through the entire dataset can be impossible for large datasets that don’t fit into memory. It’s inefficient and doesn’t necessarily provide better results. Mini-batches solve both of these problems. We reduce the variance between each parameter update which can smooth out convergence, and we don’t run into memory issues waiting for the entire training to complete.&lt;/p&gt;

&lt;h3 id=&quot;what-are-the-7-steps-in-sgd-for-machine-learning&quot;&gt;What are the 7 steps in SGD for machine learning?&lt;/h3&gt;

&lt;p&gt;Initialize weights, Predict, calculate loss, determine gradient, update parameters, repeat and stop.&lt;/p&gt;

&lt;h3 id=&quot;how-do-we-initialize-the-weights-in-a-model&quot;&gt;How do we initialize the weights in a model?&lt;/h3&gt;

&lt;p&gt;Randomly. There are other methods, but randomly is a good starting point.&lt;/p&gt;

&lt;h3 id=&quot;what-is-loss&quot;&gt;What is “loss”?&lt;/h3&gt;

&lt;p&gt;Loss is the function that tells us how well or how poorly our model predicted an output for a example.&lt;/p&gt;

&lt;h3 id=&quot;why-cant-we-always-use-a-high-learning-rate&quot;&gt;Why can’t we always use a high learning rate?&lt;/h3&gt;

&lt;p&gt;The learning rate is what we use gently adjust the parameters. A high learning rate can cause our loss to jump around sporadically as we attempt to minimize it.&lt;/p&gt;

&lt;h3 id=&quot;what-is-a-gradient&quot;&gt;What is a “gradient”?&lt;/h3&gt;

&lt;p&gt;The gradient of a function, denoted as follows, is the vector of partial derivatives with respect to all of the independent variables, aka, the parameters.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;derivative&lt;/em&gt;, it’s a function that describes the slope of another function at a given point. It tells us how “quickly” a function changes at a certain input.&lt;/p&gt;

&lt;p&gt;To calculate the partial derivative of a single parameter, you hold all other parameters constant. After computing all of the partial derivatives, they’re collected into a vector call the gradient.&lt;/p&gt;

&lt;h3 id=&quot;do-you-need-to-know-how-to-calculate-gradients-yourself&quot;&gt;Do you need to know how to calculate gradients yourself?&lt;/h3&gt;

&lt;p&gt;Nope. Thanks Pytorch!&lt;/p&gt;

&lt;h3 id=&quot;why-cant-we-use-accuracy-as-a-loss-function&quot;&gt;Why can’t we use accuracy as a loss function?&lt;/h3&gt;

&lt;p&gt;Accuracy is for humans to consume. It tells us how well a model is at prediction examples that it has never seen before (in the validate set) overall. Accuracy isn’t necessarily a function from which we can calculate a gradient/derivative in order update our weights. In the MNIST model in this chapter, a small nudge in a parameter won’t necessarily affect accuracy, unless that small nudge changes a prediction from a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt; or vice versa.&lt;/p&gt;

&lt;h3 id=&quot;draw-the-sigmoid-function-what-is-special-about-its-shape&quot;&gt;Draw the sigmoid function. What is special about its shape?&lt;/h3&gt;

&lt;p&gt;It looks like an S on it side with horizontal asymptotes a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y=0&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y=-1&lt;/code&gt;. The smooth curve makes it special because it helps us to gently calculate derivatives of our loss.&lt;/p&gt;

&lt;h3 id=&quot;what-is-the-difference-between-loss-and-metric&quot;&gt;What is the difference between loss and metric?&lt;/h3&gt;

&lt;p&gt;A we use a metric to determine human-interpretable accuracy, the model uses loss to determine how to update weights.&lt;/p&gt;

&lt;h3 id=&quot;what-is-the-function-to-calculate-new-weights-using-a-learning-rate&quot;&gt;What is the function to calculate new weights using a learning rate?&lt;/h3&gt;

&lt;p&gt;In this example: Stochastic Gradient Descent&lt;/p&gt;

&lt;h3 id=&quot;what-does-the-dataloader-class-do&quot;&gt;What does the DataLoader class do?&lt;/h3&gt;

&lt;p&gt;DataLoader will return an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(x, y)&lt;/code&gt; tuple for our model, divide the data into training and validation sets, and create mini batches&lt;/p&gt;

&lt;h3 id=&quot;write-pseudo-code-showing-the-basic-steps-taken-each-epoch-for-sgd&quot;&gt;Write pseudo-code showing the basic steps taken each epoch for SGD.&lt;/h3&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;prediction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;loss&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prediction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;backward&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;create-a-function-which-if-passed-two-arguments-1234-and-abcd-returns-1-a-2-b-3-c-4-d-what-is-special-about-that-output-data-structure&quot;&gt;Create a function which, if passed two arguments [1,2,3,4] and ‘abcd’, returns [(1, ‘a’), (2, ‘b’), (3, ‘c’), (4, ‘d’)]. What is special about that output data structure?&lt;/h3&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;zip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It returns a list of tuples.&lt;/p&gt;

&lt;h3 id=&quot;what-does-view-do-in-pytorch&quot;&gt;What does view do in PyTorch?&lt;/h3&gt;

&lt;p&gt;From the &lt;a href=&quot;https://pytorch.org/docs/stable/tensor_view.html&quot;&gt;docs&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;PyTorch allows a tensor to be a View of an existing tensor. View tensor shares the same underlying data with its base tensor. Supporting View avoids explicit data copy, thus allows us to do fast and memory efficient reshaping, slicing and element-wise operations.&lt;/p&gt;

&lt;p&gt;For example, to get a view of an existing tensor t, you can call t.view(…).&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.2108&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.4824&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.4418&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.9436&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.9554&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5866&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.7631&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.2809&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.2934&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.7608&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.7741&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.6948&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.0813&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5682&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.8023&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.3858&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]])&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.2108&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.4824&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.4418&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.9436&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.9554&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5866&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.7631&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.2809&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.2934&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.7608&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.7741&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.6948&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0813&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5682&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.8023&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.3858&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]])&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;storage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;data_ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;storage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;data_ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# `t` and `b` share the same underlying data.
&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Modifying view tensor changes base tensor as well.
&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;3.14&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;3.14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;what-are-the-bias-parameters-in-a-neural-network-why-do-we-need-them&quot;&gt;What are the “bias” parameters in a neural network? Why do we need them?&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;Bias terms are additional constants attached to neurons and added to the weighted input before the activation function is applied. Bias terms help models represent patterns that do not necessarily pass through the origin. For example, if all your features were 0, would your output also be zero? Is it possible there is some base value upon which your features have an effect? Bias terms typically accompany weights and must also be learned by your model. —&lt;a href=&quot;https://ml-cheatsheet.readthedocs.io/en/latest/nn_concepts.html?highlight=bias#bias&quot;&gt;https://ml-cheatsheet.readthedocs.io/en/latest/nn_concepts.html?highlight=bias#bias&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;what-does-the--operator-do-in-python&quot;&gt;What does the @ operator do in python?&lt;/h3&gt;

&lt;p&gt;Matrix multiplication ( &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*&lt;/code&gt; is element-wise multiplication)&lt;/p&gt;

&lt;h3 id=&quot;what-does-the-backward-method-do&quot;&gt;What does the backward method do?&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loss.backward()&lt;/code&gt; computes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dloss/dx&lt;/code&gt; for every parameter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; which has &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requires_grad=True&lt;/code&gt;. These are accumulated into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x.grad&lt;/code&gt; for every parameter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;. In pseudo-code:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;x.grad += dloss/dx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://medium.com/@zhang_yang/how-pytorch-tensors-backward-accumulates-gradient-8d1bf675579b&quot;&gt;https://medium.com/@zhang_yang/how-pytorch-tensors-backward-accumulates-gradient-8d1bf675579b&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;why-do-we-have-to-zero-the-gradients&quot;&gt;Why do we have to zero the gradients?&lt;/h3&gt;

&lt;p&gt;See link above, pytorch will &lt;em&gt;accumulate&lt;/em&gt; the gradient from all operations.&lt;/p&gt;

&lt;h3 id=&quot;what-information-do-we-have-to-pass-to-learner&quot;&gt;What information do we have to pass to Learner?&lt;/h3&gt;

&lt;p&gt;The DataLoader that contains the data, the model, the optimization function (e.g. SGD), loss functions (e.g. MSE), and any metrics to print&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;learn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Learner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dls&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;28&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;28&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;opt_func&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SGD&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;loss_func&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mnist_loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;metrics&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;batch_accuracy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;show-python-or-pseudo-code-for-the-basic-steps-of-a-training-loop&quot;&gt;Show python or pseudo-code for the basic steps of a training loop.&lt;/h3&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;epoch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;prediction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;loss&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prediction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;backward&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;what-is-relu-draw-a-plot-of-it-for-values-from--2-to-2&quot;&gt;What is “ReLU”? Draw a plot of it for values from -2 to +2.&lt;/h3&gt;

&lt;p&gt;A ReLU, or rectified linear unit, replaces every negative number with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt;&lt;/p&gt;

&lt;h3 id=&quot;what-is-an-activation-function&quot;&gt;What is an “activation function”?&lt;/h3&gt;

&lt;p&gt;I understand an activation function as being a non-linear layer in a neural network. This has the effect of allowing or preventing a certain “neuron” from activating depending on a threshold. For a ReLU, only positive values in a previous layer would be passed forward to the next layer. Similarly, a unit step function only passes values greater than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; to the subsequent layers, but all positive values are passed on as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;whats-the-difference-between-frelu-and-nnrelu&quot;&gt;What’s the difference between F.relu and nn.ReLU?&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F.relu&lt;/code&gt; is the function, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nn.ReLU&lt;/code&gt; is the Pytorch module module.&lt;/p&gt;

&lt;h3 id=&quot;the-universal-approximation-theorem-shows-that-any-function-can-be-approximated-as-closely-as-needed-using-just-one-nonlinearity-so-why-do-we-normally-use-more&quot;&gt;The universal approximation theorem shows that any function can be approximated as closely as needed using just one nonlinearity. So why do we normally use more?&lt;/h3&gt;

&lt;p&gt;Efficiency and performance. With deeper models, we don’t need as many parameters. Smaller matrices with more layers &amp;gt; larger matrices with fewer layers.&lt;/p&gt;
</summary>
   <content type="html">&lt;p&gt;&lt;a href=&quot;https://www.fast.ai/&quot;&gt;Fastai&lt;/a&gt;, known for it’s MOOCs, is working on a book, &lt;a href=&quot;https://github.com/fastai/Fastbook&quot;&gt;Fastbook&lt;/a&gt; to go along with their new MOOC starting July 2020. In my eagerness, I’ve been going through the draft of the book (linked above, though they may remove it after publication) and have been coding alongside on &lt;a href=&quot;https://www.kaggle.com/thomascountz&quot;&gt;Kaggle&lt;/a&gt;. At the end of each chapter of the book is a list of questions for the reader/students to answer. I’ve found these questions to be rigorous and useful to deepen my understanding.&lt;/p&gt;

&lt;p&gt;This blog post might not be useful to anyone besides myself, but I want to keep my answers to these questions as a reference somewhere other than a Kaggle notebook.&lt;/p&gt;

&lt;h3 id=&quot;how-is-a-greyscale-image-represented-on-a-computer-how-about-a-color-image&quot;&gt;How is a greyscale image represented on a computer? How about a color image?&lt;/h3&gt;

&lt;p&gt;A greysale image is represented by a matrix/rank 2 tensor/grid of pixels with a value between 0 and 255. Color images are a rank 3 tensor, height and width along two dimensions and third dimension representing values between 0 and 255 for redness, greenness, and blueness.&lt;/p&gt;

&lt;h3 id=&quot;how-are-the-files-and-folders-in-the-mnist_sample-dataset-structured-why&quot;&gt;How are the files and folders in the MNIST_SAMPLE dataset structured? Why?&lt;/h3&gt;

&lt;p&gt;There are two directories: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/train&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/valid&lt;/code&gt;, which each contain two directories: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/3&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/7&lt;/code&gt;. Inside those directories are images of the respective digit. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;train/&lt;/code&gt; directory contains the majority of images to be used to train a model likewise the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/valid&lt;/code&gt; directory contains images to validate a model. There’s also a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;labels.csv&lt;/code&gt; file which likely contains a mapping between file names and digit label.&lt;/p&gt;

&lt;h3 id=&quot;explain-how-the-pixel-similarity-approach-to-classifying-digits-works&quot;&gt;Explain how the “pixel similarity” approach to classifying digits works.&lt;/h3&gt;

&lt;p&gt;The idea behind this approach was to look at all of the images for a given digit and compute the average value for each pixel across all images. Then, by comparing an unlabeled digit to that “average” digit, we could determine how similar or dissimilar the unknown image was to the known average.&lt;/p&gt;

&lt;h3 id=&quot;what-is-a-list-comprehension-create-one-now-that-selects-odd-numbers-from-a-list-and-doubles-them&quot;&gt;What is a list comprehension? Create one now that selects odd numbers from a list and doubles them.&lt;/h3&gt;

&lt;p&gt;It’s a way of mapping over an enumerable object in Python&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;what-is-a-rank-3-tensor&quot;&gt;What is a “rank 3 tensor”?&lt;/h3&gt;

&lt;p&gt;A rank 3 tensor is a tensor with three dimensions, such as a color photo.&lt;/p&gt;

&lt;h3 id=&quot;what-is-the-difference-between-tensor-rank-and-shape-how-do-you-get-the-rank-from-the-shape&quot;&gt;What is the difference between tensor rank and shape? How do you get the rank from the shape?&lt;/h3&gt;

&lt;p&gt;A tensor’s rank is equivalent to the number of dimensions a tensor has. It’s shape is how many values exist in each dimension. Because the shape tells us the number of values per dimension, we can determine the number of values returned to determine the rank.&lt;/p&gt;

&lt;h3 id=&quot;what-are-rmse-and-l1-norm&quot;&gt;What are RMSE and L1 norm?&lt;/h3&gt;

&lt;p&gt;RMSE, or root mean square error is the average difference of the prediction and the label squared and then 2nd rooted?&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;mse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yhat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yhat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sqrt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;L1 norm is the average of the absolute value of the difference between y and yhat&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;l1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yhat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yhat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;how-can-you-apply-a-calculation-on-thousands-of-numbers-at-once-many-thousands-of-times-faster-than-a-python-loop&quot;&gt;How can you apply a calculation on thousands of numbers at once, many thousands of times faster than a Python loop?&lt;/h3&gt;

&lt;p&gt;Broadcasting.&lt;/p&gt;

&lt;h3 id=&quot;create-a-3x3-tensor-or-array-containing-the-numbers-from-1-to-9-double-it-select-the-bottom-right-4-numbers&quot;&gt;Create a 3x3 tensor or array containing the numbers from 1 to 9. Double it. Select the bottom right 4 numbers.&lt;/h3&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;reshape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;arrange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;what-is-broadcasting&quot;&gt;What is broadcasting?&lt;/h3&gt;

&lt;p&gt;Broadcasting is traditionally used to support computation between two unequal tensor with the side effect of not needing to copy any data. This side effect can be taken advantage of for applying calculation to two tensors even if they are equal in rank.&lt;/p&gt;

&lt;p&gt;This happens implicitly in Pytorch when operating of two vectors, for example:&lt;/p&gt;

&lt;p&gt;Non-broadcasting: slow&lt;/p&gt;
&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;zip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Broadcasting: fast&lt;/p&gt;
&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;are-metrics-generally-calculated-using-the-training-set-or-the-validation-set-why&quot;&gt;Are metrics generally calculated using the training set, or the validation set? Why?&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;A metric is a function that measures quality of the model’s predictions using the validation set, and will be printed at the end of each epoch. —Fastbook, Ch. 4&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We use a metric to judge our network’s ability to accurately predict outputs for data is hasn’t seen before.&lt;/p&gt;

&lt;h3 id=&quot;what-is-sgd&quot;&gt;What is SGD?&lt;/h3&gt;

&lt;p&gt;Gradient Descent is the process by which we update the weights/parameters/coefficients of our model in order to minimize the loss function. By taking the gradient/derivative of our loss function, we can determine how a change in our parameters would result in a change to the output. We can use that gradient to update our weights in proportion to the learning rate. Stochastic just means measure loss and update our weights in in “batches.” Some definitions say that “Stochastic” means we calculate and update for the entire training set.&lt;/p&gt;

&lt;h3 id=&quot;why-does-sgd-use-mini-batches&quot;&gt;Why does SGD use mini batches?&lt;/h3&gt;

&lt;p&gt;Updating parameters after every training example takes a long time and can mean sporadic jumps in parameters after each example as the network tries to optimize for each individual example. Waiting until after going through the entire dataset can be impossible for large datasets that don’t fit into memory. It’s inefficient and doesn’t necessarily provide better results. Mini-batches solve both of these problems. We reduce the variance between each parameter update which can smooth out convergence, and we don’t run into memory issues waiting for the entire training to complete.&lt;/p&gt;

&lt;h3 id=&quot;what-are-the-7-steps-in-sgd-for-machine-learning&quot;&gt;What are the 7 steps in SGD for machine learning?&lt;/h3&gt;

&lt;p&gt;Initialize weights, Predict, calculate loss, determine gradient, update parameters, repeat and stop.&lt;/p&gt;

&lt;h3 id=&quot;how-do-we-initialize-the-weights-in-a-model&quot;&gt;How do we initialize the weights in a model?&lt;/h3&gt;

&lt;p&gt;Randomly. There are other methods, but randomly is a good starting point.&lt;/p&gt;

&lt;h3 id=&quot;what-is-loss&quot;&gt;What is “loss”?&lt;/h3&gt;

&lt;p&gt;Loss is the function that tells us how well or how poorly our model predicted an output for a example.&lt;/p&gt;

&lt;h3 id=&quot;why-cant-we-always-use-a-high-learning-rate&quot;&gt;Why can’t we always use a high learning rate?&lt;/h3&gt;

&lt;p&gt;The learning rate is what we use gently adjust the parameters. A high learning rate can cause our loss to jump around sporadically as we attempt to minimize it.&lt;/p&gt;

&lt;h3 id=&quot;what-is-a-gradient&quot;&gt;What is a “gradient”?&lt;/h3&gt;

&lt;p&gt;The gradient of a function, denoted as follows, is the vector of partial derivatives with respect to all of the independent variables, aka, the parameters.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;derivative&lt;/em&gt;, it’s a function that describes the slope of another function at a given point. It tells us how “quickly” a function changes at a certain input.&lt;/p&gt;

&lt;p&gt;To calculate the partial derivative of a single parameter, you hold all other parameters constant. After computing all of the partial derivatives, they’re collected into a vector call the gradient.&lt;/p&gt;

&lt;h3 id=&quot;do-you-need-to-know-how-to-calculate-gradients-yourself&quot;&gt;Do you need to know how to calculate gradients yourself?&lt;/h3&gt;

&lt;p&gt;Nope. Thanks Pytorch!&lt;/p&gt;

&lt;h3 id=&quot;why-cant-we-use-accuracy-as-a-loss-function&quot;&gt;Why can’t we use accuracy as a loss function?&lt;/h3&gt;

&lt;p&gt;Accuracy is for humans to consume. It tells us how well a model is at prediction examples that it has never seen before (in the validate set) overall. Accuracy isn’t necessarily a function from which we can calculate a gradient/derivative in order update our weights. In the MNIST model in this chapter, a small nudge in a parameter won’t necessarily affect accuracy, unless that small nudge changes a prediction from a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt; or vice versa.&lt;/p&gt;

&lt;h3 id=&quot;draw-the-sigmoid-function-what-is-special-about-its-shape&quot;&gt;Draw the sigmoid function. What is special about its shape?&lt;/h3&gt;

&lt;p&gt;It looks like an S on it side with horizontal asymptotes a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y=0&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y=-1&lt;/code&gt;. The smooth curve makes it special because it helps us to gently calculate derivatives of our loss.&lt;/p&gt;

&lt;h3 id=&quot;what-is-the-difference-between-loss-and-metric&quot;&gt;What is the difference between loss and metric?&lt;/h3&gt;

&lt;p&gt;A we use a metric to determine human-interpretable accuracy, the model uses loss to determine how to update weights.&lt;/p&gt;

&lt;h3 id=&quot;what-is-the-function-to-calculate-new-weights-using-a-learning-rate&quot;&gt;What is the function to calculate new weights using a learning rate?&lt;/h3&gt;

&lt;p&gt;In this example: Stochastic Gradient Descent&lt;/p&gt;

&lt;h3 id=&quot;what-does-the-dataloader-class-do&quot;&gt;What does the DataLoader class do?&lt;/h3&gt;

&lt;p&gt;DataLoader will return an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(x, y)&lt;/code&gt; tuple for our model, divide the data into training and validation sets, and create mini batches&lt;/p&gt;

&lt;h3 id=&quot;write-pseudo-code-showing-the-basic-steps-taken-each-epoch-for-sgd&quot;&gt;Write pseudo-code showing the basic steps taken each epoch for SGD.&lt;/h3&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;prediction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;loss&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prediction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;backward&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;create-a-function-which-if-passed-two-arguments-1234-and-abcd-returns-1-a-2-b-3-c-4-d-what-is-special-about-that-output-data-structure&quot;&gt;Create a function which, if passed two arguments [1,2,3,4] and ‘abcd’, returns [(1, ‘a’), (2, ‘b’), (3, ‘c’), (4, ‘d’)]. What is special about that output data structure?&lt;/h3&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;zip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It returns a list of tuples.&lt;/p&gt;

&lt;h3 id=&quot;what-does-view-do-in-pytorch&quot;&gt;What does view do in PyTorch?&lt;/h3&gt;

&lt;p&gt;From the &lt;a href=&quot;https://pytorch.org/docs/stable/tensor_view.html&quot;&gt;docs&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;PyTorch allows a tensor to be a View of an existing tensor. View tensor shares the same underlying data with its base tensor. Supporting View avoids explicit data copy, thus allows us to do fast and memory efficient reshaping, slicing and element-wise operations.&lt;/p&gt;

&lt;p&gt;For example, to get a view of an existing tensor t, you can call t.view(…).&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.2108&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.4824&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.4418&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.9436&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.9554&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5866&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.7631&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.2809&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.2934&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.7608&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.7741&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.6948&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.0813&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5682&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.8023&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.3858&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]])&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.2108&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.4824&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.4418&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.9436&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.9554&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5866&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.7631&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.2809&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.2934&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.7608&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.7741&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.6948&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0813&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5682&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.8023&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.3858&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]])&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;storage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;data_ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;storage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;data_ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# `t` and `b` share the same underlying data.
&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Modifying view tensor changes base tensor as well.
&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;3.14&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;3.14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;what-are-the-bias-parameters-in-a-neural-network-why-do-we-need-them&quot;&gt;What are the “bias” parameters in a neural network? Why do we need them?&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;Bias terms are additional constants attached to neurons and added to the weighted input before the activation function is applied. Bias terms help models represent patterns that do not necessarily pass through the origin. For example, if all your features were 0, would your output also be zero? Is it possible there is some base value upon which your features have an effect? Bias terms typically accompany weights and must also be learned by your model. —&lt;a href=&quot;https://ml-cheatsheet.readthedocs.io/en/latest/nn_concepts.html?highlight=bias#bias&quot;&gt;https://ml-cheatsheet.readthedocs.io/en/latest/nn_concepts.html?highlight=bias#bias&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;what-does-the--operator-do-in-python&quot;&gt;What does the @ operator do in python?&lt;/h3&gt;

&lt;p&gt;Matrix multiplication ( &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*&lt;/code&gt; is element-wise multiplication)&lt;/p&gt;

&lt;h3 id=&quot;what-does-the-backward-method-do&quot;&gt;What does the backward method do?&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loss.backward()&lt;/code&gt; computes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dloss/dx&lt;/code&gt; for every parameter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; which has &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requires_grad=True&lt;/code&gt;. These are accumulated into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x.grad&lt;/code&gt; for every parameter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;. In pseudo-code:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;x.grad += dloss/dx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://medium.com/@zhang_yang/how-pytorch-tensors-backward-accumulates-gradient-8d1bf675579b&quot;&gt;https://medium.com/@zhang_yang/how-pytorch-tensors-backward-accumulates-gradient-8d1bf675579b&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;why-do-we-have-to-zero-the-gradients&quot;&gt;Why do we have to zero the gradients?&lt;/h3&gt;

&lt;p&gt;See link above, pytorch will &lt;em&gt;accumulate&lt;/em&gt; the gradient from all operations.&lt;/p&gt;

&lt;h3 id=&quot;what-information-do-we-have-to-pass-to-learner&quot;&gt;What information do we have to pass to Learner?&lt;/h3&gt;

&lt;p&gt;The DataLoader that contains the data, the model, the optimization function (e.g. SGD), loss functions (e.g. MSE), and any metrics to print&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;learn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Learner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dls&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;28&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;28&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;opt_func&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SGD&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;loss_func&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mnist_loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;metrics&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;batch_accuracy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;show-python-or-pseudo-code-for-the-basic-steps-of-a-training-loop&quot;&gt;Show python or pseudo-code for the basic steps of a training loop.&lt;/h3&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;epoch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;prediction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;loss&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prediction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;backward&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;what-is-relu-draw-a-plot-of-it-for-values-from--2-to-2&quot;&gt;What is “ReLU”? Draw a plot of it for values from -2 to +2.&lt;/h3&gt;

&lt;p&gt;A ReLU, or rectified linear unit, replaces every negative number with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt;&lt;/p&gt;

&lt;h3 id=&quot;what-is-an-activation-function&quot;&gt;What is an “activation function”?&lt;/h3&gt;

&lt;p&gt;I understand an activation function as being a non-linear layer in a neural network. This has the effect of allowing or preventing a certain “neuron” from activating depending on a threshold. For a ReLU, only positive values in a previous layer would be passed forward to the next layer. Similarly, a unit step function only passes values greater than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; to the subsequent layers, but all positive values are passed on as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;whats-the-difference-between-frelu-and-nnrelu&quot;&gt;What’s the difference between F.relu and nn.ReLU?&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F.relu&lt;/code&gt; is the function, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nn.ReLU&lt;/code&gt; is the Pytorch module module.&lt;/p&gt;

&lt;h3 id=&quot;the-universal-approximation-theorem-shows-that-any-function-can-be-approximated-as-closely-as-needed-using-just-one-nonlinearity-so-why-do-we-normally-use-more&quot;&gt;The universal approximation theorem shows that any function can be approximated as closely as needed using just one nonlinearity. So why do we normally use more?&lt;/h3&gt;

&lt;p&gt;Efficiency and performance. With deeper models, we don’t need as many parameters. Smaller matrices with more layers &amp;gt; larger matrices with fewer layers.&lt;/p&gt;
</content>
 </entry>
 

</feed>
