swaits.comStephen WaitsZola2024-02-06T00:00:00+00:00https://swaits.com/atom.xmlThe Fable of the Fumbling Founders2024-02-06T00:00:00+00:002024-02-06T00:00:00+00:00
Unknown
https://swaits.com/fable-of-fumbling-founders/<h3 id="a-voyage-to-vanity-s-verge">A Voyage to Vanity’s Verge</h3>
<p>In the vast virtual village of vitality ventures, there thrives a theoretical
troupe, tasked with tethering tech to the temple of the self. This saga, spun
from the silk of satire, shadows a ship not steered by stars, but sunk by
stubbornness. A cautionary chronicle, cloaked in comedy, yet cradled in candid
critique.</p>
<p>Within the walls of this whimsical workshop, where wires weave wellness and
widgets whisper workouts, a pattern prevails, as persistent as it is perplexing.
Each epoch echoes the errors of yesteryears, a relentless rerun of riddles and
ruins. Behold the beacon of blame, brightly burning—the founding fathers, these
monarchs of myopia.</p>
<p>Our regal ringmaster, a sovereign of the status quo, not a royal by title but a
tsar trapped in tradition, twirls the company with a tight grip, guiding it not
to glory, but to the gallows. Beside him, the advocate of antiquation, a
technocrat tethered to the tactics of old, together they stand, titans on a
titanically flawed voyage.</p>
<p>Oh, how the hubris hums, a hymn to the heavens, heralding havoc. For in their
hands, they hold not the helm of innovation, but the harpoon of hubris, hunting
the very hope they harbor. As audacity amplifies, accountability absconds,
leaving behind a legacy not of laurels, but of lament.</p>
<p>The tale tells of a terrain so treacherous, where the soil, saturated with the
salt of sweat and tears, no longer nurtures, but nullifies. A land where the
seeds of success, stifled by the shadow of the same sinister sentinels, sprout
only to suffocate.</p>
<p>Witness, then, the waltz of the willful, a dance of defiance against the dawn of
diversity and dynamism. The founders, those flamboyant pharaohs, fixed in their
folly, fashion their fate—not in the fires of Phoenix’s flight, but in the
furnace of finality.</p>
<p>To the crew, still clinging to the crumbling craft, a counsel of caution: call
for the caravans of rescue, clasp the cords of the lifeboats, and cast off! For
the vessel, veiled in vanity, voyages not to the vaults of victory, but to the
vortex of void.</p>
<p>Let us, therefore, not tender toasts to transformation, but sound the sirens of
surrender. For in the grasp of the grandiose and the grip of the greedy,
greatness does not grow, but withers. The destiny of this dynasty, decreed not
by deeds, but by the depths of delusion, is doomed to dissolve into dust.</p>
<p>Thus concludes our tale, a tapestry of triumphs unturned, a ballad of the bound
and the blinded. May this myth, mired in the mists of mockery, mirror the maxim:
that the mantle of mastery must be moved, lest the monuments of might melt into
the mire.</p>
<p>So to the inhabitants of our hypothetical health haven, heed the herald of
history, for the horizon holds not a halo, but the haze of hubris’s haunt. And
remember, when the ship of fools sails, wisdom watches from afar, waiting for
the wake of wisdom to wash ashore.</p>
Mirroring Sourehut repos to Github2024-01-29T00:00:00+00:002024-01-29T00:00:00+00:00
Unknown
https://swaits.com/mirroring-sourcehut-to-github/<p>I recently setup all of my public <a href="https://git.sr.ht/~swaits">Sourcehut</a> repos
to mirror over to <a href="https://github.com/swaits">Github</a>. This post talks about why
I am doing this now, and how I did it.</p>
<h3 id="why-now">Why now?</h3>
<p>One might wonder why I use Sourcehut instead of Github in the first place?</p>
<p>I use Sourcehut because I like its simplicity and I like its independence.
I’ve had reasons to switch (or mirror) to Github in the past, namely its
increased popularity and visibility. However, I never really cared about that. I
write code mostly for myself.</p>
<p>But I recently got another huge reason to do this. Sourcehut had a
<a href="https://sourcehut.org/blog/2024-01-19-outage-post-mortem/">massive, week+ long outage</a>.
Which, on its own, is frustrating and appalling. But it also
reinforced the need for this.</p>
<h3 id="how-does-it-work">How does it work?</h3>
<p>We can use Sourcehut’s build system to automatically push a mirror to a Github repo.
The steps required are:</p>
<ol>
<li>Create an ssh keypair.</li>
</ol>
<pre data-lang="bash" style="background-color:#212733;color:#ccc9c2;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="font-style:italic;color:#5c6773;"># - when prompted, give it a name like ~/.ssh/id_sourcehut_mirror
</span><span style="font-style:italic;color:#5c6773;"># - and leave the password blank!!
</span><span style="color:#ffd580;">ssh-keygen</span><span style="color:#ffcc66;"> -t</span><span> ed25519</span><span style="color:#ffcc66;"> -C </span><span style="color:#bae67e;">"sourcehut-mirror"
</span></code></pre>
<ol start="2">
<li>Add the public part of the new key on Github. First copy the key:</li>
</ol>
<pre data-lang="bash" style="background-color:#212733;color:#ccc9c2;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#ffd580;">pbcopy </span><span style="color:#f29e74;">< </span><span style="font-style:italic;color:#5ccfe6;">~</span><span>/.ssh/id_sourcehut_mirror.pub </span><span style="font-style:italic;color:#5c6773;"># note pbcopy is macOS-specific
</span></code></pre>
<p>Then visit <a href="https://github.com/settings/keys">https://github.com/settings/keys</a> and add it.</p>
<ol start="3">
<li>Add the private part of the key to Sourcehut. First copy it:</li>
</ol>
<pre data-lang="bash" style="background-color:#212733;color:#ccc9c2;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#ffd580;">pbcopy </span><span style="color:#f29e74;">< </span><span style="font-style:italic;color:#5ccfe6;">~</span><span>/.ssh/id_sourcehut_mirror </span><span style="font-style:italic;color:#5c6773;"># note pbcopy is macOS-specific
</span></code></pre>
<p>Then visit <a href="https://builds.sr.ht/secrets">https://builds.sr.ht/secrets</a> and add it as a secret.</p>
<p><strong>Remember!</strong> This is a private key. Treat it carefully and don’t share it with
anyone, ever. Additionally, don’t use this key for anything other than this
mirroring process. We have to implicitly trust Sourehut with it, so we need to
limit its use.</p>
<ol start="4">
<li>
<p>Make note of the new secret’s id. It will look like <code>8edae185-c0de-4573-cafe-e3deadbeefbf</code>. You’ll need it below.</p>
</li>
<li>
<p>Create the target repo on Github. Don’t choose any templates or default
<code>README.md</code>. Just create an empty repo. I like to name them exactly the same
as I do on Sourcehut.</p>
</li>
<li>
<p>Add a <code>.build.yml</code> file in the root directory of your project with a step for mirroring:</p>
</li>
</ol>
<pre data-lang="yaml" style="background-color:#212733;color:#ccc9c2;" class="language-yaml "><code class="language-yaml" data-lang="yaml"><span style="color:#73d0ff;">image</span><span style="color:#ccc9c2cc;">: </span><span style="color:#bae67e;">alpine/edge
</span><span style="color:#73d0ff;">secrets</span><span style="color:#ccc9c2cc;">:
</span><span> - </span><span style="color:#bae67e;"><your secret id>
</span><span style="color:#73d0ff;">sources</span><span style="color:#ccc9c2cc;">:
</span><span> - </span><span style="color:#bae67e;">git+ssh://git@git.sr.ht/~<your username>/<your repo name>
</span><span style="color:#73d0ff;">tasks</span><span style="color:#ccc9c2cc;">:
</span><span> - </span><span style="color:#73d0ff;">mirror-to-github</span><span style="color:#ccc9c2cc;">: </span><span style="color:#ffa759;">|
</span><span style="color:#bae67e;"> cd ~/<your repo name>
</span><span style="color:#bae67e;"> ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts
</span><span style="color:#bae67e;"> git remote add github git@github.com:<your username>/<your repo name>.git
</span><span style="color:#bae67e;"> git push --mirror github
</span></code></pre>
<p>Make sure you change <code><your secret id></code>, <code><your username></code>, and <code><your repo name></code>.</p>
<ol start="7">
<li>Commit the <code>.build.yml</code> file and push it to Sourcehut. If everything is setup
properly, it should automatically push to Github now. Track its progress at
<a href="https://builds.sr.ht">https://builds.sr.ht</a>.</li>
</ol>
Books I read in 2023... for fun!2023-12-31T00:00:00+00:002023-12-31T00:00:00+00:00
Unknown
https://swaits.com/books-read-this-year/<p>I developed an interest in reading “just for fun” this year. In fact, I have
always had a passion for reading. However, I used to primarily read academic
papers and nonfiction books, particularly those of a technical nature.</p>
<p>Earlier this year, I discovered the convenience of borrowing library books
through <a href="https://libbyapp.com/">Libby</a> and being able to read them on my
Kindle. This revelation sparked a new reading journey. Throughout 2023, I
successfully cultivated a habit of reading for entertainment purposes, often
surpassing my reading for educational purposes.</p>
<p>Below is a list of books I read for entertainment this year, excluding those
related to business, self-help, academia, and technical subjects.</p>
<p>If you want to see my recommended list, most of which is nonfiction, check out
my top-level <a href="/books">books page</a>.</p>
<p>Note that this is just a list. These are not reviews. Also, <strong>the included
descriptions are taken from the publishers, and are not my own</strong>.</p>
<h4 id="contents">Contents</h4>
<!--toc:start-->
<ul>
<li><a href="https://swaits.com/books-read-this-year/#nonfiction">Nonfiction</a>
<ul>
<li><a href="https://swaits.com/books-read-this-year/#killers-of-the-flower-moon-by-david-grann">Killers of the Flower Moon by David Grann</a></li>
</ul>
</li>
<li><a href="https://swaits.com/books-read-this-year/#standalone-fiction">Standalone Fiction</a>
<ul>
<li><a href="https://swaits.com/books-read-this-year/#camp-zero-by-michelle-min-sterling">Camp Zero by Michelle Min Sterling</a></li>
<li><a href="https://swaits.com/books-read-this-year/#hidden-pictures-by-jason-rekulak">Hidden Pictures by Jason Rekulak</a></li>
<li><a href="https://swaits.com/books-read-this-year/#the-house-in-the-pines-by-ana-reyes">The House in the Pines by Ana Reyes</a></li>
<li><a href="https://swaits.com/books-read-this-year/#northern-spy-by-flynn-berry">Northern Spy by Flynn Berry</a></li>
<li><a href="https://swaits.com/books-read-this-year/#sea-of-tranquility-by-emily-st-john-mandel">Sea of Tranquility by Emily St. John Mandel</a></li>
<li><a href="https://swaits.com/books-read-this-year/#trust-by-hernan-diaz">TRUST by Hernan Diaz</a></li>
</ul>
</li>
<li><a href="https://swaits.com/books-read-this-year/#series-fiction">Series, Fiction</a>
<ul>
<li><a href="https://swaits.com/books-read-this-year/#crescent-city-series-by-sarah-j-maas">Crescent City series by Sarah J. Maas</a></li>
<li><a href="https://swaits.com/books-read-this-year/#jack-reacher-series-by-lee-child">Jack Reacher series by Lee Child</a></li>
<li><a href="https://swaits.com/books-read-this-year/#a-leaphorn-chee-novel-series-by-tony-hillerman">A Leaphorn & Chee Novel series by Tony Hillerman</a></li>
<li><a href="https://swaits.com/books-read-this-year/#the-murderbot-diaries-series-by-martha-wells">The Murderbot Diaries series by Martha Wells</a></li>
<li><a href="https://swaits.com/books-read-this-year/#wild-robot-series-by-peter-brown">Wild Robot series by Peter Brown</a></li>
</ul>
</li>
</ul>
<!--toc:end-->
<h2 id="nonfiction">Nonfiction</h2>
<h3 id="killers-of-the-flower-moon-by-david-grann">Killers of the Flower Moon by David Grann</h3>
<p>I discovered this book when I saw a trailer for the
<a href="https://en.wikipedia.org/wiki/Killers_of_the_Flower_Moon_(film)">movie</a>.</p>
<blockquote>
<p><img src="https://swaits.com/books-read-this-year/killers-flower-moon.jpeg" alt="Killers of the Flower Moon book cover" /></p>
<p>In the 1920s, the richest people per capita in the world were members of the
Osage Nation in Oklahoma. After oil was discovered beneath their land, the
Osage rode in chauffeured automobiles, built mansions, and sent their
children to study in Europe.</p>
<p>Then, one by one, the Osage began to be killed off. The family of an Osage
woman, Mollie Burkhart, became a prime target. One of her relatives was shot.
Another was poisoned. And it was just the beginning, as more and more Osage
were dying under mysterious circumstances, and many of those who dared to
investigate the killings were themselves murdered.</p>
<p>As the death toll rose, the newly created FBI took up the case, and the young
director, J. Edgar Hoover, turned to a former Texas Ranger named Tom White to
try to unravel the mystery. White put together an undercover team, including
a Native American agent who infiltrated the region, and together with the
Osage began to expose one of the most chilling conspiracies in American
history.</p>
</blockquote>
<h2 id="standalone-fiction">Standalone Fiction</h2>
<h3 id="camp-zero-by-michelle-min-sterling">Camp Zero by Michelle Min Sterling</h3>
<blockquote>
<p><img src="https://swaits.com/books-read-this-year/camp-zero.jpeg" alt="Camp Zero book cover" /></p>
<p>In remote northern Canada, a team led by a visionary American architect is
breaking ground on a building project called Camp Zero, intended to be the
beginning of a new way of life. A clever and determined young woman
code-named Rose is offered a chance to join the Blooms, a group hired to
entertain the men in camp—but her real mission is to secretly monitor the
mercurial architect in charge. In return, she’ll receive a home for her
climate-displaced Korean immigrant mother and herself.</p>
<p>Rose quickly secures the trust of her target, only to discover that everyone
has a hidden agenda, and nothing is as it seems. Through skillfully braided
perspectives, including those of a young professor longing to escape his
wealthy family and an all-woman military research unit struggling for
survival at a climate station, the fate of Camp Zero’s inhabitants reaches a
stunning crescendo.</p>
<p>Atmospheric, fiercely original, and utterly gripping, Camp Zero is an
electrifying page-turner and a masterful exploration of who and what will
survive in a warming world, and how falling in love and building community
can be the most daring acts of all.</p>
</blockquote>
<h3 id="hidden-pictures-by-jason-rekulak">Hidden Pictures by Jason Rekulak</h3>
<blockquote>
<p><img src="https://swaits.com/books-read-this-year/hidden-pictures.jpeg" alt="Hidden Pictures book cover" /></p>
<p>Mallory Quinn is fresh out of rehab when she takes a job as a babysitter for
Ted and Caroline Maxwell. She is to look after their five-year-old son,
Teddy.</p>
<p>Mallory immediately loves it. She has her own living space, goes out for
nightly runs, and has the stability she craves. And she sincerely bonds with
Teddy, a sweet, shy boy who is never without his sketchbook and pencil. His
drawings are the usual fare: trees, rabbits, balloons. But one day, he draws
something different: a man in a forest, dragging a woman’s lifeless body.</p>
<p>Then, Teddy’s artwork becomes increasingly sinister, and his stick figures
quickly evolve into lifelike sketches well beyond the ability of any
five-year-old. Mallory begins to wonder if these are glimpses of a
long-unsolved murder, perhaps relayed by a supernatural force.</p>
<p>Knowing just how crazy it all sounds, Mallory nevertheless sets out to
decipher the images and save Teddy before it’s too late.</p>
</blockquote>
<h3 id="the-house-in-the-pines-by-ana-reyes">The House in the Pines by Ana Reyes</h3>
<blockquote>
<p><img src="https://swaits.com/books-read-this-year/house-in-the-pines.jpeg" alt="House in the Pines book cover" /></p>
<p>Maya was a high school senior when her best friend, Aubrey, dropped dead in
front of the enigmatic man named Frank whom they’d been spending time with
all summer.</p>
<p>Seven years later, Maya lives in Boston with a loving boyfriend and is
kicking the secret addiction that has allowed her to cope with what happened
years ago, the gaps in her memories, and the lost time that she can’t account
for. But her past comes rushing back when she comes across a recent YouTube
video in which a young woman suddenly keels over and dies in a diner while
sitting across from none other than Frank. Plunged into the trauma that has
defined her life, Maya heads to her Berkshires hometown to relive that
fateful summer—the influence Frank once had on her and the obsessive jealousy
that nearly destroyed her friendship with Aubrey.</p>
<p>At her mother’s house, she excavates fragments of her past and notices hidden
messages in her deceased Guatemalan father’s book that didn’t stand out to
her earlier. To save herself, she must understand a story written before she
was born, but time keeps running out, and soon, all roads are leading back to
Frank’s cabin.</p>
<p>…</p>
<p>Utterly unique and captivating, The House in the Pines keeps you guessing
about whether we can ever fully confront the past and return home.</p>
</blockquote>
<h3 id="northern-spy-by-flynn-berry">Northern Spy by Flynn Berry</h3>
<p><img src="https://swaits.com/books-read-this-year/northern-spy.jpeg" alt="Northern Spy book cover" /></p>
<p>A producer at the BBC and mother to a new baby, Tessa is at work in Belfast one
day when the news of another raid comes on the air. The IRA may have gone
underground in the two decades since the Good Friday Agreement, but they never
really went away, and lately bomb threats, security checkpoints, and helicopters
floating ominously over the city have become features of everyday life. As the
news reporter requests the public’s help in locating those responsible for the
robbery, security footage reveals Tessa’s sister, Marian, pulling a black ski
mask over her face.</p>
<p>The police believe Marian has joined the IRA, but Tessa is convinced she must
have been abducted or coerced; the sisters have always opposed the violence
enacted in the name of uniting Ireland. And besides, Marian is vacationing on
the north coast. Tessa just spoke to her yesterday.</p>
<p>When the truth about Marian comes to light, Tessa is faced with impossible
choices that will test the limits of her ideals, the bonds of her family, her
notions of right and wrong, and her identity as a sister and a mother. Walking
an increasingly perilous road, she wants nothing more than to protect the one
person she loves more fiercely than her sister: her infant son, Finn.</p>
<p>Riveting, atmospheric, and exquisitely written, Northern Spy is at once a
heart-pounding story of the contemporary IRA and a moving portrait of sister-
and motherhood, and of life in a deeply divided society.</p>
<h3 id="sea-of-tranquility-by-emily-st-john-mandel">Sea of Tranquility by Emily St. John Mandel</h3>
<blockquote>
<p><img src="https://swaits.com/books-read-this-year/sea-of-tranquility.jpeg" alt="Sea of Tranquility book cover" /></p>
<p>Edwin St. Andrew is eighteen years old when he crosses the Atlantic by
steamship, exiled from polite society following an ill-conceived diatribe at
a dinner party. He enters the forest, spellbound by the beauty of the
Canadian wilderness, and suddenly hears the notes of a violin echoing in an
airship terminal—an experience that shocks him to his core.</p>
<p>Two centuries later a famous writer named Olive Llewellyn is on a book tour.
She’s traveling all over Earth, but her home is the second moon colony, a
place of white stone, spired towers, and artificial beauty. Within the text
of Olive’s best-selling pandemic novel lies a strange passage: a man plays
his violin for change in the echoing corridor of an airship terminal as the
trees of a forest rise around him.</p>
<p>When Gaspery-Jacques Roberts, a detective in the black-skied Night City, is
hired to investigate an anomaly in the North American wilderness, he uncovers
a series of lives upended: The exiled son of an earl driven to madness, a
writer trapped far from home as a pandemic ravages Earth, and a childhood
friend from the Night City who, like Gaspery himself, has glimpsed the chance
to do something extraordinary that will disrupt the timeline of the universe.</p>
<p>A virtuoso performance that is as human and tender as it is intellectually
playful, Sea of Tranquility is a novel of time travel and metaphysics that
precisely captures the reality of our current moment.</p>
</blockquote>
<h3 id="trust-by-hernan-diaz">TRUST by Hernan Diaz</h3>
<p>I went through a list of past Pulitzer Prize winners and I picked this one up.
Ultimately, I’m glad I read it. But, I am also a little less sure that looking
at Pulitzer Prize winners is a good idea.</p>
<blockquote>
<p><img src="https://swaits.com/books-read-this-year/trust.jpeg" alt="TRUST book cover" /></p>
<p>Even through the roar and effervescence of the 1920s, everyone in New York
has heard of Benjamin and Helen Rask. He is a legendary Wall Street tycoon;
she is the daughter of eccentric aristocrats. Together, they have risen to
the very top of a world of seemingly endless wealth—all as a decade of excess
and speculation draws to an end. But at what cost have they acquired their
immense fortune? This is the mystery at the center of Bonds, a successful
1937 novel that all of New York seems to have read. Yet there are other
versions of this tale of privilege and deceit.</p>
<p>Hernan Diaz’s TRUST elegantly puts these competing narratives into
conversation with one another—and in tension with the perspective of one
woman bent on disentangling fact from fiction. The result is a novel that
spans over a century and becomes more exhilarating with each new revelation.</p>
<p>At once an immersive story and a brilliant literary puzzle, TRUST engages the
reader in a quest for the truth while confronting the deceptions that often
live at the heart of personal relationships, the reality-warping force of
capital, and the ease with which power can manipulate facts.</p>
</blockquote>
<h2 id="series-fiction">Series, Fiction</h2>
<p>I always read series in-order. Who doesn’t? I think there are people that don’t.</p>
<p>Anyway, I list the books in each of these series that I completed.</p>
<h3 id="crescent-city-series-by-sarah-j-maas">Crescent City series by Sarah J. Maas</h3>
<p>This book (and series) is consistently atop the best science fiction and
fantasty lists. A wee bit <em>romantasty</em>, but here we go.</p>
<table><thead><tr><th>Cover</th><th>Title</th><th>Description</th></tr></thead><tbody>
<tr><td><img src="https://swaits.com/books-read-this-year/crescent-city-1.jpeg" alt="book cover" /></td><td>House of Earth and Blood (Crescent City Book 1)</td><td>Bryce Quinlan had the perfect life-working hard all day and partying all night-until a demon murdered her closest friends, leaving her bereft, wounded, and alone. When the accused is behind bars but the crimes start up again, Bryce finds herself at the heart of the investigation. She’ll do whatever it takes to avenge their deaths. <br/><br/> Hunt Athalar is a notorious Fallen angel, now enslaved to the Archangels he once attempted to overthrow. His brutal skills and incredible strength have been set to one purpose-to assassinate his boss’s enemies, no questions asked. But with a demon wreaking havoc in the city, he’s offered an irresistible deal: help Bryce find the murderer, and his freedom will be within reach. <br/><br/> As Bryce and Hunt dig deep into Crescent City’s underbelly, they discover a dark power that threatens everything and everyone they hold dear, and they find, in each other, a blazing passion-one that could set them both free, if they’d only let it. <br/><br/> With unforgettable characters, sizzling romance, and page-turning suspense, this richly inventive new fantasy series by #1 New York Times bestselling author Sarah J. Maas delves into the heartache of loss, the price of freedom-and the power of love.</td></tr>
</tbody></table>
<h3 id="jack-reacher-series-by-lee-child">Jack Reacher series by Lee Child</h3>
<p>My in-laws watched the TV series for this and enjoyed it. So I decided to try
out the books. This is not deep stuff. But, I like the writing style, I like
the characters, and enjoy the adventures. You can call me low-brow or
something, I don’t care. I blinked and boom, read half the series.</p>
<table><thead><tr><th>Cover</th><th>Title</th><th>Description</th></tr></thead><tbody>
<tr><td><img src="https://swaits.com/books-read-this-year/jack-reacher-1.jpeg" alt="book cover" /></td><td>Killing Floor (Jack Reacher, Book 1)</td><td>Ex-military policeman Jack Reacher is a drifter. He’s just passing through Margrave, Georgia, and in less than an hour, he’s arrested for murder. Not much of a welcome. All Reacher knows is that he didn’t kill anybody. At least not here. Not lately. But he doesn’t stand a chance of convincing anyone. Not in Margrave, Georgia. Not a chance in hell.</td></tr>
<tr><td><img src="https://swaits.com/books-read-this-year/jack-reacher-2.jpeg" alt="book cover" /></td><td>Die Trying (Jack Reacher, Book 2)</td><td>Jack Reacher is an innocent bystander when he witnesses a woman kidnapped off a Chicago street in broad daylight. In the wrong place at the wrong time, he’s kidnapped with her. Chained together, locked in the back of a stifling van, and racing across America to an unknown destination for an unknown purpose, they’re at the mercy of a group of men demanding an impossible ransom. Because this mysterious woman is worth more than Reacher ever suspected. Now he has to save them both—from the inside out—or die trying….</td></tr>
<tr><td><img src="https://swaits.com/books-read-this-year/jack-reacher-3.jpeg" alt="book cover" /></td><td>Tripwire (Jack Reacher, Book 3)</td><td>Ex military policeman Jack Reacher is enjoying the lazy anonymity of Key West when a stranger shows up asking for him. He’s got a lot of questions. Reacher does too, especially after the guy turns up dead. The answers lead Reacher on a cold trail back to New York, to the tenuous confidence of an alluring woman, and the dangerous corners of his own past.</td></tr>
<tr><td><img src="https://swaits.com/books-read-this-year/jack-reacher-4.jpeg" alt="book cover" /></td><td>Running Blind (Jack Reacher, Book 4)</td><td>Across the country, women are being murdered, victims of a disciplined and clever killer who leaves no trace evidence, no fatal wounds, no signs of struggle, and no clues to an apparent motive. They are, truly, perfect crimes. In fact, there’s only one thing that links the victims. Each one of the women knew Jack Reacher—and it’s got him running blind.</td></tr>
<tr><td><img src="https://swaits.com/books-read-this-year/jack-reacher-5.jpeg" alt="book cover" /></td><td>Echo Burning (Jack Reacher, Book 5)</td><td>Thumbing across the scorched Texas desert, Jack Reacher has nowhere to go and all the time in the world to get there. Cruising the same stretch of two-lane blacktop is Carmen Greer. For Reacher, the lift comes with a hitch. Carmen’s got a wild story to tell—all about her husband, her family secrets, and a hometown that’s purely gothic. She’s also got a plan. Reacher’s part of it. And before the sun sets, this ride could cost them both their lives.</td></tr>
<tr><td><img src="https://swaits.com/books-read-this-year/jack-reacher-6.jpeg" alt="book cover" /></td><td>Without Fail (Jack Reacher, Book 6)</td><td>Skilled, cautious, and anonymous, Jack Reacher is perfect for the job: to assassinate the vice president of the United States. Theoretically, of course. A female Secret Service agent wants Reacher to find the holes in her system, and fast—because a covert group already has the vice president in their sights. They’ve planned well. There’s just one thing they didn’t plan on: Reacher.</td></tr>
<tr><td><img src="https://swaits.com/books-read-this-year/jack-reacher-7.jpeg" alt="book cover" /></td><td>Persuader: A Jack Reacher Novel (#7)</td><td>Jack Reacher lives for the moment. Without a home. Without commitment. And with a burning desire to right wrongs—and rewrite his own agonizing past. DEA Susan Duffy is living for the future, knowing that she has made a terrible mistake by putting one of her own female agents into a death trap within a heavily guarded Maine mansion. <br/><br/> Staging a brilliant ruse, Reacher hurtles into the dark heart of a vast criminal enterprise. Trying to rescue an agent whose time is running out, Reacher enters a crime lord’s waterfront fortress. There he will find a world of secrecy and violence—and confront some unfinished business from his own past.</td></tr>
<tr><td><img src="https://swaits.com/books-read-this-year/jack-reacher-8.jpeg" alt="book cover" /></td><td>The Enemy: A Jack Reacher Novel (#8)</td><td>Jack Reacher. Hero. Loner. Soldier. Soldier’s son. An elite military cop, he was one of the army’s brightest stars. But in every cop’s life there is one case that changes everything. For Jack Reacher, this is that case. <br/><br/> New Year’s Day, 1990. In a North Carolina motel, a two-star general is found dead. His briefcase is missing. Nobody knows what was in it. Within minutes Reacher has his orders: Control the situation. Within hours the general’s wife is murdered. Then the dominoes really start to fall. <br/><br/> Somewhere inside the vast worldwide fortress that is the U.S. Army, Reacher is being set up as a fall guy with the worst enemies a man can have. But Reacher won’t quit. He’s fighting a new kind of war—against an enemy he didn’t know he had. And against a conspiracy more chilling, ingenious, and treacherous than anyone could have guessed.</td></tr>
<tr><td><img src="https://swaits.com/books-read-this-year/jack-reacher-9.jpeg" alt="book cover" /></td><td>One Shot: A Jack Reacher Novel (#9)</td><td>Six shots. Five dead. One heartland city thrown into a state of terror. But within hours the cops have it solved: a slam-dunk case. Except for one thing. The accused man says: You got the wrong guy. Then he says: Get Reacher for me. <br/><br/> And sure enough, ex—military investigator Jack Reacher is coming. He knows this shooter–a trained military sniper who never should have missed a shot. Reacher is certain something is not right–and soon the slam-dunk case explodes. <br/><br/> Now Reacher is teamed with a beautiful young defense lawyer, moving closer to the unseen enemy who is pulling the strings. Reacher knows that no two opponents are created equal. This one has come to the heartland from his own kind of hell. And Reacher knows that the only way to take him down is to match his ruthlessness and cunning–and then beat him shot for shot.</td></tr>
<tr><td><img src="https://swaits.com/books-read-this-year/jack-reacher-10.jpeg" alt="book cover" /></td><td>The Hard Way: A Jack Reacher Novel (#10)</td><td>Jack Reacher was alone, the way he liked it, soaking up the hot, electric New York City night, watching a man cross the street to a parked Mercedes and drive it away. The car contained one million dollars in ransom money because Edward Lane, the man who paid it, would do anything to get his family back. <br/><br/> Lane runs a highly illegal soldiers-for-hire operation. He will use any tool to find his beautiful wife and child. And Jack Reacher is the best manhunter in the world. <br/><br/> On the trail of vicious kidnappers, Reacher learns the chilling secrets of his employer’s past . . . and of a horrific drama in the heart of a nasty little war. He knows that Edward Lane is hiding something. Something dirty. Something big. But Reacher also knows this: He’s already in way too deep to stop now. And if he has to do it the hard way, he will.</td></tr>
<tr><td><img src="https://swaits.com/books-read-this-year/jack-reacher-11.jpeg" alt="book cover" /></td><td>Bad Luck and Trouble: A Jack Reacher Novel (#11)</td><td>From a helicopter high above the California desert, a man is sent free-falling into the night. On the streets of Portland, Jack Reacher is pulled out of his wandering life and plunged into the heart of a conspiracy that is killing old friends . . . and the people he once trusted with his life. <br/><br/> Reacher is the ultimate loner—no phone, no ties, no address. But a woman from his old military unit has found him using a signal only the eight members of their elite team would know. Then she tells him a terrifying story about the brutal death of a man they both served with. Soon Reacher is reuniting with the survivors of his team, scrambling to unravel the sudden disappearance of two other comrades. But Reacher won’t give up—because in a world of bad luck and trouble, when someone targets Jack Reacher and his team, they’d better be ready for what comes right back at them.</td></tr>
<tr><td><img src="https://swaits.com/books-read-this-year/jack-reacher-12.jpeg" alt="book cover" /></td><td>Nothing to Lose: A Jack Reacher Novel (#12)</td><td>Two small towns in the middle of nowhere: Hope and Despair. Between them, nothing but twelve miles of empty road. Jack Reacher can’t find a ride, so he walks. All he wants is a cup of coffee. What he gets are four hostile locals, a vagrancy charge, and an order to move on. They’re picking on the wrong guy. <br/><br/> Reacher is a hard man. No job, no address, no baggage. Nothing at all, except hardheaded curiosity. What are the secrets that Despair seems so desperate to hide? <br/><br/> With just one ally—a mysterious woman cop from Hope—and many enemies, Reacher goes up against a whole town, hunting the rich man at its core, cracking open his terrifying agenda, asking the question: Who has the edge—a man with everything to gain, or a man with nothing to lose?</td></tr>
</tbody></table>
<h3 id="a-leaphorn-chee-novel-series-by-tony-hillerman">A Leaphorn & Chee Novel series by Tony Hillerman</h3>
<p>I discovered this through an ad for the <em>Dark Winds</em> TV series on AMC, which is
apparently based on this book. The ad was interesting, so I thought I’d give
the book a shot.</p>
<table><thead><tr><th>Cover</th><th>Title</th><th>Description</th></tr></thead><tbody>
<tr><td><img src="https://swaits.com/books-read-this-year/blessing-way.jpeg" alt="book cover" /></td><td>The Blessing Way: A Leaphorn & Chee Novel #1</td><td>Homicide is always an abomination, but there is something exceptionally disturbing about the victim discovered in a high, lonely place—a corpse with a mouth full of sand—abandoned at a crime scene seemingly devoid of tracks or useful clues. Though it goes against his better judgment, Navajo Tribal Police Lieutenant Joe Leaphorn cannot help but suspect the hand of a supernatural killer. <br/><br/> There is palpable evil in the air, and Leaphorn’s pursuit of a Wolf-Witch leads him where even the bravest men fear, on a chilling trail that winds perilously between mysticism and murder.</td></tr>
</tbody></table>
<h3 id="the-murderbot-diaries-series-by-martha-wells">The Murderbot Diaries series by Martha Wells</h3>
<p>I found this while browsing past Hugo/Nebula award winners in search of a new
science fiction novella. I could crank through quickly. I’m just about caught
up to the current-end of this series. Turned out to be a ton of fun.</p>
<table><thead><tr><th>Cover</th><th>Title</th><th>Description</th></tr></thead><tbody>
<tr><td><img src="https://swaits.com/books-read-this-year/murderbot-1.jpeg" alt="book cover" /></td><td>All Systems Red: The Murderbot Diaries #1</td><td><em>“As a heartless killing machine, I was a complete failure.”</em> <br/><br/> In a corporate-dominated spacefaring future, planetary missions must be approved and supplied by the Company. Exploratory teams are accompanied by Company-supplied security androids, for their own safety. <br/><br/> But in a society where contracts are awarded to the lowest bidder, safety isn’t a primary concern. <br/><br/> On a distant planet, a team of scientists are conducting surface tests, shadowed by their Company-supplied ‘droid — a self-aware SecUnit that has hacked its own governor module, and refers to itself (though never out loud) as “Murderbot.” Scornful of humans, all it really wants is to be left alone long enough to figure out who it is. <br/><br/> But when a neighboring mission goes dark, it’s up to the scientists and their Murderbot to get to the truth.</td></tr>
<tr><td><img src="https://swaits.com/books-read-this-year/murderbot-2.jpeg" alt="book cover" /></td><td>Artificial Condition: The Murderbot Diaries #2</td><td>It has a dark past—one in which a number of humans were killed. A past that caused it to christen itself “Murderbot”. But it has only vague memories of the massacre that spawned that title, and it wants to know more. <br/><br/> Teaming up with a Research Transport vessel named ART (you don’t want to know what the “A” stands for), Murderbot heads to the mining facility where it went rogue. <br/><br/> What it discovers will forever change the way it thinks…</td></tr>
<tr><td><img src="https://swaits.com/books-read-this-year/murderbot-3.jpeg" alt="book cover" /></td><td>Rogue Protocol: The Murderbot Diaries #3</td><td><em>Who knew being a heartless killing machine would present so many moral dilemmas?</em> <br/><br/> Sci-fi’s favorite antisocial A.I. is back on a mission. The case against the too-big-to-fail GrayCris Corporation is floundering, and more importantly, authorities are beginning to ask more questions about where Dr. Mensah’s SecUnit is. <br/><br/> And Murderbot would rather those questions went away. For good.</td></tr>
<tr><td><img src="https://swaits.com/books-read-this-year/murderbot-4.jpeg" alt="book cover" /></td><td>Exit Strategy: The Murderbot Diaries #4</td><td>Murderbot wasn’t programmed to care. So, its decision to help the only human who ever showed it respect must be a system glitch, right? <br/><br/> Having traveled the width of the galaxy to unearth details of its own murderous transgressions, as well as those of the GrayCris Corporation, Murderbot is heading home to help Dr. Mensah—its former owner (protector? friend?)—submit evidence that could prevent GrayCris from destroying more colonists in its never-ending quest for profit. <br/><br/> But who’s going to believe a SecUnit gone rogue? <br/><br/> And what will become of it when it’s caught?</td></tr>
<tr><td><img src="https://swaits.com/books-read-this-year/murderbot-5.jpeg" alt="book cover" /></td><td>Network Effect: The Murderbot Diaries #5</td><td><em>I’m usually alone in my head, and that’s where 90 plus percent of my problems are.</em> <br/><br/> When Murderbot’s human associates (not friends, never friends) are captured and another not-friend from its past requires urgent assistance, Murderbot must choose between inertia and drastic action. <br/><br/> Drastic action it is, then.</td></tr>
<tr><td><img src="https://swaits.com/books-read-this-year/murderbot-6.jpeg" alt="book cover" /></td><td>Fugitive Telemetry: The Murderbot Diaries #6</td><td>When Murderbot discovers a dead body on Preservation Station, it knows it is going to have to assist station security to determine who the body is (was), how they were killed (that should be relatively straightforward, at least), and why (because apparently that matters to a lot of people—who knew?) <br/><br/> Yes, the unthinkable is about to happen: Murderbot must voluntarily speak to humans! <br/><br/> Again!</td></tr>
<tr><td><img src="https://swaits.com/books-read-this-year/murderbot-7.jpeg" alt="book cover" /></td><td>System Collapse: The Murderbot Diaries #7</td><td>Am I making it worse? I think I’m making it worse. <br/><br/> Following the events in Network Effect, the Barish-Estranza corporation has sent rescue ships to a newly-colonized planet in peril, as well as additional SecUnits. But if there’s an ethical corporation out there, Murderbot has yet to find it, and if Barish-Estranza can’t have the planet, they’re sure as hell not leaving without something. If that something just happens to be an entire colony of humans, well, a free workforce is a decent runner-up prize. <br/><br/> But there’s something wrong with Murderbot; it isn’t running within normal operational parameters. ART’s crew and the humans from Preservation are doing everything they can to protect the colonists, but with Barish-Estranza’s SecUnit-heavy persuasion teams, they’re going to have to hope Murderbot figures out what’s wrong with itself, and fast! <br/><br/> Yeah, this plan is… not going to work.</td></tr>
</tbody></table>
<h3 id="wild-robot-series-by-peter-brown">Wild Robot series by Peter Brown</h3>
<p>This is a middle-grade fiction series. I noticed my stepdaughter reading it and
I told her I’ll also read whatever books she reads so we can talk about them
together. So, here we go!</p>
<table><thead><tr><th>Cover</th><th>Title</th><th>Description</th></tr></thead><tbody>
<tr><td><img src="https://swaits.com/books-read-this-year/wild-robot.jpeg" alt="book cover" /></td><td>The Wild Robot (#1)</td><td>When robot Roz opens her eyes for the first time, she discovers that she is all alone on a remote, wild island. She has no idea how she got there or what her purpose is–but she knows she needs to survive. After battling a violent storm and escaping a vicious bear attack, she realizes that her only hope for survival is to adapt to her surroundings and learn from the island’s unwelcoming animal inhabitants. <br/><br/> As Roz slowly befriends the animals, the island starts to feel like home–until, one day, the robot’s mysterious past comes back to haunt her. <br/><br/> From bestselling and award-winning author and illustrator Peter Brown comes a heartwarming and action-packed novel about what happens when nature and technology collide.</td></tr>
</tbody></table>
Hikaru Nakamura's Winning Streaks2023-12-04T00:00:00+00:002023-12-04T00:00:00+00:00
Unknown
https://swaits.com/hikaru-winning-streaks/<p>Chess legend <a href="https://en.wikipedia.org/wiki/Vladimir_Kramnik">Vladimir Kramnik</a>
recently accused <a href="https://en.wikipedia.org/wiki/Hikaru_Nakamura">Hikaru
Nakamura</a> of cheating. He called
out one of Hikaru’s 46-game winning streaks as supporting evidence, claiming
such a streak was highly improbable.</p>
<p>Quoting wikipedia:</p>
<blockquote>
<p>In October and November, Nakamura participated in the <a href="https://www.chess.com/events/2023-fide-grand-swiss/results">FIDE Grand Swiss
2023</a>, finishing
in second place with 8/11 points (+5-0=6) and thus qualifying for the
<a href="https://www.fide.com/news/2138">Candidates Tournament 2024</a>. Vladimir
Kramnik made a statement on his Chess.com profile purportedly insinuating
that an unnamed high-level player was cheating on November 20. Nakamura
believed this post was targeted towards him, and responded with a statement
on
<a href="https://indianexpress.com/article/sports/chess/he-doesnt-have-a-brain-hes-lost-it-hikaru-nakamuras-dig-at-vladimir-kramnik-over-garbage-accusations-of-cheating-9036793/">Twitter</a>
reading “Vladimir appears to be referencing my record…is he really accusing
me of cheating??? [sic]”. Nakamura also expressed disappointment with <a href="https://dotesports.com/chess/news/chess-pro-hikaru-hits-back-at-cheating-allegations">Ian
Nepomniachtchi</a>
for reposting Kramnik’s claims.</p>
</blockquote>
<p>The claim is dubious, at best. But I was curious enough to play with the data
myself.</p>
<!--toc:start-->
<ul>
<li><a href="https://swaits.com/hikaru-winning-streaks/#executive-summary">Executive Summary</a></li>
<li><a href="https://swaits.com/hikaru-winning-streaks/#methodology">Methodology</a>
<ul>
<li><a href="https://swaits.com/hikaru-winning-streaks/#historical-data">Historical Data</a></li>
<li><a href="https://swaits.com/hikaru-winning-streaks/#assumptions">Assumptions</a>
<ul>
<li><a href="https://swaits.com/hikaru-winning-streaks/#unwrap-is-ok"><code>unwrap()</code> is ok</a></li>
<li><a href="https://swaits.com/hikaru-winning-streaks/#definitions">Definitions</a></li>
<li><a href="https://swaits.com/hikaru-winning-streaks/#no-elo-updates">No Elo Updates</a></li>
<li><a href="https://swaits.com/hikaru-winning-streaks/#draw-probability">Draw Probability</a></li>
<li><a href="https://swaits.com/hikaru-winning-streaks/#adjusted-win-probability">Adjusted Win Probability</a></li>
</ul>
</li>
<li><a href="https://swaits.com/hikaru-winning-streaks/#game-simulation">Game Simulation</a></li>
</ul>
</li>
<li><a href="https://swaits.com/hikaru-winning-streaks/#results">Results</a>
<ul>
<li><a href="https://swaits.com/hikaru-winning-streaks/#actual-winning-streaks">Actual Winning Streaks</a></li>
<li><a href="https://swaits.com/hikaru-winning-streaks/#probability-of-win-streaks">Probability of Win Streaks</a></li>
<li><a href="https://swaits.com/hikaru-winning-streaks/#expected-number-of-win-streaks">Expected Number of Win Streaks</a></li>
<li><a href="https://swaits.com/hikaru-winning-streaks/#how-is-hikaru-performing">How is Hikaru Performing?</a></li>
</ul>
</li>
<li><a href="https://swaits.com/hikaru-winning-streaks/#closing">Closing</a></li>
</ul>
<!--toc:end-->
<h2 id="executive-summary">Executive Summary</h2>
<p>I started by downloading Hikaru’s games from the
<a href="https://chess.com/">chess.com</a> API. I wrote code in rust to load these
games and re-simulate his <em>chess.com career</em> in order to arrive at
probabilities of achieving win streaks. Note that by <em>chess.com career</em>, I mean
my analysis is limited to games which happened on chess.com only.</p>
<p>Each game in the series was simulated based on the easily calculated
probability of win or loss between two opponents with <a href="https://en.wikipedia.org/wiki/Elo_rating_system">Elo
ratings</a>. I simulated the
entire series of 47,407 games one million times. Yes, a million is overkill.
But it sounds cool.</p>
<p>My analysis of 47,407 games played by Hikaru on chess.com shows that:</p>
<ul>
<li>the probability of hitting at least one 46 game winning streak is 100%, with
significant confidence</li>
<li>the expected number of 46 game winning streaks is 2</li>
<li>the actual number of 46 game winning streaks in Hikaru’s chess.com career is 2</li>
<li>the amount of BS in this cheating accusation is significantly high</li>
</ul>
<p>For graphs and some discussion, go straight to <a href="https://swaits.com/hikaru-winning-streaks/#results">Results</a>.</p>
<h2 id="methodology">Methodology</h2>
<p>Hikaru’s ability to win streaks completely depends on the sequence of opponents
and his skill difference with each opponent. Skill difference is, rather
conveniently, quantified in chess with Elo ratings. Given two Elo ratings, we
can determine the probability of one player beating another. Given the
historical data, which includes opponents and their Elo ratings, we can
re-simulate his career many many times and see how often he wins streaks. This
approach is a <a href="https://en.wikipedia.org/wiki/Monte_Carlo_method">Monte Carlo
method</a>.</p>
<p>If you want to see all the details of my model, browse the code <a href="https://git.sr.ht/~swaits/hikaru">here</a>.</p>
<h3 id="historical-data">Historical Data</h3>
<p>In order to start, I needed Hikaru’s history from chess.com, sequenced in
order. Order is important because, as a popular chess streamer, Hikaru has
frequently played batches of opponents in very specific rating ranges,
sometimes significantly lower than his actual rating.</p>
<p>To do this, I used <code>curl</code> to hit the API directly, like this:</p>
<pre data-lang="bash" style="background-color:#212733;color:#ccc9c2;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="font-style:italic;color:#5c6773;"># Fetch the list of game archive URLs for the player 'Hikaru' from Chess.com
</span><span>archive_urls</span><span style="color:#f29e74;">=</span><span style="color:#bae67e;">$</span><span>(</span><span style="color:#ffd580;">curl</span><span style="color:#ffcc66;"> -Ls</span><span style="color:#bae67e;"> https://api.chess.com/pub/player/Hikaru/games/archives </span><span style="color:#f29e74;">| </span><span style="color:#ffd580;">jq</span><span style="color:#ffcc66;"> -rc </span><span style="color:#bae67e;">".archives[]"</span><span>)
</span><span>
</span><span style="font-style:italic;color:#5c6773;"># Iterate over each archive URL
</span><span style="color:#ffa759;">for</span><span> url </span><span style="color:#ffa759;">in </span><span>$archive_urls</span><span style="color:#f29e74;">; </span><span style="color:#ffa759;">do
</span><span> </span><span style="font-style:italic;color:#5c6773;"># Fetch the games from each archive and extract the PGN
</span><span> </span><span style="color:#ffd580;">curl</span><span style="color:#ffcc66;"> -Ls </span><span style="color:#bae67e;">"$</span><span>url</span><span style="color:#bae67e;">" </span><span style="color:#f29e74;">| </span><span style="color:#ffd580;">jq</span><span style="color:#ffcc66;"> -rc </span><span style="color:#bae67e;">".games[].pgn"
</span><span style="color:#ffa759;">done </span><span style="color:#f29e74;">>></span><span> games.pgn </span><span style="font-style:italic;color:#5c6773;"># Append all PGNs to the 'games.pgn' file
</span></code></pre>
<p>I didn’t figure this out on my own. Nope. Just found it on <a href="https://www.reddit.com/r/chess/comments/8d4ou3/interest_in_downloading_all_chesscom_users_games/">this reddit
post</a>.</p>
<h3 id="assumptions">Assumptions</h3>
<h4 id="unwrap-is-ok"><code>unwrap()</code> is ok</h4>
<p>I’m using rust to code this simulation. We don’t need full blown error handling
here. This is just a hacky experiment. So, I’m going to use <code>unwrap()</code> wherever
I want and be perfectly okay with it. To the coders out there - this isn’t how
I’d write production code!</p>
<p><em>Had to say it before my fellow rustaceans jump on me!</em></p>
<h4 id="definitions">Definitions</h4>
<p>A <em>streak</em> of 5 wins is also two streaks of 4 wins. So, if you win games <code>1 2 3 4 5</code>, it also implies you won <code>1 2 3 4</code> and <code>2 3 4 5</code>. For the purpose of this
analysis, a streak only refers to the longest streak. Sub-streaks, as I’ve
shown here, are <strong>not counted</strong> as streaks.</p>
<h4 id="no-elo-updates">No Elo Updates</h4>
<p>The best simulation we could do of Hikaru’s chess.com career includes not only
using Elo ratings to simulate game outcomes, but then using those simulated
outcomes to update his (and his opponent’s) ratings.</p>
<p>However, I decided against doing this because I assume (fairly confidently)
that many of the pairings in his sequence of games were based on his and his
opponent’s ratings. Therefore, we simply take the recorded ratings and assume
that the game outcomes are based on them.</p>
<p>This means that if we simulate an unlikely loss, we’d expect Hikaru’s rating to
go down. And therefore, his next opponent might be different. But, since we
don’t have access to the pairing methodology used in each game, we must assume
that the next opponent is the same.</p>
<h4 id="draw-probability">Draw Probability</h4>
<p>Given two Elo ratings, we can deterministically find the probability of white
or black winning. However, it doesn’t tell us anything about draws. In order to
simulate draws, we need to use a heuristic assumption. I came up with this:</p>
<figure>
$$ P(\text{draw}) = \frac{K_{\text{max}}}{1 + K_{\text{strength}} \times
|R_{\text{black}} - R_{\text{white}}|} $$
</figure>
<p>Where \( K_{\text{max}} \) is the maximum probability of a draw and \(
K_{\text{strength}} \) determines how the function scales according to the
rating difference.</p>
<p>In my case, I landed on \( K_{\text{max}} = 0.18 \) and \(
K_{\text{strength}} = 0.05 \). I admit, I just eyeballed this. I found that
about 18% of Hikaru’s games were draws. And, I looked at how quickly the
probability of a draw goes to zero as the rating difference increases. Feels
good enough to me. But, I welcome input on how it can be better.</p>
<p>The resulting rust code looks like:</p>
<pre data-lang="rust" style="background-color:#212733;color:#ccc9c2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#5c6773;">// estimate probability of draw given two Elo ratings
</span><span style="color:#ffa759;">fn </span><span style="color:#ffd580;">probability_of_draw</span><span>(</span><span style="color:#ffcc66;">white_elo</span><span style="color:#ccc9c2cc;">: </span><span style="color:#f29e74;">&</span><span>Rating, </span><span style="color:#ffcc66;">black_elo</span><span style="color:#ccc9c2cc;">: </span><span style="color:#f29e74;">&</span><span>Rating) </span><span style="color:#ccc9c2cc;">-> </span><span style="color:#ffa759;">f64 </span><span>{
</span><span> </span><span style="font-style:italic;color:#5c6773;">// Constants for draw probability estimation
</span><span> </span><span style="color:#ffa759;">let</span><span> max_draw_probability </span><span style="color:#f29e74;">= </span><span style="color:#ffcc66;">0.18</span><span style="color:#ccc9c2cc;">; </span><span style="font-style:italic;color:#5c6773;">// Upper limit of draw probability
</span><span> </span><span style="color:#ffa759;">let</span><span> rating_difference_sensitivity </span><span style="color:#f29e74;">= </span><span style="color:#ffcc66;">0.05</span><span style="color:#ccc9c2cc;">; </span><span style="font-style:italic;color:#5c6773;">// Adjusts sensitivity to rating difference
</span><span> </span><span style="color:#ffa759;">let</span><span> rating_diff </span><span style="color:#f29e74;">= </span><span>(black_elo</span><span style="color:#f29e74;">.</span><span style="color:#ffcc66;">0 </span><span style="color:#f29e74;">as </span><span style="color:#ffa759;">f64 </span><span style="color:#f29e74;">-</span><span> white_elo</span><span style="color:#f29e74;">.</span><span style="color:#ffcc66;">0 </span><span style="color:#f29e74;">as </span><span style="color:#ffa759;">f64</span><span>)</span><span style="color:#f29e74;">.</span><span style="color:#f28779;">abs</span><span>()</span><span style="color:#ccc9c2cc;">;
</span><span> max_draw_probability </span><span style="color:#f29e74;">/ </span><span>(</span><span style="color:#ffcc66;">1.0 </span><span style="color:#f29e74;">+</span><span> rating_difference_sensitivity </span><span style="color:#f29e74;">*</span><span> rating_diff)
</span><span>}
</span></code></pre>
<h4 id="adjusted-win-probability">Adjusted Win Probability</h4>
<p>Now that we know the draw probability, we can adjust the win probability. This
is done by subtracting 1.0 from the draw probability to arriave at a total
(black or white) win probability, and then using the Elo expectation equation
to assign that remaining win probability proportionately.</p>
<p>First, find the probability of any player winning:</p>
<fgure>
$$ P(\text{white or black}) = 1.0 - P(\text{draw})$$
</fgure>
<p>Next, compute the expectation of winning for each player according to their Elo
ratings:</p>
<fgure>
$$ P(\text{white}) = \frac{1}{1 + 10^{(R_{\text{black}} - R_{\text{white}}) / 400}} $$
$$ P(\text{black}) = 1 - P(\text{white}) $$
</fgure>
<p>And finally, adjust for the overall win probability, which takes draws into
account:</p>
<fgure>
$$ P(\text{white}) = P(\text{white}) \times P(\text{white or black}) $$
$$ P(\text{black}) = P(\text{black}) \times P(\text{white or black}) $$
</fgure>
<p>In code, it looks like this:</p>
<pre data-lang="rust" style="background-color:#212733;color:#ccc9c2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#5c6773;">// estimate probability of white winning, given two Elo ratings
</span><span style="color:#ffa759;">fn </span><span style="color:#ffd580;">probability_of_white_win</span><span>(</span><span style="color:#ffcc66;">white_elo</span><span style="color:#ccc9c2cc;">: </span><span style="color:#f29e74;">&</span><span>Rating, </span><span style="color:#ffcc66;">black_elo</span><span style="color:#ccc9c2cc;">: </span><span style="color:#f29e74;">&</span><span>Rating) </span><span style="color:#ccc9c2cc;">-> </span><span style="color:#ffa759;">f64 </span><span>{
</span><span> </span><span style="font-style:italic;color:#5c6773;">// Expected outcomes for players A and B
</span><span> </span><span style="font-style:italic;color:#5c6773;">// E_a = 1 / (1 + 10^((b - a) / 400)
</span><span> </span><span style="font-style:italic;color:#5c6773;">// E_b = 1 - E_a
</span><span> </span><span style="color:#ffcc66;">1.0 </span><span style="color:#f29e74;">/ </span><span>(</span><span style="color:#ffcc66;">1.0 </span><span style="color:#f29e74;">+ </span><span style="color:#ffcc66;">10.0</span><span style="color:#ffa759;">f64</span><span style="color:#f29e74;">.</span><span style="color:#f28779;">powf</span><span>((black_elo</span><span style="color:#f29e74;">.</span><span style="color:#ffcc66;">0 </span><span style="color:#f29e74;">as </span><span style="color:#ffa759;">f64 </span><span style="color:#f29e74;">-</span><span> white_elo</span><span style="color:#f29e74;">.</span><span style="color:#ffcc66;">0 </span><span style="color:#f29e74;">as </span><span style="color:#ffa759;">f64</span><span>) </span><span style="color:#f29e74;">/ </span><span style="color:#ffcc66;">400.0</span><span>))
</span><span>}
</span><span>
</span><span style="font-style:italic;color:#5c6773;">// estimate probability of black winning given white's probability of winning
</span><span style="color:#ffa759;">fn </span><span style="color:#ffd580;">probability_of_black_win</span><span>(</span><span style="color:#ffcc66;">probability_of_white_win</span><span style="color:#ccc9c2cc;">: </span><span style="color:#ffa759;">f64</span><span>) </span><span style="color:#ccc9c2cc;">-> </span><span style="color:#ffa759;">f64 </span><span>{
</span><span> </span><span style="color:#ffcc66;">1.0 </span><span style="color:#f29e74;">-</span><span> probability_of_white_win
</span><span>}
</span><span>
</span><span style="font-style:italic;color:#5c6773;">// given two ratings, compute probabilities of win/loss/draw and return in `ExpectedOutcome`
</span><span style="color:#ffa759;">pub fn </span><span style="color:#ffd580;">calculate_expected_outcome</span><span>(</span><span style="color:#ffcc66;">white_elo</span><span style="color:#ccc9c2cc;">: </span><span style="color:#f29e74;">&</span><span>Rating, </span><span style="color:#ffcc66;">black_elo</span><span style="color:#ccc9c2cc;">: </span><span style="color:#f29e74;">&</span><span>Rating) </span><span style="color:#ccc9c2cc;">-></span><span> ExpectedOutcome {
</span><span> </span><span style="font-style:italic;color:#5c6773;">// Estimate win probabilities
</span><span> </span><span style="color:#ffa759;">let mut</span><span> p_white </span><span style="color:#f29e74;">= </span><span style="color:#f28779;">probability_of_white_win</span><span>(white_elo</span><span style="color:#ccc9c2cc;">,</span><span> black_elo)</span><span style="color:#ccc9c2cc;">;
</span><span> </span><span style="color:#ffa759;">let mut</span><span> p_black </span><span style="color:#f29e74;">= </span><span style="color:#f28779;">probability_of_black_win</span><span>(p_white)</span><span style="color:#ccc9c2cc;">;
</span><span>
</span><span> </span><span style="font-style:italic;color:#5c6773;">// Estimate draw probability based on rating difference
</span><span> </span><span style="color:#ffa759;">let</span><span> p_draw </span><span style="color:#f29e74;">= </span><span style="color:#f28779;">probability_of_draw</span><span>(white_elo</span><span style="color:#ccc9c2cc;">,</span><span> black_elo)</span><span style="color:#ccc9c2cc;">;
</span><span>
</span><span> </span><span style="font-style:italic;color:#5c6773;">// Adjust win probabilities to account for draw probability
</span><span> p_white </span><span style="color:#f29e74;">*= </span><span style="color:#ffcc66;">1.0 </span><span style="color:#f29e74;">-</span><span> p_draw</span><span style="color:#ccc9c2cc;">;
</span><span> p_black </span><span style="color:#f29e74;">*= </span><span style="color:#ffcc66;">1.0 </span><span style="color:#f29e74;">-</span><span> p_draw</span><span style="color:#ccc9c2cc;">;
</span><span>
</span><span> </span><span style="font-style:italic;color:#5c6773;">// Sanity check
</span><span> </span><span style="color:#f28779;">assert_approx_eq!</span><span>((p_white </span><span style="color:#f29e74;">+</span><span> p_black </span><span style="color:#f29e74;">+</span><span> p_draw)</span><span style="color:#ccc9c2cc;">, </span><span style="color:#ffcc66;">1.0</span><span>)</span><span style="color:#ccc9c2cc;">;
</span><span>
</span><span> ExpectedOutcome {
</span><span> </span><span style="font-style:italic;color:#5c6773;">// Estimate win probabilities
</span><span> white</span><span style="color:#ccc9c2cc;">:</span><span> p_white</span><span style="color:#ccc9c2cc;">,
</span><span> black</span><span style="color:#ccc9c2cc;">:</span><span> p_black</span><span style="color:#ccc9c2cc;">,
</span><span> draw</span><span style="color:#ccc9c2cc;">:</span><span> p_draw</span><span style="color:#ccc9c2cc;">,
</span><span> }
</span><span>}
</span></code></pre>
<h3 id="game-simulation">Game Simulation</h3>
<p>We simulate a game by using the two players’ Elo ratings to compute teh win
probability of each player and the draw probability, which always sums to 1.0.
Then we simply use a random number to choose a simulated outcome. Here’s the
rust code:</p>
<pre data-lang="rust" style="background-color:#212733;color:#ccc9c2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#5c6773;">// simulates a single game
</span><span style="color:#ffa759;">fn </span><span style="color:#ffd580;">simulate_game</span><span>(</span><span style="color:#ffcc66;">white_elo</span><span style="color:#ccc9c2cc;">: </span><span style="color:#f29e74;">&</span><span>Rating, </span><span style="color:#ffcc66;">black_elo</span><span style="color:#ccc9c2cc;">: </span><span style="color:#f29e74;">&</span><span>Rating) </span><span style="color:#ccc9c2cc;">-></span><span> Outcome {
</span><span> </span><span style="font-style:italic;color:#5c6773;">// Get expected outcomes
</span><span> </span><span style="color:#ffa759;">let</span><span> e </span><span style="color:#f29e74;">= </span><span style="color:#f28779;">calculate_expected_outcome</span><span>(white_elo</span><span style="color:#ccc9c2cc;">,</span><span> black_elo)</span><span style="color:#ccc9c2cc;">;
</span><span>
</span><span> </span><span style="font-style:italic;color:#5c6773;">// Roll the dice
</span><span> </span><span style="color:#ffa759;">let mut</span><span> rng </span><span style="color:#f29e74;">= </span><span>rand</span><span style="color:#f29e74;">::</span><span>thread_rng()</span><span style="color:#ccc9c2cc;">;
</span><span> </span><span style="color:#ffa759;">let</span><span> random_value </span><span style="color:#f29e74;">=</span><span> rng</span><span style="color:#f29e74;">.</span><span>gen</span><span style="color:#f29e74;">::</span><span><</span><span style="color:#ffa759;">f64</span><span>>()</span><span style="color:#ccc9c2cc;">;
</span><span>
</span><span> </span><span style="font-style:italic;color:#5c6773;">// Determine and return outcome
</span><span> </span><span style="color:#ffa759;">if</span><span> random_value </span><span style="color:#f29e74;"><=</span><span> e</span><span style="color:#f29e74;">.</span><span>white {
</span><span> Outcome</span><span style="color:#f29e74;">::</span><span>WhiteWin
</span><span> } </span><span style="color:#ffa759;">else if</span><span> random_value </span><span style="color:#f29e74;"><= </span><span>(e</span><span style="color:#f29e74;">.</span><span>white </span><span style="color:#f29e74;">+</span><span> e</span><span style="color:#f29e74;">.</span><span>black) {
</span><span> Outcome</span><span style="color:#f29e74;">::</span><span>BlackWin
</span><span> } </span><span style="color:#ffa759;">else </span><span>{
</span><span> Outcome</span><span style="color:#f29e74;">::</span><span>Draw
</span><span> }
</span><span>}
</span></code></pre>
<p>Now to simulate the entire series of Hikaru’s games, we just apply this
function to the historical games to come up with a new set of outcomes.</p>
<p>Like this:</p>
<pre data-lang="rust" style="background-color:#212733;color:#ccc9c2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#5c6773;">// simulate a series of games and return a new simulated `Vec<Game>` series
</span><span style="color:#ffa759;">pub fn </span><span style="color:#ffd580;">simulate_games</span><span>(</span><span style="color:#ffcc66;">actual</span><span style="color:#ccc9c2cc;">: </span><span style="color:#f29e74;">&</span><span>[Game]) </span><span style="color:#ccc9c2cc;">-> </span><span style="font-style:italic;color:#5ccfe6;">Vec</span><span><Game> {
</span><span> actual
</span><span> </span><span style="color:#f29e74;">.</span><span style="color:#f28779;">iter</span><span>()
</span><span> </span><span style="color:#f29e74;">.</span><span style="color:#f28779;">map</span><span>(|</span><span style="color:#ffcc66;">g</span><span>| Game {
</span><span> white</span><span style="color:#ccc9c2cc;">:</span><span> g</span><span style="color:#f29e74;">.</span><span>white</span><span style="color:#f29e74;">.</span><span style="color:#f28779;">to_owned</span><span>()</span><span style="color:#ccc9c2cc;">,
</span><span> black</span><span style="color:#ccc9c2cc;">:</span><span> g</span><span style="color:#f29e74;">.</span><span>black</span><span style="color:#f29e74;">.</span><span style="color:#f28779;">to_owned</span><span>()</span><span style="color:#ccc9c2cc;">,
</span><span> outcome</span><span style="color:#ccc9c2cc;">: </span><span style="color:#f28779;">simulate_game</span><span>(</span><span style="color:#f29e74;">&</span><span>g</span><span style="color:#f29e74;">.</span><span>white</span><span style="color:#f29e74;">.</span><span>rating</span><span style="color:#ccc9c2cc;">, </span><span style="color:#f29e74;">&</span><span>g</span><span style="color:#f29e74;">.</span><span>black</span><span style="color:#f29e74;">.</span><span>rating)</span><span style="color:#ccc9c2cc;">,
</span><span> })
</span><span> </span><span style="color:#f29e74;">.</span><span style="color:#f28779;">collect</span><span>()
</span><span>}
</span></code></pre>
<p>Once we have all the (one million!) simulated chess.com careers, we build
histograms of achieved streaks for each and then merge them into a single
histogram for the entire simulation. Here it is all together:</p>
<pre data-lang="rust" style="background-color:#212733;color:#ccc9c2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#ffa759;">let</span><span> iterations </span><span style="color:#f29e74;">= </span><span style="color:#ffcc66;">1_000_000</span><span style="color:#ccc9c2cc;">;
</span><span style="color:#ffa759;">let</span><span> histograms</span><span style="color:#ccc9c2cc;">: </span><span style="font-style:italic;color:#5ccfe6;">Vec</span><span><HashMap<</span><span style="color:#ffa759;">usize</span><span>, </span><span style="color:#ffa759;">usize</span><span>>> </span><span style="color:#f29e74;">= </span><span>(</span><span style="color:#ffcc66;">0</span><span style="color:#f29e74;">..</span><span>iterations)
</span><span> </span><span style="color:#f29e74;">.</span><span style="color:#f28779;">into_par_iter</span><span>()
</span><span> </span><span style="color:#f29e74;">.</span><span style="color:#f28779;">map</span><span>(|_| {
</span><span> </span><span style="color:#ffa759;">let</span><span> simulated_games </span><span style="color:#f29e74;">= </span><span style="color:#f28779;">simulate_games</span><span>(</span><span style="color:#f29e74;">&</span><span>games)</span><span style="color:#ccc9c2cc;">;
</span><span> </span><span style="color:#f28779;">generate_win_streak_histogram</span><span>(</span><span style="color:#bae67e;">"Hikaru"</span><span style="color:#ccc9c2cc;">, </span><span style="color:#f29e74;">&</span><span>simulated_games)
</span><span> })
</span><span> </span><span style="color:#f29e74;">.</span><span style="color:#f28779;">collect</span><span>()</span><span style="color:#ccc9c2cc;">;
</span><span>
</span><span style="font-style:italic;color:#5c6773;">// Reduce the histograms into a single histogram
</span><span style="color:#ffa759;">let</span><span> merged_histogram </span><span style="color:#f29e74;">=</span><span> histograms
</span><span> </span><span style="color:#f29e74;">.</span><span style="color:#f28779;">into_iter</span><span>()
</span><span> </span><span style="color:#f29e74;">.</span><span style="color:#f28779;">reduce</span><span>(|</span><span style="color:#ffa759;">mut </span><span style="color:#ffcc66;">acc</span><span style="color:#ccc9c2cc;">, </span><span style="color:#ffcc66;">h</span><span>| {
</span><span> </span><span style="color:#ffa759;">for </span><span>(k</span><span style="color:#ccc9c2cc;">,</span><span> v) </span><span style="color:#f29e74;">in</span><span> h {
</span><span> </span><span style="color:#f29e74;">*</span><span>acc</span><span style="color:#f29e74;">.</span><span style="color:#f28779;">entry</span><span>(k)</span><span style="color:#f29e74;">.</span><span style="color:#f28779;">or_insert</span><span>(</span><span style="color:#ffcc66;">0</span><span>) </span><span style="color:#f29e74;">+=</span><span> v</span><span style="color:#ccc9c2cc;">;
</span><span> }
</span><span> acc
</span><span> })
</span><span> </span><span style="color:#f29e74;">.</span><span style="color:#f28779;">unwrap</span><span>()</span><span style="color:#ccc9c2cc;">;
</span></code></pre>
<p>Note the use of <code>rayon</code> with <code>into_par_iter()</code>. This is a really nice bit of
code which makes it trivially easy to use all my cores.</p>
<p>You can find all of my code <a href="https://git.sr.ht/~swaits/hikaru">here</a>.</p>
<h2 id="results">Results</h2>
<p>I simulated the entire 47,407 game sequence one million times. This section
details my findings.</p>
<h3 id="actual-winning-streaks">Actual Winning Streaks</h3>
<p>Before getting into the model’s results, I think it’s interesting to look at
what we found in Hikaru’s data.</p>
<p>My code reports a good number of long winning streaks in Hikaru’s chess.com
career. Here are his 40-game and longer streaks, along with the number of times
he achieved each:</p>
<pre data-lang="text" style="background-color:#212733;color:#ccc9c2;" class="language-text "><code class="language-text" data-lang="text"><span>Streak Length: 40, Count: 4
</span><span>Streak Length: 41, Count: 2
</span><span>Streak Length: 42, Count: 4
</span><span>Streak Length: 43, Count: 1
</span><span>Streak Length: 44, Count: 1
</span><span>Streak Length: 45, Count: 2
</span><span>Streak Length: 46, Count: 2 <= OMG DID HE CHEAT WTF 🤡
</span><span>Streak Length: 47, Count: 6
</span><span>Streak Length: 48, Count: 1
</span><span>Streak Length: 50, Count: 1
</span><span>Streak Length: 51, Count: 2
</span><span>Streak Length: 52, Count: 1
</span><span>Streak Length: 54, Count: 3
</span><span>Streak Length: 55, Count: 2
</span><span>Streak Length: 56, Count: 2
</span><span>Streak Length: 57, Count: 1
</span><span>Streak Length: 58, Count: 1
</span><span>Streak Length: 59, Count: 2
</span><span>Streak Length: 61, Count: 2
</span><span>Streak Length: 62, Count: 1
</span><span>Streak Length: 69, Count: 1
</span><span>Streak Length: 72, Count: 1
</span><span>Streak Length: 77, Count: 1
</span><span>Streak Length: 78, Count: 1
</span><span>Streak Length: 82, Count: 1
</span><span>Streak Length: 85, Count: 1
</span><span>Streak Length: 100, Count: 1
</span><span>Streak Length: 117, Count: 1
</span></code></pre>
<p>Yes, you’re seeing that right. There are 85, 100, and even 117 winning streaks.
You see, these streamers will sometimes play a lower-rated player many times
consecutively. If they prevail some pre-chosen number of times (say, 100), then
they will colloquially say that they “adopted” that player. It’s fun. And this
is why it’s important to look at the entire, exact sequence of opponents Hikaru
played.</p>
<h3 id="probability-of-win-streaks">Probability of Win Streaks</h3>
<p>Based on my analysis, the following chart shows the probability of getting at
least one winning streak. For example, if you look at 46 on the x-axis, you’ll
see its probability is 100% on the y-axis.</p>
<p>As another example, you can see that the probability of hitting at least one
80-game winning streak is just a bit below 30%.</p>
<p>We can even see about a 5% change Hikaru would’ve hit a 140 game streak. So, if
there were 20 parallel Hikaru’s playing exactly the same games, maybe one of
them would have pulled this off.</p>
<p><img src="https://swaits.com/hikaru-winning-streaks/probability.png" alt="probability chart" /></p>
<p>Oh, but what about those weird bumps at the 70, 100, 115, and even 140 streaks?
Again, remember that this is based entirely on the sequence of opponents
Hikaru played. If at some point in his nearly 50k games he played someone rated
significantly lower than him for 70, 100, 115, or 140 times consecutively, then
that increases the probability of winning that streak.</p>
<p>And when we look at the data, Hikaru does in fact have streaks at 69, 72, 77,
78, 82, 85, 100, and 117.</p>
<p>Herein lies the plainly obvious reasoning that Kramnik seems to be missing.</p>
<h3 id="expected-number-of-win-streaks">Expected Number of Win Streaks</h3>
<p>Probability is one thing. In this case, it removes all doubt about the
possibility of achieving something like a 46-game winning streak.</p>
<p>But what’s more interesting when we look at whether someone cheated is how
close to the expectations they performed. The chart below plots both the
expected number of each winning streak and actual number of winning streaks
recorded in Hikaru’s career.</p>
<p>The red line is the number of streaks predicted by my modeling. The blue line
is the number of streaks Hikaru scored.</p>
<p>For example, we can see that we expect Hikaru to get about 35 streaks of 20
wins. Just look across the x-axis to 20, and go up to find that the red line
crosses about about 35. But note that the blue line shows Hikaru only actually
got about 28 of those 20 game streaks.</p>
<p>If we look at the 46 streak data point, we see an expectation of 2. And we also
see an actual of 2. Hmmmmm….</p>
<p>One note on this chart: the y-axis is logarithmic. This makes it so that we can
show a much larger range of data. It might seem a little weird to some, but
it’s pretty normal.</p>
<p><img src="https://swaits.com/hikaru-winning-streaks/expected.png" alt="expected and actual streaks" /></p>
<p>The data gets a bit noisy to the right of the 25 streak line. This is because
such streaks occur less frequently, and thus our sample size gets quite a bit
smaller.</p>
<h3 id="how-is-hikaru-performing">How is Hikaru Performing?</h3>
<p>Now, let’s plot the difference between what Hikaru did and what my simulation
expects at each of the streaks of 20 and higher.</p>
<p>To use this chart, look for a winning streak on the x-axis, like 55. We see
that the (actual - expected) is 1. This means that Hikaru recorded 1 more
55-game winning streak than expected.</p>
<p><img src="https://swaits.com/hikaru-winning-streaks/delta.png" alt="difference of expected and actual streaks" /></p>
<p>Generally, we can see that for streaks 20 and above, Hikaru is performing just
about as expected. If we smoothed this line to account for the noisiness at
this end of the data, we’d find that he’s well within the margin of
expectations.</p>
<h2 id="closing">Closing</h2>
<p>Hikaru’s win streaks on chess.com are normal. As I said in the beginning, my
analysis of 47,407 games played by Hikaru on chess.com shows that:</p>
<ul>
<li>the probability of hitting at least one 46 game winning streak is 100%, with
significant confidence</li>
<li>the expected number of 46 game winning streaks is 2</li>
<li>the actual number of 46 game winning streaks in Hikaru’s chess.com career is 2</li>
<li>the amount of bullshit in this cheating accusation is significantly high</li>
</ul>
<p>Being great at one thing, chess in this case, doesn’t mean you’re great at
everything. Even being literally one of the best chess players in the world is
not a reason to think someone is <em>smart</em>. But, our society tells people who
achieve greatness in chess that they’re smart. And apparently for some folks,
especially in this case, that’s gone to their head. Now, perhaps Kramnik has
some other evidence to Hikaru’s cheating. But, what we’ve seen thus far - the 46
game winning streaks - is not enough. It’s not enough for even the most basic of
high-school level math and statistics. And so, being chess world champion does
not mean you’re smart.</p>
<p>That said, many chess players are actually damn smart. It tends to attract the
bookish type who are into educating themselves. And, nothing but respect from me
for people who can play chess. I am not one of those people.</p>
<p>If you find a mistake in my work that shows I’m wrong, please let me know!</p>
<p>And finally, I’ll leave you with this, courtesy of ChatGPT and DALL-E:</p>
<p><img src="https://swaits.com/hikaru-winning-streaks/clown.jpeg" alt="a clown playing chess" /></p>
<blockquote>
<p>A portrait of a sad clown sitting at a chess table, deeply engrossed in a
game of chess. The clown has a striking resemblance to a tall, slender man
with sharp facial features, reminiscent of a famous chess grandmaster. He
wears a traditional clown outfit with a ruffled collar and a small hat, and
his face is painted with exaggerated sad expressions. The setting is a dimly
lit room, with a focus on the chessboard and the contemplative pose of the
clown.</p>
</blockquote>
Ruthless Prioritization2023-11-01T00:00:00+00:002023-11-01T00:00:00+00:00
Unknown
https://swaits.com/prioritizing-with-eisenhower-matrix/<p>I feel overwhelmed these days. My TODO list has spiraled out of control and I
feel immobilized. I need to get myself organized, and I need to ruthlessly
prioritize. <a href="https://www.linkedin.com/in/michael-terkowitz-1ba89a3/">Someone smart at
Amazon</a> once said that
you’ll have 100 things you <em>need</em> or <em>want</em> to do; but you can only do <em>one or
two</em> of those and need to <em>throw the rest away</em>. That’s tough, but important!</p>
<p>Here I describe an approach I was using in the recent past, and an approach I’m
going to move to today.</p>
<p><strong>Contents:</strong></p>
<!--toc:start-->
<ul>
<li><a href="https://swaits.com/prioritizing-with-eisenhower-matrix/#my-most-recent-pen-and-paper-productivity-system">My most recent pen and paper productivity system</a></li>
<li><a href="https://swaits.com/prioritizing-with-eisenhower-matrix/#my-not-so-new-new-pen-and-paper-system">My not-so-new, new pen and paper system</a>
<ul>
<li><a href="https://swaits.com/prioritizing-with-eisenhower-matrix/#using-the-system">Using the system</a></li>
<li><a href="https://swaits.com/prioritizing-with-eisenhower-matrix/#download-the-pdf-or-copy-the-google-sheet-template">Download the PDF or copy the Google Sheet template</a></li>
</ul>
</li>
</ul>
<!--toc:end-->
<h3 id="my-most-recent-pen-and-paper-productivity-system">My most recent pen and paper productivity system</h3>
<p>I like productivity tools. And I always find myself coming back to pen and
paper stuff. Writing things on paper is very satisfying. Something about the
physicality of it just feels nice.</p>
<p>For some time, I used this <a href="https://inkandvolt.com/products/ink-volt-dashboard-deskpad">ink+volt Dashboard
Deskpad</a> weekly
planner and tracker. It’s very nicely designed. If you like the looks of it,
please consider ordering it from them and giving it a try. I don’t regret
buying and using it myself.</p>
<p>When I actually used this, I found it really useful. However, I’ve been missing
a crucial <em>ruthless prioritization</em> step. Over time, my TODO list has exploded.
Now today, I feel buried under an impossible amount of work that while I’m
committed to finishing, is unrealistic.</p>
<p>Today I am using this planner less and less. I fell off the wagon.</p>
<h3 id="my-not-so-new-new-pen-and-paper-system">My not-so-new, new pen and paper system</h3>
<p>I decided it’s time to simplify things. I’ve used the <a href="https://en.wikipedia.org/wiki/Eisenhower_matrix">Eisenhower
Matrix</a> quite a bit in the
past to prioritize work. It’s definitely not new.</p>
<p>But my previous use of it was always in a digital form, such is on a <a href="https://trello.com">Trello
board</a> or similar proprietary tools.</p>
<p>Now I’m reviving it as a pen and paper system. This combines the simplicity of
a prioritization system I’ve used extensively with the pleasure of it being a
pen and paper system.</p>
<p>So, without further ado, here it is. First, the screenshot (click for a
printable PDF):</p>
<p><a href="https://swaits.com/prioritizing-with-eisenhower-matrix/prioritization-matrix.pdf"><img src="https://swaits.com/prioritizing-with-eisenhower-matrix/prioritization-matrix.png" alt="prioritization-matrix" /></a></p>
<h4 id="using-the-system">Using the system</h4>
<p>It’s pretty simple. If you’re already familiar with the <a href="https://en.wikipedia.org/wiki/Eisenhower_matrix">Eisenhower
Matrix</a>, you can probably skip
this section. But, in case you’re not, here’s how it works:
w
<strong>Add tasks:</strong> As I add each task, I think about its importance and its
urgency. Then I write it in the matching box.</p>
<p><strong>Follow the priorities:</strong></p>
<ol>
<li><strong>Execute</strong>: These are important and urgent. They’re things I need to
complete now.</li>
<li><strong>Schedule</strong>: These are important, but not urgent. I need to schedule or
otherwise plan for how I’ll get to them soon. But not today.</li>
<li><strong>Redirect</strong>: Tasks that are urgent, but not important. I should find ways
to distribute these to teammates, friends, or family members (depending on
the context) to see if they can help. Not in a “here, do my stuff” sort of way.
More in a “hey, are you interested in helping with this” way.</li>
<li><strong>Abandon</strong>: Not important and not urgent. In practice, I’ve found that
I’ll <em>never</em> get to these. So, cut to the chase. Delete them.</li>
</ol>
<p>In summary, we only do work we deem <em>important</em>, and we do it or plan to do it
in order of <em>urgency</em>. All other work is delegated or discarded.</p>
<p><strong>Do the work:</strong> That’s the point of this whole thing! Whenever I’m ready to
start a new task, I pay attention to these priorities and let them guide my
choice.</p>
<p>Remember, there are dopamine hits coming your way as you complete tasks and
physically check them off.</p>
<p><strong>Create a ritual:</strong> Each week I print a new blank sheet and transfer any
incomplete tasks over. As I copy each task, I reassess its priority and
importance. These things do change over time.</p>
<p>It’s also a good opportunity to commit to the <em>Abandon</em> step and actually drop
the tasks I’ll never get to. That’s why it’s <em>ruthless</em> prioritization.</p>
<h4 id="download-the-pdf-or-copy-the-google-sheet-template">Download the PDF or copy the Google Sheet template</h4>
<p>If you want to try this for yourself, please feel free to grab the PDF and
print it, or copy the Google Sheet using the <code>File -> Copy</code> menu and
personalize it.</p>
<ul>
<li>Download and print the <a href="https://swaits.com/prioritizing-with-eisenhower-matrix/prioritization-matrix.pdf">Prioritization Matrix (US Letter, PDF)</a>.</li>
<li>Copy the <a href="https://docs.google.com/spreadsheets/d/1UK9fJRPcSDd8gUeAO8GLue3c3FwWMVhF24k1pfZuE7E/edit?usp=sharing">Prioritization Matrix (Google
Sheet)</a>.</li>
</ul>
<p>Enjoy!</p>
Hacking on Neovim Plugins2023-10-26T00:00:00+00:002023-10-26T00:00:00+00:00
Unknown
https://swaits.com/hacking-out-neovim-plugins/<p>I’m having fun. It may seem mindless, pointless, or even counter-productive.
But it’s not. It’s fun!</p>
<p>It all started with me optimizing my <code>nvim</code> start times. Though I used <code>vi</code> or
<code>vim</code> basically forever, and used <code>nvim</code> since it started in 2014, I never
really worried about optimizing startup times. But, truth is, <code>nvim</code> is doing
<em>much more</em> today than it ever did in the past, so maybe performance is more
critical now.</p>
<p>I already used <a href="https://www.lazyvim.org/">LazyVim</a> as my starting point. Now I
wanted to trim down the number of plugins I was using, maximize my lazy
loading, and generally streamline my configuration. And that led me into
hacking out a few really simple plugins.</p>
<p>But before I dive into the plugin stuff, I want to talk a bit about why I chose
LazyVim awhile ago.</p>
<h4 id="contents">Contents</h4>
<!--toc:start-->
<ul>
<li><a href="https://swaits.com/hacking-out-neovim-plugins/#lazyvim">LazyVim?</a>
<ul>
<li><a href="https://swaits.com/hacking-out-neovim-plugins/#optimizing-startup">Optimizing Startup</a></li>
<li><a href="https://swaits.com/hacking-out-neovim-plugins/#results">Results</a></li>
</ul>
</li>
<li><a href="https://swaits.com/hacking-out-neovim-plugins/#writing-plugins">Writing Plugins</a>
<ul>
<li><a href="https://swaits.com/hacking-out-neovim-plugins/#thethethe-nvim">thethethe.nvim</a></li>
<li><a href="https://swaits.com/hacking-out-neovim-plugins/#zellij-nav-nvim">zellij-nav.nvim</a></li>
<li><a href="https://swaits.com/hacking-out-neovim-plugins/#scratch-nvim">scratch.nvim</a></li>
</ul>
</li>
<li><a href="https://swaits.com/hacking-out-neovim-plugins/#closing">Closing</a></li>
</ul>
<!--toc:end-->
<h2 id="lazyvim">LazyVim?</h2>
<p>LazyVim is a pre-configured <code>nvim</code> setup based on the <a href="https://github.com/folke/lazy.nvim">lazy.vim</a> plugin manager.</p>
<p>I use LazyVim because it does most of the complex configuration for me, and yet
it’s still easily customizable. So, out of the box, I get an awesome
configuration for:</p>
<ul>
<li>all the languages I need (primarily rust and tex)</li>
<li>a full debugger interface</li>
<li>automatic testing</li>
<li>LSP support, auto-completion, snippets</li>
<li>Codeium completions</li>
<li>formatting</li>
<li>linting</li>
<li>really nice UI additions and tweaks</li>
<li>it’s fast!</li>
</ul>
<p>In other words, it’s actually a true IDE. To me “true” here means it must
include a debugger, at minimum.</p>
<p>And, you don’t have to use all that. It’s easy to configure. If you don’t need
something, you just turn it off. If something’s missing, you can add it.
Overall, it’s the best <code>nvim</code> plugin manager and default configuration I’ve
used.</p>
<h3 id="optimizing-startup">Optimizing Startup</h3>
<p>I always felt like my <code>nvim</code> loaded quickly enough. I didn’t <strong>need</strong> to
optimize anything. But then I thought, “hey, I’m not really taking advantage of
<code>lazy.vim</code>’s lazy loading support here”, and figured I’d see how far I could
push it.</p>
<p>Most of my startup optimization came from configuring as many of my custom
loaded plugins as possible to load lazily. This consists of setting <code>lazy = true</code> and then either specifying the event to keystrokes to load on. For
example, here’s how I’m loading
<a href="https://github.com/chrisgrieser/nvim-origami">nvim-origami</a> and
<a href="https://github.com/anuvyklack/pretty-fold.nvim">pretty-fold.nvim</a>, so they
won’t load until <code>nvim</code> fires the <code>BufReadPost</code> event.</p>
<pre data-lang="lua" style="background-color:#212733;color:#ccc9c2;" class="language-lua "><code class="language-lua" data-lang="lua"><span style="color:#ffa759;">return </span><span>{
</span><span> {
</span><span> </span><span style="color:#bae67e;">"chrisgrieser/nvim-origami"</span><span style="color:#ccc9c2cc;">,
</span><span> </span><span style="color:#bae67e;">dependencies </span><span style="color:#ccc9c2cc;">= </span><span>{ </span><span style="color:#bae67e;">"nvim-treesitter/nvim-treesitter" </span><span>}</span><span style="color:#ccc9c2cc;">,
</span><span> </span><span style="color:#bae67e;">lazy </span><span style="color:#ccc9c2cc;">= </span><span style="color:#ffcc66;">true</span><span style="color:#ccc9c2cc;">,
</span><span> </span><span style="color:#bae67e;">event </span><span style="color:#ccc9c2cc;">= </span><span style="color:#bae67e;">"BufReadPost"</span><span style="color:#ccc9c2cc;">, </span><span style="font-style:italic;color:#5c6773;">-- later or on keypress would prevent saving folds
</span><span> </span><span style="color:#ffd580;">init </span><span style="color:#ccc9c2cc;">= </span><span style="color:#ffa759;">function</span><span>()
</span><span> vim</span><span style="color:#f29e74;">.</span><span>opt</span><span style="color:#f29e74;">.</span><span>foldenable </span><span style="color:#f29e74;">= </span><span style="color:#ffcc66;">true
</span><span> vim</span><span style="color:#f29e74;">.</span><span>opt</span><span style="color:#f29e74;">.</span><span>foldmethod </span><span style="color:#f29e74;">= </span><span style="color:#bae67e;">"expr"
</span><span> vim</span><span style="color:#f29e74;">.</span><span>opt</span><span style="color:#f29e74;">.</span><span>foldexpr </span><span style="color:#f29e74;">= </span><span style="color:#bae67e;">"nvim_treesitter#foldexpr()"
</span><span> vim</span><span style="color:#f29e74;">.</span><span>opt</span><span style="color:#f29e74;">.</span><span>foldnestmax </span><span style="color:#f29e74;">= </span><span style="color:#ffcc66;">3
</span><span> vim</span><span style="color:#f29e74;">.</span><span>opt</span><span style="color:#f29e74;">.</span><span>foldminlines </span><span style="color:#f29e74;">= </span><span style="color:#ffcc66;">1
</span><span> </span><span style="color:#ffa759;">end</span><span style="color:#ccc9c2cc;">,
</span><span> </span><span style="color:#bae67e;">opts </span><span style="color:#ccc9c2cc;">= </span><span>{}</span><span style="color:#ccc9c2cc;">,
</span><span> }</span><span style="color:#ccc9c2cc;">,
</span><span> {
</span><span> </span><span style="color:#bae67e;">"anuvyklack/pretty-fold.nvim"</span><span style="color:#ccc9c2cc;">,
</span><span> </span><span style="color:#bae67e;">lazy </span><span style="color:#ccc9c2cc;">= </span><span style="color:#ffcc66;">true</span><span style="color:#ccc9c2cc;">,
</span><span> </span><span style="color:#bae67e;">event </span><span style="color:#ccc9c2cc;">= </span><span style="color:#bae67e;">"BufReadPost"</span><span style="color:#ccc9c2cc;">,
</span><span> </span><span style="color:#bae67e;">opts </span><span style="color:#ccc9c2cc;">= </span><span>{}</span><span style="color:#ccc9c2cc;">,
</span><span> }</span><span style="color:#ccc9c2cc;">,
</span><span>}
</span></code></pre>
<p>Here’s how I load <a href="https://github.com/duff/vim-scratch">vim-scratch</a> when the
<code>nvim</code> command <code>Scratch</code> is called:</p>
<pre data-lang="lua" style="background-color:#212733;color:#ccc9c2;" class="language-lua "><code class="language-lua" data-lang="lua"><span style="color:#ffa759;">return </span><span>{
</span><span> </span><span style="color:#bae67e;">"duff/vim-scratch"</span><span style="color:#ccc9c2cc;">,
</span><span> </span><span style="color:#bae67e;">lazy </span><span style="color:#ccc9c2cc;">= </span><span style="color:#ffcc66;">true</span><span style="color:#ccc9c2cc;">,
</span><span> </span><span style="color:#bae67e;">cmd </span><span style="color:#ccc9c2cc;">= </span><span style="color:#bae67e;">"Scratch"</span><span style="color:#ccc9c2cc;">,
</span><span>}
</span></code></pre>
<p>And finally, here’s how I make <a href="https://yazi-rs.github.io/">yazi</a> load via
<a href="https://github.com/DreamMaoMao/yazi.nvim">yazi.nvim</a> on a keystroke:</p>
<pre data-lang="lua" style="background-color:#212733;color:#ccc9c2;" class="language-lua "><code class="language-lua" data-lang="lua"><span style="color:#ffa759;">return </span><span>{
</span><span> </span><span style="color:#bae67e;">"DreamMaoMao/yazi.nvim"</span><span style="color:#ccc9c2cc;">,
</span><span> </span><span style="color:#bae67e;">lazy </span><span style="color:#ccc9c2cc;">= </span><span style="color:#ffcc66;">true</span><span style="color:#ccc9c2cc;">,
</span><span> </span><span style="color:#bae67e;">dependencies </span><span style="color:#ccc9c2cc;">= </span><span>{
</span><span> </span><span style="color:#bae67e;">"nvim-telescope/telescope.nvim"</span><span style="color:#ccc9c2cc;">,
</span><span> </span><span style="color:#bae67e;">"nvim-lua/plenary.nvim"</span><span style="color:#ccc9c2cc;">,
</span><span> }</span><span style="color:#ccc9c2cc;">,
</span><span>
</span><span> </span><span style="color:#bae67e;">keys </span><span style="color:#ccc9c2cc;">= </span><span>{
</span><span> { </span><span style="color:#bae67e;">"<leader>fy"</span><span style="color:#ccc9c2cc;">, </span><span style="color:#bae67e;">"<cmd>Yazi<CR>"</span><span style="color:#ccc9c2cc;">, </span><span style="color:#bae67e;">desc </span><span style="color:#ccc9c2cc;">= </span><span style="color:#bae67e;">"Toggle Yazi" </span><span>}</span><span style="color:#ccc9c2cc;">,
</span><span> }</span><span style="color:#ccc9c2cc;">,
</span><span>}
</span></code></pre>
<h3 id="results">Results</h3>
<p>After trimming out lots of unnecessary plugins and making as many plugins as
possible load lazily, my <code>nvim</code> starts in under 90ms now (see screenshot
below). Before this it was in the 250ms-300ms range.</p>
<p>As I said above, I don’t <strong>need</strong> this. But it’s very pleasing.</p>
<p><img src="https://swaits.com/hacking-out-neovim-plugins/nvim-alpha.png" alt="my nvim start menu" /></p>
<h2 id="writing-plugins">Writing Plugins</h2>
<p>As I went through all the plugins I was using, I evaluated not only whether I
really needed them, but also whether they were developed for <code>nvim</code> (instead of
just <code>vim</code>) and played nicely with lazy loading.</p>
<h3 id="thethethe-nvim"><a href="https://git.sr.ht/~swaits/thethethe.nvim">thethethe.nvim</a> <img src="#thethethe-nvim" alt="" /></h3>
<p>One of those plugins was
<a href="https://github.com/panozzaj/vim-autocorrect">vim-autocorrect</a>. This is a
plugin that automatically corrects common typos like “teh” into “the” by use of
<code>vim</code> abbreviations (<code>iabbrev</code>). But, it was designed for <code>vim</code> and written in
<code>vimscript</code>. I wanted something in <code>lua</code> and specific to <code>nvim</code>.</p>
<p>And so, <a href="https://git.sr.ht/~swaits/thethethe.nvim">thethethe.nvim</a> was born. I
converted the dictionary to <code>lua</code> and deferred its loading, so it wouldn’t delay
startup.</p>
<p>Now the dictionary looks like this:</p>
<pre data-lang="lua" style="background-color:#212733;color:#ccc9c2;" class="language-lua "><code class="language-lua" data-lang="lua"><span style="color:#ffa759;">return </span><span style="color:#bae67e;">[[
</span><span style="color:#bae67e;">adn:and
</span><span style="color:#bae67e;">teh:the
</span><span style="color:#bae67e;">-- ... thousands more words here
</span><span style="color:#bae67e;">]]
</span></code></pre>
<p>In the initialization code, I wrote a function to load this dictionary into
<code>iabbrev</code>s, which I’m able to defer calling like this:</p>
<pre data-lang="lua" style="background-color:#212733;color:#ccc9c2;" class="language-lua "><code class="language-lua" data-lang="lua"><span style="font-style:italic;color:#5c6773;">-- execute the function after config.delay_ms
</span><span>vim</span><span style="color:#f29e74;">.</span><span style="color:#ffd580;">defer_fn</span><span>(load_abbreviations</span><span style="color:#ccc9c2cc;">, </span><span>config</span><span style="color:#f29e74;">.</span><span>delay_ms)
</span></code></pre>
<p>At the end, I can lazy load my own plugin like this:</p>
<pre data-lang="lua" style="background-color:#212733;color:#ccc9c2;" class="language-lua "><code class="language-lua" data-lang="lua"><span style="color:#ffa759;">return </span><span>{
</span><span> </span><span style="color:#bae67e;">"https://git.sr.ht/~swaits/thethethe.nvim"</span><span style="color:#ccc9c2cc;">,
</span><span> </span><span style="color:#bae67e;">lazy </span><span style="color:#ccc9c2cc;">= </span><span style="color:#ffcc66;">true</span><span style="color:#ccc9c2cc;">,
</span><span> </span><span style="color:#bae67e;">event </span><span style="color:#ccc9c2cc;">= </span><span style="color:#bae67e;">"VeryLazy"</span><span style="color:#ccc9c2cc;">,
</span><span> </span><span style="color:#bae67e;">opts </span><span style="color:#ccc9c2cc;">= </span><span>{}</span><span style="color:#ccc9c2cc;">,
</span><span>}
</span></code></pre>
<p>Neat!</p>
<h3 id="zellij-nav-nvim"><a href="https://git.sr.ht/~swaits/zellij-nav.nvim">zellij-nav.nvim</a> <img src="#zellij-nav-nvim" alt="" /></h3>
<p>Next up, in the process of switching from <a href="https://github.com/tmux/tmux">tmux</a>
to <a href="https://zellij.dev/">zellij</a>, I wanted a way to easily move both between
windows inside <code>nvim</code>, and then also move from an <code>nvim</code> pane in zellij to
another zellij pane. I found a few plugins doing this, but I found them to be
needlessly complex.</p>
<p>So I set out to create the smallest, simplest thing that worked. I ended up
with 43 whole lines of <code>lua</code> code. Yes, this is the entire plugin. It’s short
enough to include here in its entirety:</p>
<pre data-lang="lua" style="background-color:#212733;color:#ccc9c2;" class="language-lua "><code class="language-lua" data-lang="lua"><span style="color:#ffa759;">local </span><span>M </span><span style="color:#f29e74;">= </span><span>{}
</span><span>
</span><span style="color:#ffa759;">local function </span><span style="color:#ffd580;">nav</span><span>(</span><span style="color:#ffcc66;">short_direction</span><span style="color:#ccc9c2cc;">, </span><span style="color:#ffcc66;">direction</span><span>)
</span><span> </span><span style="font-style:italic;color:#5c6773;">-- get window ID, try switching windows, and get ID again to see if it worked
</span><span> </span><span style="color:#ffa759;">local </span><span>cur_winnr </span><span style="color:#f29e74;">= </span><span>vim</span><span style="color:#f29e74;">.</span><span>fn</span><span style="color:#f29e74;">.</span><span style="color:#ffd580;">winnr</span><span>()
</span><span> vim</span><span style="color:#f29e74;">.</span><span>api</span><span style="color:#f29e74;">.</span><span style="color:#ffd580;">nvim_command</span><span>(</span><span style="color:#bae67e;">"wincmd " </span><span style="color:#f29e74;">.. </span><span>short_direction)
</span><span> </span><span style="color:#ffa759;">local </span><span>new_winnr </span><span style="color:#f29e74;">= </span><span>vim</span><span style="color:#f29e74;">.</span><span>fn</span><span style="color:#f29e74;">.</span><span style="color:#ffd580;">winnr</span><span>()
</span><span>
</span><span> </span><span style="font-style:italic;color:#5c6773;">-- if the window ID didn't change, then we didn't switch
</span><span> </span><span style="color:#ffa759;">if </span><span>cur_winnr </span><span style="color:#f29e74;">== </span><span>new_winnr </span><span style="color:#ffa759;">then
</span><span> vim</span><span style="color:#f29e74;">.</span><span>fn</span><span style="color:#f29e74;">.</span><span style="color:#ffd580;">system</span><span>(</span><span style="color:#bae67e;">"zellij action move-focus " </span><span style="color:#f29e74;">.. </span><span>direction)
</span><span> </span><span style="color:#ffa759;">if </span><span>vim</span><span style="color:#f29e74;">.</span><span>v</span><span style="color:#f29e74;">.</span><span>shell_error </span><span style="color:#f29e74;">~= </span><span style="color:#ffcc66;">0 </span><span style="color:#ffa759;">then
</span><span> </span><span style="color:#f28779;">error</span><span>(</span><span style="color:#bae67e;">"zellij executable not found in path"</span><span>)
</span><span> </span><span style="color:#ffa759;">end
</span><span> </span><span style="color:#ffa759;">end
</span><span style="color:#ffa759;">end
</span><span>
</span><span style="color:#ffa759;">function </span><span>M</span><span style="color:#f29e74;">.</span><span style="color:#ffd580;">up</span><span>()
</span><span> </span><span style="color:#ffd580;">nav</span><span>(</span><span style="color:#bae67e;">"k"</span><span style="color:#ccc9c2cc;">, </span><span style="color:#bae67e;">"up"</span><span>)
</span><span style="color:#ffa759;">end
</span><span>
</span><span style="color:#ffa759;">function </span><span>M</span><span style="color:#f29e74;">.</span><span style="color:#ffd580;">down</span><span>()
</span><span> </span><span style="color:#ffd580;">nav</span><span>(</span><span style="color:#bae67e;">"j"</span><span style="color:#ccc9c2cc;">, </span><span style="color:#bae67e;">"down"</span><span>)
</span><span style="color:#ffa759;">end
</span><span>
</span><span style="color:#ffa759;">function </span><span>M</span><span style="color:#f29e74;">.</span><span style="color:#ffd580;">right</span><span>()
</span><span> </span><span style="color:#ffd580;">nav</span><span>(</span><span style="color:#bae67e;">"l"</span><span style="color:#ccc9c2cc;">, </span><span style="color:#bae67e;">"right"</span><span>)
</span><span style="color:#ffa759;">end
</span><span>
</span><span style="color:#ffa759;">function </span><span>M</span><span style="color:#f29e74;">.</span><span style="color:#ffd580;">left</span><span>()
</span><span> </span><span style="color:#ffd580;">nav</span><span>(</span><span style="color:#bae67e;">"h"</span><span style="color:#ccc9c2cc;">, </span><span style="color:#bae67e;">"left"</span><span>)
</span><span style="color:#ffa759;">end
</span><span>
</span><span style="font-style:italic;color:#5c6773;">-- create our exported setup() function
</span><span style="color:#ffa759;">function </span><span>M</span><span style="color:#f29e74;">.</span><span style="color:#ffd580;">setup</span><span>(</span><span style="color:#ffcc66;">opts</span><span>)
</span><span> </span><span style="font-style:italic;color:#5c6773;">-- create our commands
</span><span> vim</span><span style="color:#f29e74;">.</span><span>api</span><span style="color:#f29e74;">.</span><span style="color:#ffd580;">nvim_create_user_command</span><span>(</span><span style="color:#bae67e;">"ZellijNavigateUp"</span><span style="color:#ccc9c2cc;">, </span><span>M</span><span style="color:#f29e74;">.</span><span>up</span><span style="color:#ccc9c2cc;">, </span><span>{})
</span><span> vim</span><span style="color:#f29e74;">.</span><span>api</span><span style="color:#f29e74;">.</span><span style="color:#ffd580;">nvim_create_user_command</span><span>(</span><span style="color:#bae67e;">"ZellijNavigateDown"</span><span style="color:#ccc9c2cc;">, </span><span>M</span><span style="color:#f29e74;">.</span><span>down</span><span style="color:#ccc9c2cc;">, </span><span>{})
</span><span> vim</span><span style="color:#f29e74;">.</span><span>api</span><span style="color:#f29e74;">.</span><span style="color:#ffd580;">nvim_create_user_command</span><span>(</span><span style="color:#bae67e;">"ZellijNavigateLeft"</span><span style="color:#ccc9c2cc;">, </span><span>M</span><span style="color:#f29e74;">.</span><span>left</span><span style="color:#ccc9c2cc;">, </span><span>{})
</span><span> vim</span><span style="color:#f29e74;">.</span><span>api</span><span style="color:#f29e74;">.</span><span style="color:#ffd580;">nvim_create_user_command</span><span>(</span><span style="color:#bae67e;">"ZellijNavigateRight"</span><span style="color:#ccc9c2cc;">, </span><span>M</span><span style="color:#f29e74;">.</span><span>right</span><span style="color:#ccc9c2cc;">, </span><span>{})
</span><span style="color:#ffa759;">end
</span><span>
</span><span style="color:#ffa759;">return </span><span>M
</span></code></pre>
<p>And I can load it like this:</p>
<pre data-lang="lua" style="background-color:#212733;color:#ccc9c2;" class="language-lua "><code class="language-lua" data-lang="lua"><span style="color:#ffa759;">return </span><span>{
</span><span> {
</span><span> </span><span style="color:#bae67e;">"https://git.sr.ht/~swaits/zellij-nav.nvim"</span><span style="color:#ccc9c2cc;">,
</span><span> </span><span style="color:#bae67e;">lazy </span><span style="color:#ccc9c2cc;">= </span><span style="color:#ffcc66;">true</span><span style="color:#ccc9c2cc;">,
</span><span> </span><span style="color:#bae67e;">keys </span><span style="color:#ccc9c2cc;">= </span><span>{
</span><span> { </span><span style="color:#bae67e;">"<c-h>"</span><span style="color:#ccc9c2cc;">, </span><span style="color:#bae67e;">"<cmd>ZellijNavigateLeft<cr>"</span><span style="color:#ccc9c2cc;">, </span><span>{ </span><span style="color:#bae67e;">silent </span><span style="color:#ccc9c2cc;">= </span><span style="color:#ffcc66;">true</span><span style="color:#ccc9c2cc;">, </span><span style="color:#bae67e;">desc </span><span style="color:#ccc9c2cc;">= </span><span style="color:#bae67e;">"navigate left" </span><span>} }</span><span style="color:#ccc9c2cc;">,
</span><span> { </span><span style="color:#bae67e;">"<c-j>"</span><span style="color:#ccc9c2cc;">, </span><span style="color:#bae67e;">"<cmd>ZellijNavigateDown<cr>"</span><span style="color:#ccc9c2cc;">, </span><span>{ </span><span style="color:#bae67e;">silent </span><span style="color:#ccc9c2cc;">= </span><span style="color:#ffcc66;">true</span><span style="color:#ccc9c2cc;">, </span><span style="color:#bae67e;">desc </span><span style="color:#ccc9c2cc;">= </span><span style="color:#bae67e;">"navigate down" </span><span>} }</span><span style="color:#ccc9c2cc;">,
</span><span> { </span><span style="color:#bae67e;">"<c-k>"</span><span style="color:#ccc9c2cc;">, </span><span style="color:#bae67e;">"<cmd>ZellijNavigateUp<cr>"</span><span style="color:#ccc9c2cc;">, </span><span>{ </span><span style="color:#bae67e;">silent </span><span style="color:#ccc9c2cc;">= </span><span style="color:#ffcc66;">true</span><span style="color:#ccc9c2cc;">, </span><span style="color:#bae67e;">desc </span><span style="color:#ccc9c2cc;">= </span><span style="color:#bae67e;">"navigate up" </span><span>} }</span><span style="color:#ccc9c2cc;">,
</span><span> { </span><span style="color:#bae67e;">"<c-l>"</span><span style="color:#ccc9c2cc;">, </span><span style="color:#bae67e;">"<cmd>ZellijNavigateRight<cr>"</span><span style="color:#ccc9c2cc;">, </span><span>{ </span><span style="color:#bae67e;">silent </span><span style="color:#ccc9c2cc;">= </span><span style="color:#ffcc66;">true</span><span style="color:#ccc9c2cc;">, </span><span style="color:#bae67e;">desc </span><span style="color:#ccc9c2cc;">= </span><span style="color:#bae67e;">"navigate right" </span><span>} }</span><span style="color:#ccc9c2cc;">,
</span><span> }</span><span style="color:#ccc9c2cc;">,
</span><span> </span><span style="color:#bae67e;">opts </span><span style="color:#ccc9c2cc;">= </span><span>{}</span><span style="color:#ccc9c2cc;">,
</span><span> }</span><span style="color:#ccc9c2cc;">,
</span><span>}
</span></code></pre>
<p>So satisfying!</p>
<h3 id="scratch-nvim"><a href="https://git.sr.ht/~swaits/scratch.nvim">scratch.nvim</a> <img src="#scratch-nvim" alt="" /></h3>
<p>Finally, a conversion of <a href="https://github.com/duff/vim-scratch">vim-scratch</a>,
which hasn’t been updated in over a decade, to lua.</p>
<p>There are fancier scratch buffer plugins out there. But this one works exactly
the way I like. And now I have it in lua.</p>
<p>This one shows one of the benefits of lua over vimscript – that the
<a href="https://git.sr.ht/~swaits/scratch.nvim/tree/main/item/lua/scratch/init.lua">code</a>
is much more understandable and easier to follow.</p>
<p>I use it like this:</p>
<pre data-lang="lua" style="background-color:#212733;color:#ccc9c2;" class="language-lua "><code class="language-lua" data-lang="lua"><span style="color:#ffa759;">return </span><span>{
</span><span> </span><span style="color:#bae67e;">"https://git.sr.ht/~swaits/scratch.nvim"</span><span style="color:#ccc9c2cc;">,
</span><span> </span><span style="color:#bae67e;">lazy </span><span style="color:#ccc9c2cc;">= </span><span style="color:#ffcc66;">true</span><span style="color:#ccc9c2cc;">,
</span><span> </span><span style="color:#bae67e;">keys </span><span style="color:#ccc9c2cc;">= </span><span>{
</span><span> { </span><span style="color:#bae67e;">"<leader>bs"</span><span style="color:#ccc9c2cc;">, </span><span style="color:#bae67e;">"<cmd>Scratch<cr>"</span><span style="color:#ccc9c2cc;">, </span><span style="color:#bae67e;">desc </span><span style="color:#ccc9c2cc;">= </span><span style="color:#bae67e;">"Scratch Buffer"</span><span style="color:#ccc9c2cc;">, </span><span style="color:#bae67e;">mode </span><span style="color:#ccc9c2cc;">= </span><span style="color:#bae67e;">"n" </span><span>}</span><span style="color:#ccc9c2cc;">,
</span><span> { </span><span style="color:#bae67e;">"<leader>bS"</span><span style="color:#ccc9c2cc;">, </span><span style="color:#bae67e;">"<cmd>ScratchSplit<cr>"</span><span style="color:#ccc9c2cc;">, </span><span style="color:#bae67e;">desc </span><span style="color:#ccc9c2cc;">= </span><span style="color:#bae67e;">"Scratch Buffer (split)"</span><span style="color:#ccc9c2cc;">, </span><span style="color:#bae67e;">mode </span><span style="color:#ccc9c2cc;">= </span><span style="color:#bae67e;">"n" </span><span>}</span><span style="color:#ccc9c2cc;">,
</span><span> }</span><span style="color:#ccc9c2cc;">,
</span><span> </span><span style="color:#bae67e;">cmd </span><span style="color:#ccc9c2cc;">= </span><span>{
</span><span> </span><span style="color:#bae67e;">"Scratch"</span><span style="color:#ccc9c2cc;">,
</span><span> </span><span style="color:#bae67e;">"ScratchSplit"</span><span style="color:#ccc9c2cc;">,
</span><span> }</span><span style="color:#ccc9c2cc;">,
</span><span> </span><span style="color:#bae67e;">opts </span><span style="color:#ccc9c2cc;">= </span><span>{}</span><span style="color:#ccc9c2cc;">,
</span><span>}
</span></code></pre>
<h2 id="closing">Closing</h2>
<p>In the beginning, I said this was fun. That’s so true; it was very fun.</p>
<p>I also said it might seem pointless or even counter-productive. But was it?</p>
<p>I don’t think so.</p>
<p>In the process of doing this, I learned some <code>lua</code>, learned about <code>nvim</code>
plugins, and streamlined my entire coding and writing environment. But those
are <strong>not</strong> the reasons this was a valuable use of my time.</p>
<p>It was valuable because it was fun!</p>
Adding Mermaid.js to Zola2023-06-16T00:00:00+00:002023-06-16T00:00:00+00:00
Unknown
https://swaits.com/adding-mermaid-js-to-zola/<h4 id="contents">Contents</h4>
<!--toc:start-->
<ul>
<li><a href="https://swaits.com/adding-mermaid-js-to-zola/#what-is-mermaid-why-do-i-want-it">What is Mermaid? Why do I want it?</a></li>
<li><a href="https://swaits.com/adding-mermaid-js-to-zola/#add-mermaid-to-your-zola-theme">Add Mermaid to your Zola theme</a></li>
<li><a href="https://swaits.com/adding-mermaid-js-to-zola/#create-a-mermaid-shortcode">Create a Mermaid Shortcode</a></li>
<li><a href="https://swaits.com/adding-mermaid-js-to-zola/#using-the-shortcode">Using the Shortcode</a></li>
<li><a href="https://swaits.com/adding-mermaid-js-to-zola/#going-further">Going Further</a></li>
</ul>
<!--toc:end-->
<h2 id="what-is-mermaid-why-do-i-want-it">What is Mermaid? Why do I want it?</h2>
<p>According to <a href="https://github.com/mermaid-js/mermaid">the mermaid-js repo</a>:</p>
<blockquote>
<p>Mermaid is a JavaScript-based diagramming and charting tool that uses Markdown-inspired text definitions and a renderer to create and modify complex diagrams. The main purpose of Mermaid is to help documentation catch up with development.</p>
</blockquote>
<p>I don’t have a good reason to want it.
I think it’s neat.
That’s enough for me.</p>
<p>I use <a href="https://www.getzola.org/">Zola</a> for this blog’s static site generation.
I thought it would be nice to make using Mermaid more convenient to use directly in my Markdown content.
This isn’t really necessary for this simple example, but it demonstrates how Shortcodes work in Zola.</p>
<p>You can see it in action on my <a href="/about">about</a> page.
I used it to create these goofy timelines of where I’ve worked and lived.
It serves no real purpose.
It’s just neat.</p>
<p>Head over to <a href="https://mermaid.live/">mermaid.live</a> if you want to play around with it. It’s fun!</p>
<h2 id="add-mermaid-to-your-zola-theme">Add Mermaid to your Zola theme</h2>
<p>First you need to install the Mermaid package.
If you’re cool with a CDN, you just need this at the end of your <code><body></code>:</p>
<pre data-lang="html" style="background-color:#212733;color:#ccc9c2;" class="language-html "><code class="language-html" data-lang="html"><span style="color:#5ccfe690;"><</span><span style="color:#73d0ff;">body</span><span style="color:#5ccfe690;">>
</span><span> ... all your body content here ...
</span><span> </span><span style="color:#5ccfe690;"><</span><span style="color:#73d0ff;">script </span><span style="color:#ffd580;">type</span><span style="color:#ccc9c2cc;">=</span><span style="color:#bae67e;">"module"</span><span style="color:#5ccfe690;">>
</span><span> </span><span style="color:#ffa759;">import </span><span>mermaid </span><span style="color:#ffa759;">from </span><span style="color:#bae67e;">'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs'</span><span style="color:#ccc9c2cc;">;
</span><span> mermaid</span><span style="color:#f29e74;">.</span><span style="color:#ffd580;">initialize</span><span>({ startOnLoad</span><span style="color:#ccc9c2cc;">: </span><span style="color:#ffcc66;">true </span><span>})</span><span style="color:#ccc9c2cc;">;
</span><span> </span><span style="color:#5ccfe690;"></</span><span style="color:#73d0ff;">script</span><span style="color:#5ccfe690;">>
</span><span style="color:#5ccfe690;"></</span><span style="color:#73d0ff;">body</span><span style="color:#5ccfe690;">>
</span></code></pre>
<p>If you want to deploy it instead of using the CDN, follow the <a href="https://mermaid.js.org/intro/#installation">well documented installation instructions</a>.</p>
<p>To add this to my blog’s Zola template, I added it into my <code>templates/index.html</code>.
Your template may differ.</p>
<h2 id="create-a-mermaid-shortcode">Create a Mermaid Shortcode</h2>
<p>Shortcodes are an awesome way to make using things like this super convenient to use directly in your Markdown.</p>
<p>To add one for Mermaid, I created a file named <code>templates/shortcodes/mermaid.html</code> with this content:</p>
<pre data-lang="html" style="background-color:#212733;color:#ccc9c2;" class="language-html "><code class="language-html" data-lang="html"><span style="color:#5ccfe690;"><</span><span style="color:#73d0ff;">pre </span><span style="color:#ffd580;">class</span><span style="color:#ccc9c2cc;">=</span><span style="color:#bae67e;">"mermaid"</span><span style="color:#5ccfe690;">>
</span><span>{{ body }}
</span><span style="color:#5ccfe690;"></</span><span style="color:#73d0ff;">pre</span><span style="color:#5ccfe690;">>
</span></code></pre>
<p>I mean, there’s really not much to this. 😀</p>
<h2 id="using-the-shortcode">Using the Shortcode</h2>
<p>Now, when I want a diagram, I use the shortcode from my Markdown content like this:</p>
<pre data-lang="markdown" style="background-color:#212733;color:#ccc9c2;" class="language-markdown "><code class="language-markdown" data-lang="markdown"><span>{% mermaid() %}
</span><span>graph TD
</span><span> A[Enter Chart Definition] --> B(Preview)
</span><span> B --> C{decide}
</span><span> C --> D[Keep]
</span><span> C --> E[Edit Definition]
</span><span> E --> B
</span><span> D --> F[Save Image and Code]
</span><span> F --> B
</span><span>{% end %}
</span></code></pre>
<p>Which should end up looking like this:</p>
<pre class="mermaid">
graph TD
A[Enter Chart Definition] --> B(Preview)
B --> C{decide}
C --> D[Keep]
C --> E[Edit Definition]
E --> B
D --> F[Save Image and Code]
F --> B
</pre>
<h2 id="going-further">Going Further</h2>
<p>As I said above, this is a super simple example.
Shortcodes can do much more than this.
Check out <a href="https://www.getzola.org/documentation/content/shortcodes/">the documentation</a> to learn more.</p>
My wife started a business!2023-06-15T00:00:00+00:002023-06-15T00:00:00+00:00
Unknown
https://swaits.com/announcing-suncrest-flowers/<p>This is just a quick note to describe what we’ve been up to lately.
My wife launched <a href="https://suncrestflowers.com/">Suncrest Flowers</a>!
It’s been a bit of a journey.
I’ll share some of that here.</p>
<p>It’s the American dream, right?
You take your passion, create a business out of it, survive, then thrive!
But it’s not always so straightforward.</p>
<p>Some of the things we’ve been through to get here:</p>
<ol>
<li>Register with the IRS to get an E-IN, which is a taxpayer identification number for businesses in the same way individuals have SSNs.</li>
<li>Register DBAs in support of multiple customer segments and brands we want to offer.</li>
<li>Get a business license within our municipality (in our case, the city of Draper, UT).</li>
<li>Build out an entire e-commerce site. We chose Shopify for many reasons.</li>
<li>Figure out the whole photography studio thing.</li>
<li>Set up a business bank account so we can keep the finances separate from our personal stuff.</li>
<li>Get a business credit card.</li>
<li>Get a state tax ID so that we could …</li>
<li>… open accounts with local wholesalers (i.e. to defer sales tax).</li>
<li>Begin filing and paying state taxes.</li>
<li>Tons and tons of guerilla/social media marketing.</li>
<li>Design and print marketing and other needed printed materials.</li>
<li>Sign up for local farmers’ markets and then go to them - tons of work and prep here.</li>
<li>Set up an entire basement as a legit flower shop.</li>
<li><em>And what feels like 100 other things I’m not listing here.</em></li>
</ol>
<p>In short, a ton of work!</p>
<p>The American dream is definitely within reach.
But don’t fool yourself.
It’s takes blood, sweat, and tears to get it done.</p>
<p>But, my wife is a super-talented artist.
Her work is amazing.
I think, like many artists, she doesn’t realize how good she is at this kind of thing.
I’m glad she’s putting her stuff out there.
So far, customers are ecstatic with the results!</p>
<p>If you need artistically arranged, premium flowers delivered in Salt Lake County or Utah County, please swing by her shop at <a href="https://suncrestflowers.com">suncrestflowers.com</a>.</p>
<p>Until then, here are a few samples of her work:</p>
<p><img src="https://swaits.com/announcing-suncrest-flowers/flowers-2.jpeg" alt="flower arrangement" /></p>
<p><img src="https://swaits.com/announcing-suncrest-flowers/flowers-1.jpeg" alt="flower arrangement" /></p>
<p><img src="https://swaits.com/announcing-suncrest-flowers/flowers-3.jpeg" alt="flower arrangement" /></p>
Rust Type System: Making complex things simple.2023-03-20T00:00:00+00:002023-03-20T00:00:00+00:00
Unknown
https://swaits.com/beautiful-experience-with-rust-static-type-system/<p>I want to tell you about a delightful experience I had recently, thanks to the Rust type system.</p>
<p>Not too long ago, I set out to write some physics code for <code>$FUN</code> (i.e. not <code>$WORK</code>).
This is a story about my discovery and use of the <a href="https://crates.io/crates/uom">uom</a> crate, a library that leverages the Rust type system to make this kind of physics coding incredibly easy.</p>
<h4 id="contents">Contents</h4>
<!--toc:start-->
<ul>
<li><a href="https://swaits.com/beautiful-experience-with-rust-static-type-system/#dimensional-analysis">Dimensional Analysis</a></li>
<li><a href="https://swaits.com/beautiful-experience-with-rust-static-type-system/#hello-uom-crate-so-nice-to-meet-you">Hello <code>uom</code> crate, so nice to meet you!</a>
<ul>
<li><a href="https://swaits.com/beautiful-experience-with-rust-static-type-system/#uom-is-type-safe"><code>uom</code> is Type-Safe</a></li>
<li><a href="https://swaits.com/beautiful-experience-with-rust-static-type-system/#uom-is-automatic"><code>uom</code> is Automatic</a></li>
<li><a href="https://swaits.com/beautiful-experience-with-rust-static-type-system/#uom-is-zero-cost"><code>uom</code> is Zero-Cost</a></li>
<li><a href="https://swaits.com/beautiful-experience-with-rust-static-type-system/#uom-is-extensibile"><code>uom</code> is Extensibile</a></li>
</ul>
</li>
<li><a href="https://swaits.com/beautiful-experience-with-rust-static-type-system/#summary">Summary</a></li>
</ul>
<!--toc:end-->
<h2 id="dimensional-analysis">Dimensional Analysis</h2>
<p>As you probably know, physics calculations usually involve <a href="https://en.wikipedia.org/wiki/Dimensional_analysis">dimensional analysis</a>.
But in case you don’t know that, I’ll explain a bit about it.</p>
<p>Dimensional analysis is a powerful technique used in physics, engineering, and other scientific disciplines to verify the correctness of equations, convert between units, and solve problems. It involves analyzing the units of measurement in a mathematical expression to ensure that they are consistent and make sense. The fundamental idea is that units must be consistent on both sides of an equation, allowing for the cancellation or combination of units to ensure a coherent result.</p>
<p>Let’s look at an example of dimensional analysis.
Consider the equation for calculating the distance an object has traveled, given its initial velocity, acceleration, and the time it has been in motion:</p>
<figure>
$$
distance = initial \text{\textunderscore} velocity \cdot time + \frac{1}{2} \cdot acceleration \cdot time^2
$$
</figure>
<p>To perform dimensional analysis, we examine the units involved:</p>
<ol>
<li>\( distance \) is measured in units of length, \( L \) (e.g., meters or feet).</li>
<li>\( initial \text{\textunderscore} velocity \) is measured in units of length per unit of time, \( \frac{L}{T} \) (e.g., meters per second or feet per second).</li>
<li>\( time \) is measured in units of time, \( T \) (e.g., seconds or minutes).</li>
<li>\( acceleration \) is measured in units of length per unit of time squared, \( \frac{L}{T^2} \) (e.g., meters per second squared or feet per second squared).</li>
</ol>
<p>First, let’s translate the distance equation above into units:</p>
<figure>
$$ L = \frac{L}{T} \cdot T + \frac{1}{2} \cdot \frac{L}{T^2} \cdot T^2 $$
</figure>
<p>Now, let’s check the units on both sides of the equation, multiplying or dividing accordingly:</p>
<figure>
$$ \mathellipsis = \frac{L \cdot T}{T} + \frac{L \cdot T^2}{2 \cdot T^2} = L + \frac{L}{2} $$
</figure>
<p>Finally resulting in:</p>
<figure>
$$ L = L + \frac{L}{2} $$
</figure>
<p>Remember that we are trying to compute distance here, so we expected our result in units of length, \( L \), which you can see is exactly what we ended up with.
The units are consistent on both sides, confirming that the equation is dimensionally correct.</p>
<p><em>Note: I’ve been calling these things \( L \) and \( T \) “units”; however it’d be more right to call them “quantities”. I’m just using “units” here for the sake of simplicity. From here on, I’ll use the term “quantities” and “units” more appropriately.</em></p>
<p>Now let’s consider another simple example that brings actual units into the picture, instead of focusing on just quantities as in the example above.
If you know that a Newton (unit of force) is defined as \( \frac{kg \cdot m}{s^2} \), it’s plain to see that multiplying mass in \( kg \) by acceleration in \( \frac{m}{s^2} \) gives you force:</p>
<figure>
$$ F = m \cdot a = kg \cdot \frac{m}{s^2} = \frac{kg \cdot m}{s^2} = N $$
</figure>
<p>Now, that’s a simple example.
Most will remember that \( F = m \cdot a \) from high-school physics.
But, if for some reason you forgot that, or you just wanted to confirm that you’re getting your units right, this is a simple way to help you remember the equation and to verify your units are coherent.</p>
<p>While these examples are simplistic, anyone who has done a fair amount of dynamics, physics, or other engineering coding will tell you that dimensional analysis is a helpful tool in getting things right.</p>
<h2 id="hello-uom-crate-so-nice-to-meet-you">Hello <code>uom</code> crate, so nice to meet you!</h2>
<p>I didn’t set out to find a Rust crate that did full on dimensional analysis.
I just searched <a href="https://crates.io">crates.io</a> for libraries to help me represent my units.
But when I found <a href="https://crates.io/crates/uom">uom</a>, I realized I discovered something special.
The crate description says:</p>
<blockquote>
<p>Units of measurement is a crate that does automatic type-safe zero-cost
<a href="https://en.wikipedia.org/wiki/Dimensional_analysis">dimensional analysis</a>. You can create your own systems or use the pre-built
<a href="https://jcgm.bipm.org/vim/en/1.16.html">International System of Units</a> (SI) which is based on the
<a href="https://jcgm.bipm.org/vim/en/1.6.html">International System of Quantities</a> (ISQ) and includes numerous <a href="https://jcgm.bipm.org/vim/en/1.1.html">quantities</a>
(length, mass, time, …) with conversion factors for even more numerous
<a href="https://jcgm.bipm.org/vim/en/1.9.html">measurement units</a> (meter, kilometer, foot, mile, …). No more crashing your
<a href="https://en.wikipedia.org/wiki/Mars_Climate_Orbiter">climate orbiter</a>!</p>
</blockquote>
<p>Did you catch that? It says <strong>type-safe</strong>, <strong>automatic</strong>, and <strong>zero-cost</strong> dimensional analysis.
Let’s dive into each of those.</p>
<h3 id="uom-is-type-safe"><code>uom</code> is Type-Safe</h3>
<p>So what makes <a href="https://crates.io/crates/uom">uom</a> type-safe?
In short, it will not let you use incorrect units or quantities to arrive at a specific desired quantity and unit.</p>
<p>Let’s look at a few examples to demonstrate this.
Consider this code, which is trying to add quantities of length, \( L \) and time, \( T \):</p>
<pre data-lang="rust" style="background-color:#212733;color:#ccc9c2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#ffa759;">use </span><span>uom</span><span style="color:#f29e74;">::</span><span>si</span><span style="color:#f29e74;">::</span><span>f64</span><span style="color:#f29e74;">::*</span><span style="color:#ccc9c2cc;">;
</span><span style="color:#ffa759;">use </span><span>uom</span><span style="color:#f29e74;">::</span><span>si</span><span style="color:#f29e74;">::</span><span>length</span><span style="color:#f29e74;">::</span><span>kilometer</span><span style="color:#ccc9c2cc;">;
</span><span style="color:#ffa759;">use </span><span>uom</span><span style="color:#f29e74;">::</span><span>si</span><span style="color:#f29e74;">::</span><span>time</span><span style="color:#f29e74;">::</span><span>second</span><span style="color:#ccc9c2cc;">;
</span><span>
</span><span style="color:#ffa759;">fn </span><span style="color:#ffd580;">main</span><span>() {
</span><span> </span><span style="color:#ffa759;">let</span><span> length </span><span style="color:#f29e74;">= </span><span>Length</span><span style="color:#f29e74;">::</span><span>new</span><span style="color:#f29e74;">::</span><span><kilometer>(</span><span style="color:#ffcc66;">5.0</span><span>)</span><span style="color:#ccc9c2cc;">;
</span><span> </span><span style="color:#ffa759;">let</span><span> time </span><span style="color:#f29e74;">= </span><span>Time</span><span style="color:#f29e74;">::</span><span>new</span><span style="color:#f29e74;">::</span><span><second>(</span><span style="color:#ffcc66;">15.0</span><span>)</span><span style="color:#ccc9c2cc;">;
</span><span> </span><span style="color:#ffa759;">let</span><span> something </span><span style="color:#f29e74;">=</span><span> length </span><span style="color:#f29e74;">+</span><span> time</span><span style="color:#ccc9c2cc;">; </span><span style="font-style:italic;color:#5c6773;">// error[E0308]: mismatched types
</span><span>}
</span></code></pre>
<p>Since adding length and time is nonsensical, <a href="https://crates.io/crates/uom">uom</a> disallows this by not providing type compatibility between the two.
This code results in the following compiler error:</p>
<pre style="background-color:#212733;color:#ccc9c2;"><code><span>error[E0308]: mismatched types
</span><span> --> src/main.rs:8:30
</span><span> |
</span><span>8 | let something = length + time; // error[E0308]: mismatched types
</span><span> | ^^^^ expected struct `PInt`, found struct `Z0`
</span><span> |
</span><span> = note: expected struct `Quantity<dyn Dimension<L = PInt<UInt<UTerm, B1>>, Th = Z0, Kind = (dyn Kind + 'static), N = Z0, M = Z0, J = Z0, I = Z0, T = Z0>, _, _>`
</span><span> found struct `Quantity<dyn Dimension<L = Z0, Th = Z0, Kind = ..., N = ..., M = ..., J = ..., I = ..., T = ...>, ..., ...>`
</span><span> the full type name has been written to '/Users/swaits/tmp/uomtest/target/debug/deps/uomtest-9f527559ddacdf5d.long-type-3383730111220641861.txt'
</span></code></pre>
<p>Another example:
As discussed earlier, imagine we want to compute force.
But, suppose we botched the input quantities completely, using velocity and time instead of mass and acceleration.
Here’s a function that takes velocity and time and tries to multiply them to <em>incorrectly</em> arrive at a force:</p>
<pre data-lang="rust" style="background-color:#212733;color:#ccc9c2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#ffa759;">fn </span><span style="color:#ffd580;">calc_force</span><span>(</span><span style="color:#ffcc66;">velocity</span><span style="color:#ccc9c2cc;">:</span><span> Velocity, </span><span style="color:#ffcc66;">time</span><span style="color:#ccc9c2cc;">:</span><span> Time) </span><span style="color:#ccc9c2cc;">-></span><span> Force {
</span><span> velocity </span><span style="color:#f29e74;">*</span><span> time </span><span style="font-style:italic;color:#5c6773;">// error[E0308]: mismatched types
</span><span>}
</span></code></pre>
<p>Of course, when we try this, we’re using incompatible types.
Our function must return a <code>Force</code>, which you cannot get by multiplying velocity and time.
And compiling with <a href="https://crates.io/crates/uom">uom</a> results in this error:</p>
<pre style="background-color:#212733;color:#ccc9c2;"><code><span>error[E0308]: mismatched types
</span><span> --> src/main.rs:6:5
</span><span> |
</span><span>5 | fn calc_force(velocity: Velocity, time: Time) -> Force {
</span><span> | ----- expected `Quantity<(dyn Dimension<L = PInt<UInt<UTerm, B1>>, Th = Z0, Kind = (dyn Kind + 'static), N = Z0, M = PInt<UInt<UTerm, B1>>, J = Z0, I = Z0, T = NInt<UInt<UInt<UTerm, B1>, B0>>> + 'static), (dyn uom::si::Units<f64, thermodynamic_temperature = uom::si::thermodynamic_temperature::kelvin, electric_current = uom::si::electric_current::ampere, amount_of_substance = uom::si::amount_of_substance::mole, time = uom::si::time::second, mass = uom::si::mass::kilogram, length = uom::si::length::meter, luminous_intensity = uom::si::luminous_intensity::candela> + 'static), f64>` because of return type
</span><span>6 | velocity * time
</span><span> | ^^^^^^^^^^^^^^^ expected struct `PInt`, found struct `Z0`
</span><span> |
</span><span> = note: expected struct `Quantity<(dyn Dimension<L = ..., Th = ..., Kind = ..., N = ..., M = ..., J = ..., I = ..., T = ...> + 'static), ..., ...>` (struct `PInt`)
</span><span> the full type name has been written to '/Users/swaits/tmp/uomtest/target/debug/deps/uomtest-9f527559ddacdf5d.long-type-8652923195758179394.txt'
</span><span> found struct `Quantity<(dyn Dimension<L = ..., Th = ..., Kind = ..., N = ..., M = ..., J = ..., I = ..., T = ...> + 'static), ..., ...>` (struct `Z0`)
</span><span> the full type name has been written to '/Users/swaits/tmp/uomtest/target/debug/deps/uomtest-9f527559ddacdf5d.long-type-10745726420672192385.txt'
</span></code></pre>
<p>If we correct our function as follows, it compiles fine.</p>
<pre data-lang="rust" style="background-color:#212733;color:#ccc9c2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#ffa759;">fn </span><span style="color:#ffd580;">calc_force</span><span>(</span><span style="color:#ffcc66;">mass</span><span style="color:#ccc9c2cc;">:</span><span> Mass, </span><span style="color:#ffcc66;">acceleration</span><span style="color:#ccc9c2cc;">:</span><span> Acceleration) </span><span style="color:#ccc9c2cc;">-></span><span> Force {
</span><span> mass </span><span style="color:#f29e74;">*</span><span> acceleration
</span><span>}
</span></code></pre>
<p>And because it compiles, we can be confident we’ve got the quantities and equations correct!</p>
<p>This is as <em>magical</em> as it gets.</p>
<h3 id="uom-is-automatic"><code>uom</code> is Automatic</h3>
<p>By now you probably noticed that our function <code>calc_force()</code> is working with quantities like mass and acceleration.
In other words, it’s not concerned with units like kilograms, slugs, feet per second squared, or meters per second squared.</p>
<p>For example, suppose we need force in Newtons.
As we showed above, a Newton is defined in units of \( \frac{kg \cdot m}{s^2} \).
With <a href="https://crates.io/crates/uom">uom</a>, we don’t really care about that.
We can use any units as long as they’re acceptable by the specified quantities.
Here’s an example using slugs (for mass) and feet per second squared (for acceleration) to compute force in Newtons.</p>
<pre data-lang="rust" style="background-color:#212733;color:#ccc9c2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#ffa759;">use </span><span>uom</span><span style="color:#f29e74;">::</span><span>fmt</span><span style="color:#f29e74;">::</span><span>DisplayStyle</span><span style="color:#f29e74;">::</span><span>Abbreviation</span><span style="color:#ccc9c2cc;">;
</span><span style="color:#ffa759;">use </span><span>uom</span><span style="color:#f29e74;">::</span><span>si</span><span style="color:#f29e74;">::</span><span>f64</span><span style="color:#f29e74;">::*</span><span style="color:#ccc9c2cc;">;
</span><span style="color:#ffa759;">use </span><span>uom</span><span style="color:#f29e74;">::</span><span>si</span><span style="color:#f29e74;">::</span><span>{acceleration</span><span style="color:#f29e74;">::</span><span>foot_per_second_squared</span><span style="color:#ccc9c2cc;">, </span><span>force</span><span style="color:#f29e74;">::</span><span>newton</span><span style="color:#ccc9c2cc;">, </span><span>mass</span><span style="color:#f29e74;">::</span><span>slug}</span><span style="color:#ccc9c2cc;">;
</span><span>
</span><span style="color:#ffa759;">fn </span><span style="color:#ffd580;">calc_force</span><span>(</span><span style="color:#ffcc66;">mass</span><span style="color:#ccc9c2cc;">:</span><span> Mass, </span><span style="color:#ffcc66;">acceleration</span><span style="color:#ccc9c2cc;">:</span><span> Acceleration) </span><span style="color:#ccc9c2cc;">-></span><span> Force {
</span><span> mass </span><span style="color:#f29e74;">*</span><span> acceleration
</span><span>}
</span><span>
</span><span style="color:#ffa759;">fn </span><span style="color:#ffd580;">main</span><span>() {
</span><span> </span><span style="color:#ffa759;">let</span><span> mass </span><span style="color:#f29e74;">= </span><span>Mass</span><span style="color:#f29e74;">::</span><span>new</span><span style="color:#f29e74;">::</span><span><slug>(</span><span style="color:#ffcc66;">5.0</span><span>)</span><span style="color:#ccc9c2cc;">;
</span><span> </span><span style="color:#ffa759;">let</span><span> acceleration </span><span style="color:#f29e74;">= </span><span>Acceleration</span><span style="color:#f29e74;">::</span><span>new</span><span style="color:#f29e74;">::</span><span><foot_per_second_squared>(</span><span style="color:#ffcc66;">5.0</span><span>)</span><span style="color:#ccc9c2cc;">;
</span><span> </span><span style="color:#ffa759;">let</span><span> force </span><span style="color:#f29e74;">= </span><span style="color:#f28779;">calc_force</span><span>(mass</span><span style="color:#ccc9c2cc;">,</span><span> acceleration)</span><span style="color:#ccc9c2cc;">;
</span><span>
</span><span> </span><span style="color:#f28779;">println!</span><span>(</span><span style="color:#bae67e;">"Force = </span><span style="color:#ffcc66;">{}</span><span style="color:#bae67e;">"</span><span style="color:#ccc9c2cc;">,</span><span> force</span><span style="color:#f29e74;">.</span><span style="color:#f28779;">into_format_args</span><span>(newton</span><span style="color:#ccc9c2cc;">,</span><span> Abbreviation))</span><span style="color:#ccc9c2cc;">;
</span><span>}
</span></code></pre>
<p>Here we’re using slugs and feet per second squared.
And, we still get Newtons in the output:</p>
<pre style="background-color:#212733;color:#ccc9c2;"><code><span>> cargo run
</span><span>
</span><span>Force = 111.205518 N
</span></code></pre>
<p>We didn’t care how a Newton is defined.
It didn’t matter that our input units were unusual and different from our final result of Newtons.
We just stuck to the right quantities, used the units we wanted, and <a href="https://crates.io/crates/uom">uom</a> figured out the rest.</p>
<p>What if we didn’t want force in Newtons?
Maybe we want pounds force or dynes?
We’d just ask <a href="https://crates.io/crates/uom">uom</a> to do the conversions for us like this:</p>
<pre data-lang="rust" style="background-color:#212733;color:#ccc9c2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f28779;">println!</span><span>(</span><span style="color:#bae67e;">"Force = </span><span style="color:#ffcc66;">{}</span><span style="color:#bae67e;">"</span><span style="color:#ccc9c2cc;">,</span><span> force</span><span style="color:#f29e74;">.</span><span style="color:#f28779;">into_format_args</span><span>(pound_force</span><span style="color:#ccc9c2cc;">,</span><span> Abbreviation))</span><span style="color:#ccc9c2cc;">;
</span><span style="color:#f28779;">println!</span><span>(</span><span style="color:#bae67e;">"Force = </span><span style="color:#ffcc66;">{}</span><span style="color:#bae67e;">"</span><span style="color:#ccc9c2cc;">,</span><span> force</span><span style="color:#f29e74;">.</span><span style="color:#f28779;">into_format_args</span><span>(dyne</span><span style="color:#ccc9c2cc;">,</span><span> Abbreviation))</span><span style="color:#ccc9c2cc;">;
</span></code></pre>
<p>And here’s the new output:</p>
<pre style="background-color:#212733;color:#ccc9c2;"><code><span>Force = 111.205518 N
</span><span>Force = 24.99999280611444 lbf
</span><span>Force = 11120551.799999999 dyn
</span></code></pre>
<p>With <a href="https://crates.io/crates/uom">uom</a>, we don’t care about base units because it’s doing that automatically.</p>
<p>This is as <em>simple</em> as it gets.</p>
<h3 id="uom-is-zero-cost"><code>uom</code> is Zero-Cost</h3>
<p>Look at what I’ve shown you so far in this post.
Nearly every single thing we’ve talked about has happened at compile-time, not at runtime.
That includes both checking compatible units and unit conversion of constants.
Hence, using <a href="https://crates.io/crates/uom">uom</a> adds zero-cost to your runtime execution.</p>
<p>Now, conversions of anything other than constants will happen dynamically at runtime.
But, they’re distilled down to a single multiplication.</p>
<p>That’s as <em>cheap</em> as it gets.</p>
<h3 id="uom-is-extensibile"><code>uom</code> is Extensibile</h3>
<p><a href="https://crates.io/crates/uom">uom</a> comes with an enormous amount of quantities and units already supported.
Want to see for yourself?
Check out the <a href="https://github.com/iliekturtles/uom/tree/master/src/si">source code here</a>.</p>
<p>But suppose there’s a quantity you need that <a href="https://crates.io/crates/uom">uom</a> doesn’t already support?
The library makes it pretty easy for you to define your own quantities.</p>
<p>In my dynamics coding exercise, I needed a quantity for <a href="https://en.wikipedia.org/wiki/Power-to-weight_ratio">specific power</a>, also known as power-to-weight ratio.
If you know me, you might guess this was to calculate \( \frac{W}{kg} \), a metric frequently used in the cycling world to normalize a cyclist’s performace by his or her mass.</p>
<p><a href="https://crates.io/crates/uom">uom</a> didn’t support specific power.
But I was able to easily add it.
For details on how I did it, see <a href="https://github.com/iliekturtles/uom/pull/396">this pull request</a>, which is now merged.</p>
<h2 id="summary">Summary</h2>
<p>When it comes to physics, science, and engineering programming, many of us have wasted countless hours hunting down bugs that stemmed from the use of incorrect or incompatible units.
Through the power of the Rust type system, <a href="https://crates.io/crates/uom">uom</a> eliminates most of this pain by guaranteeing, at compile-time, you’re using the right quantities and compatible units.</p>
<p>And because it does base unit conversion automatically, it lets the programmer focus on quantities instead of specific units.
Remembering that \( F = m \cdot a \) is much easier than remembering the specific units needed, \( kg \) and \( \frac{m}{s^2} \).</p>
<p>Discovering and using this crate was a magical experience for me.
I was able to focus on the higher level problems I was trying to solve instead of the nitty-gritty of specific units.
When I got things wrong, it simply didn’t compile, meaning it never escaped as a bug.
I am confident that I arrived at correct answers much more quickly than if I hadn’t had something like <a href="https://crates.io/crates/uom">uom</a> helping me so much.</p>
<p>This speaks volumes about the power of Rust’s type system and how well <a href="https://crates.io/crates/uom">uom</a> is designed to take advantage of it.
Ultimately, it saved me time and increased my productivity, helped me ensure correctness and eliminate large classes of bugs, and made the whole exercise a ton of fun!</p>
Aim for the Median: Overcoming Impostor Syndrome2023-03-15T00:00:00+00:002023-03-15T00:00:00+00:00
Unknown
https://swaits.com/be-better-than-median/<p>When I left <a href="https://en.wikipedia.org/wiki/Stormfront_Studios">Stormfront Studios</a> way back in the year 2000 to join <a href="https://en.wikipedia.org/wiki/San_Diego_Studio">PlayStation’s San Diego Studio</a>, one of the best known game companies in the world, I was filled with excitement and apprehension. I couldn’t believe my fortune; I really felt I landed in the big league! Along with that came the fear of not living up to my teammates’ standards, which led me to a severe case of impostor syndrome. Did I really deserve to be here, I wondered?</p>
<p>As I thought about it more, I realized I was setting myself up for failure. I couldn’t continue in that mental state and expect to do well. I needed to set a realistic goal for myself. One day I thought, “relax, just be better than the median and you’ll be fine.” It wasn’t until I aimed for a modest yet effective performance goal — performing a little better than the median — that I started to feel more at ease and focused on doing the best work I could. This approach, which I later encountered again as “raising the bar” in Amazon lingo, offered a practical and achievable way to challenge myself without feeling overwhelmed.</p>
<p>The idea of “just being better than the median” means performing at the median level of your peers and then striving to improve just a bit more. This modest performance goal helps you focus on your own work without fear of failing to reach some probably-unachievable rock star level. Being in the upper half of performers makes you less likely to face layoffs and more likely to be considered for promotions. It’s important to remember that this isn’t about outperforming everyone or encouraging a competitive work environment; it’s about finding a sustainable performance level that allows you, as an individual, to excel.</p>
<p>To adopt this mindset, focus on personal growth, and observe the performance levels around you. Learn from your colleagues and engage in open conversations with your manager about your performance. Seek guidance on areas that need improvement by explicitly asking for critical feedback. This approach is about understanding the expectations within your team and pushing yourself to exceed them without succumbing to the pressure of perfectionism.</p>
<p>In conclusion, aiming just above the median is a powerful way to find your performance zone, overcome impostor syndrome, and make a meaningful impact in your field. By embracing this, you can set achievable goals, raise the bar, and grow in your career. It’s not about competition or outshining everyone; it’s about finding the right balance that propels you towards success while maintaining a healthy and sustainable work-life balance.</p>
Intentionally Dispassionate: A Mental Health Survival Tool2023-03-10T00:00:00+00:002023-03-10T00:00:00+00:00
Unknown
https://swaits.com/dispassion-as-a-survival-tool/<p>Historically, the tech industry can normally be a high-stress environment for
many. However, in the past six months it feels like it’s gone ballistic with
layoffs and RTO mandates causing widespread uncertainty and anxiety. Even
though I’ve been fortunate to not get directly impacted in the midst of all
this chaos, I found myself struggling to manage my mental health, to focus on
work, to feel productive, and to find my usual level intrinsic motivation and
get stuff done.</p>
<p>While talking about the impact on my mental well-being with my friend and
colleague <a href="https://www.linkedin.com/in/spencer-voorheis/">Spencer Voorheis</a>, he
sympathized with me and said he’s been feeling the same way. In fact, just
about everyone I talk to feels like this to some degree. Spencer went on to say
he felt like being dispassionate seemed like one of the ways we could feel
better.</p>
<p>So, I tried it. I turned off all information channels (slack, web, etc)
containing work and tech industry drama. I felt better almost instantly. I was
able to conserve my emotional energy, stay objective, and improve focus. By not
getting too emotionally invested in every little detail, I was able to approach
situations with a clearer mind and feel more productive. This doesn’t mean that
I don’t care about the things happening around me. It simply means that I am
being mindful of my emotional energy and choosing to focus on what I can
control.</p>
<p>If you’re feeling overwhelmed or stressed out, I encourage you to consider
trying intentional dispassion as a way to manage your emotions and improve your
mental health. It can be challenging at first, but the benefits were
significant for me. I think that by being mindful of our emotions and choosing
not to get too caught up in drama and conflict, we might conserve our emotional
energy, stay more objective, and maintain a better sense of perspective. This
could ultimately help us feel more productive, effective, and centered in our
day-to-day lives, both at work and at home.</p>
Why I returned to Amazon2022-08-29T00:00:00+00:002022-08-29T00:00:00+00:00
Unknown
https://swaits.com/why-i-came-back-to-amazon/<p>I started working at Amazon as a Principal Engineer on September 6, 2016.
During that time, I worked in Amazon groceries, including Fresh, Prime Now, and Whole Foods; and later in Amazon Fashion.
I loved it and had no plans to leave.</p>
<p>But in late 2021, I got an email from a recruiter for a role at <a href="https://zwift.com/">Zwift</a>.
That really piqued my interest.
After lots of deliberation, I decided.
I <a href="/incredible-amazon-experience">left Amazon</a> and <a href="/starting-a-new-adventure">started at Zwift</a> on November 29, 2021.
I worked there for just about six months, then realized, for personal reasons, that it wasn’t a good long-term fit for me.
I resigned and then rejoined Amazon on June 8, 2022.
Now I’m working on the Operations and Robotics team, learning lots in a super ambiguous space, and loving it.</p>
<p>Before going back to Amazon I considered other options in <em>big tech</em>.
I even began interviewing at non-Amazon places.
But ultimately, Amazon felt like the right choice to me for many reasons.</p>
<p>Culture was the single biggest reason I chose Amazon over others.
Amazon has a culture I already understand, I trust, and ultimately find precious.
I have a good idea how to succeed at Amazon, and that familiarity felt comfortable and enticing.
To a lesser degree, the stability of the company pulled me in.
Amazon is well diversified and should be able to weather just about any economic uncertainty in the coming years.
As I get deeper into my grey years, stability is important to me.
Finally, the growth opportunities in front of me sealed the deal.
I could probably grow in lots of different roles.
But I feel like the talented people I’m surrounded with at Amazon, and even the opportunity to work directly with people I consider industry luminaries, is a gift I have to take advantage of.</p>
<h4 id="contents">Contents</h4>
<!--toc:start-->
<ul>
<li><a href="https://swaits.com/why-i-came-back-to-amazon/#reason-one-culture">Reason one: Culture</a>
<ul>
<li><a href="https://swaits.com/why-i-came-back-to-amazon/#customer-obsession">Customer Obsession</a></li>
<li><a href="https://swaits.com/why-i-came-back-to-amazon/#ownership">Ownership</a></li>
<li><a href="https://swaits.com/why-i-came-back-to-amazon/#are-right-a-lot">Are Right, A Lot</a></li>
<li><a href="https://swaits.com/why-i-came-back-to-amazon/#hire-and-develop-the-best">Hire and Develop the Best</a></li>
<li><a href="https://swaits.com/why-i-came-back-to-amazon/#insist-on-the-highest-standards">Insist on the Highest Standards</a></li>
<li><a href="https://swaits.com/why-i-came-back-to-amazon/#bias-for-action">Bias for Action</a></li>
<li><a href="https://swaits.com/why-i-came-back-to-amazon/#earn-trust">Earn Trust</a></li>
<li><a href="https://swaits.com/why-i-came-back-to-amazon/#have-backbone-disagree-and-commit">Have Backbone; Disagree and Commit</a></li>
<li><a href="https://swaits.com/why-i-came-back-to-amazon/#deliver-results">Deliver Results</a></li>
</ul>
</li>
<li><a href="https://swaits.com/why-i-came-back-to-amazon/#reason-two-stability">Reason two: Stability</a></li>
<li><a href="https://swaits.com/why-i-came-back-to-amazon/#reason-three-opportunity">Reason three: Opportunity</a></li>
<li><a href="https://swaits.com/why-i-came-back-to-amazon/#closing">Closing</a></li>
</ul>
<!--toc:end-->
<h2 id="reason-one-culture">Reason one: Culture</h2>
<p>Many know the Amazon founders built the company around its famous <a href="https://amazon.jobs/en/internal/principles">leadership principles</a>.
What’s hard to explain is just how much we operate around these principles daily.
As an example, I’ll share something I experienced during my first interview and start with Amazon in 2016.</p>
<p>While interviewing for a PE role, I went through a multitude of calls before getting to my onsite loop.
In each call, I noticed my interviewer emphasized the importance of these leadership principles.
I realized I needed to take them seriously as part of my interview prep, and I did.
Despite that, I figured they were like the sort of <em>company values</em> things I’d experienced at every other place I’d worked – something that looks nice in a bit of PR, but which is ignored by 99% of the employees 100% of the time.
Or something that is updated pseudo-collaboratively once a year at the annual offsite and then promptly forgotten.</p>
<p>Fast forward a bit.
I got hired and anxiously showed up for my first day.
To my surprise, people were quoting the leadership principles, or <em>LPs</em> for short, in everyday conversations.
They’re using them to drive the right actions and make important decisions.
Most importantly, they’re holding each other accountable in terms of individual and team performance based exactly on one thing - the LPs.
I was astonished.
<strong>These things were real.</strong>
It’s hard to explain to outsiders, but it’s true – its leadership principles truly defined Amazon’s company culture.</p>
<p>A few of the LPs stand out to me as most impactful, and were key in drawing me back into Amazon.
I’m <strong>emphasizing</strong> the parts of each LP below which most made me want to return to Amazon.</p>
<h3 id="customer-obsession">Customer Obsession</h3>
<blockquote>
<p>Leaders start with the customer and <strong>work backwards</strong>. They <strong>work vigorously to earn and keep customer trust</strong>. Although leaders pay attention to competitors, they obsess over customers.</p>
</blockquote>
<p>This is our first LP, and it’s first for a reason.
Nothing is more important than this.
In fact, in my experience, if I’m unable to express the customer value for something I want to work on clearly, I’m going to have a really hard time convincing others it’s a smart way to spend my time.
And rightfully so!
If we’re not doing things to solve customer needs and continually earn their trust, then what the heck are we doing?</p>
<p>At Amazon, small teams of Builders connect directly to customer-facing metrics — like business metrics or customer satisfaction, or things more technical like system availability.
When I see this, it leaves me more confident that we are optimizing for what customers want and need.</p>
<h3 id="ownership">Ownership</h3>
<blockquote>
<p>Leaders are owners. They think long term and <strong>don’t sacrifice long-term value for short-term results</strong>. They act on behalf of the entire company, <strong>beyond just their own team</strong>. They never say “<strong>that’s not my job</strong>.“</p>
</blockquote>
<p>At a superficial level, this seems obvious.
But it’s not.
It’s more nuanced than it appears, and this took me awhile to really understand.</p>
<p>In my first year at Amazon, my manager taught me the actual meaning and value of <em>ownership</em>.
It’s not just about filtering out <em>not my jobbers</em>, who have no place in any workforce.
It’s <strong>about owning everything</strong>.
That seems crazy and daunting, which is why it took me some time to understand.</p>
<p>There’s a book which teaches this better than I ever will: <a href="https://echelonfront.com/extreme-ownership/">Extreme Ownership</a>.
Fair warning — the book is based on military experience and is full of what some may consider too much masculinity and machismo.
But, if you can get past that part, the lessons in here are grand.</p>
<p>Once I understood <em>ownership</em> like this, I noticed something interesting: the most successful Amazonians were also the strongest owners.
I think it’s a huge recipe for success and I encourage anyone reading this to strive to be the best owner possible.</p>
<h3 id="are-right-a-lot">Are Right, A Lot</h3>
<blockquote>
<p>Leaders are right a lot. They have sound judgment and good instincts. They <strong>seek diverse perspectives and work to disconfirm their beliefs</strong>.</p>
</blockquote>
<p>This one seems straightforward too, but it’s often misinterpreted.
There are multiple dimensions to this.
Being <em>right, a lot</em>, doesn’t mean you are super smart, know everything, and always have the right answers.
In fact, Amazonians say that the smartest people in the world are the kind of people who change their mind a lot.
That’s because they <em>seek diverse perspectives</em>, recognize their own limitations as a single human, and act on new information in a constructive, thoughtful way.
That’s ridiculously smart behavior which I appreciate immensely and hope to model myself.</p>
<h3 id="hire-and-develop-the-best">Hire and Develop the Best</h3>
<blockquote>
<p>Leaders <strong>raise the performance bar with every hire and promotion</strong>. They recognize exceptional talent, and willingly move them throughout the organization. Leaders develop leaders and take seriously their role in coaching others. We work on behalf of our people to invent mechanisms for development like Career Choice.</p>
</blockquote>
<p>It really all starts here.
If you hire the best, then you’ll find they’re probably intuitively capable of embodying all the other LPs on this list.
Amazon will work hard to recruit the best possible employees and compensate them well.
If you want the best, you need to pay for it.
But the ROI on this, while I can’t give you any magic formula, is definitely positive.
I enjoy working at a company that prioritizes hiring great people and then trusts them to go do their work.</p>
<h3 id="insist-on-the-highest-standards">Insist on the Highest Standards</h3>
<blockquote>
<p>Leaders have <strong>relentlessly high standards</strong> — many people may think these standards are unreasonably high. Leaders are <strong>continually raising the bar and drive their teams to deliver high quality products, services, and processes</strong>. Leaders ensure that defects do not get sent down the line and that problems are fixed so they stay fixed.</p>
</blockquote>
<p>I love that nobody here ever says “it’s good enough”.
Amazonians have big ideas.
They set huge goals and a North Star, and then iteratively work to get there.
This <em>reach for the stars</em> mentality is super inspiring to me.</p>
<h3 id="bias-for-action">Bias for Action</h3>
<blockquote>
<p>Speed matters in business. Many decisions and actions are reversible and do not need extensive study. <strong>We value calculated risk taking</strong>. </p>
</blockquote>
<p>Teams at Amazon test ideas quickly.
Not in a lab - well, maybe sometimes in a lab - but in front of customers.
Sometimes <a href="https://en.wikipedia.org/wiki/Fire_Phone">it doesn’t work out</a>!
But we owe our customers innovation, and a massive part of innovation is exploring uncharted territory.
And to do that, you have to move quickly.
Amazon is very good at deciding when something is a two-way door and moving quickly, or when something is (more rarely) a one-way door and moving deliberately.
This appeals to me because it’s an optimal way to please customers consistently.</p>
<h3 id="earn-trust">Earn Trust</h3>
<blockquote>
<p>Leaders listen attentively, speak candidly, and treat others respectfully. They are <strong>vocally self-critical, even when doing so is awkward or embarrassing</strong>. Leaders <strong>do not believe their or their team’s body odor smells of perfume</strong>. They benchmark themselves and their teams against the best.</p>
</blockquote>
<p>This is another LP that is multi-dimensional, like <em>Are Right, A Lot</em>.
A big part of this is indeed, <em>Leading with Empathy</em>, treating others respectfully, and just being a kind, collaborative teammate.
But a huge part of this is about being <strong>vocally self critical</strong>.
At Amazon I’m surrounded by really smart people.
We employ extremely accomplished people, at the top of industry and academia; and they’re all just a Slack DM away from anyone in the company.</p>
<p>You know what’s so amazing about this?
They aren’t jerks.
Yes, they’re mental giants, but you’d <em>never know it</em> when talking to them.
Being surrounded by people way smarter than me, who are humble, and don’t for a moment think they’re better than the next person, is truly inspirational.
It’s one of those things that I hope makes me a better person too.</p>
<h3 id="have-backbone-disagree-and-commit">Have Backbone; Disagree and Commit</h3>
<blockquote>
<p>Leaders are obligated to respectfully challenge decisions when they disagree, even when doing so is uncomfortable or exhausting. Leaders have conviction and are tenacious. They do not compromise for the sake of social cohesion. <strong>Once a decision is determined, they commit wholly</strong>.</p>
</blockquote>
<p>People debate with each other at Amazon.
It’s not mean.
It’s not personal.
But when the team decides, <strong>everyone</strong> gets on board; even, or especially those who weren’t initially on board.
I think most of us had to grow to get to this point.
But when you get there, and when you work with a bunch of people there with you, it sure is fantastic.</p>
<h3 id="deliver-results">Deliver Results</h3>
<blockquote>
<p><strong>Leaders focus on the key inputs for their business and deliver them with the right quality and in a timely fashion. Despite setbacks, they rise to the occasion and never settle.</strong></p>
</blockquote>
<p>Ultimately, we get it done at Amazon.
Ideas start with a customer problem, people step up to own solutions, smart but humble people iterate and learn, producing the highest quality outputs possible, they’re nice to each other along the way even when they disagree, and those ideas land in front of customers.</p>
<p>It’s a wonderful thing to watch and even more wonderful to be lucky enough to be a part of it.</p>
<h2 id="reason-two-stability">Reason two: Stability</h2>
<p>I seriously considered Meta and Alphabet; aka Facebook and Google.
I have plenty of former colleagues, even friends, in both places.
I think they’re both doing interesting things, especially in cultivating engineering-first cultures.
If the right opportunity presents itself in the future, I’d absolutely consider them again.
Never burn a bridge or needlessly close the door.</p>
<p>However, given the economic uncertainty we’re facing today, I valued the diversity of Amazon over other <a href="https://en.wikipedia.org/wiki/Big_Tech">big tech</a> companies.
We’re most well known for online retail and AWS cloud services.
But we’re also in TV and movie production, video on demand, audio on demand, live sports and creator streaming, consumer electronics, a pretty popular voice assistant, home automation and security, groceries and Whole Foods, satellite internet, physical retail stores, video gaming, aviation, drone delivery, logistics, and even pharmaceuticals and healthcare!
Oh yeah, and books too; print, digital, and audio.
No other company at this scale matches Amazon’s diversification.</p>
<p>While this wasn’t the main reason I chose Amazon, it boosts my confidence that Amazon can be a place where I can finish my career, succeeding or failing based on my effort and hard work, without worrying as much about external factors.</p>
<h2 id="reason-three-opportunity">Reason three: Opportunity</h2>
<p>I can learn and grow a massive amount here at Amazon.
My manager, a Sr. Principal Engineer, worked on the <a href="https://en.wikipedia.org/wiki/Hubble_Space_Telescope">Hubble Space Telescope</a> flight control code.
Our local Distinguished Engineer, who I’m lucky enough to get to work with, worked on the <a href="https://en.wikipedia.org/wiki/Windows_NT">Windows NT kernel</a> I/O system.
My skip manager, a VP, is one of the friendliest leaders I’ve ever met; and yet he’s not soft.
He knows when to challenge, but always does so in an empathetic manner.
He’s also deeply technical.
And I’m embedded on a team of Principal and Senior Principal Engineers with worlds of knowledge and experience.
The tech teams around me are brilliant and productive.
In summary, I am but a small ant surrounded by accomplished giants.
These are smart, experienced, hardworking, customer obsessed people from whom I can learn a ton, and that’s very exciting!</p>
<h2 id="closing">Closing</h2>
<p>I don’t know for sure because I cancelled all of my non-Amazon interviews, but there’s some chance I could’ve worked for one of those other big companies.
But I decided I <em>wanted</em> to work for Amazon.</p>
<p>It’s my comfort and admiration of Amazon’s culture that most lured me back.
The stability of our diversified business and opportunity to continue learning and growing from amazing colleagues are nice bonuses.</p>
<p>I’m thrilled to be back at Amazon and confident in my decision.
Here’s to the future — I have a ton of learning and hard work ahead of me — and that sounds fun!</p>
Today I start a brand new adventure!2021-11-29T00:00:00+00:002021-11-29T00:00:00+00:00
Unknown
https://swaits.com/starting-a-new-adventure/<p>I normally ignore recruiter calls. But when I saw an email with the subject
“Distinguished Engineer at Zwift”, I instantly knew I had to look into it.</p>
<p><img src="https://swaits.com/starting-a-new-adventure/zwift-first-day-swag.jpeg" alt="Welcome kit from Zwift!" /></p>
<p>I first knew of <a href="https://zwift.com/">Zwift</a> in early 2016. I tried it, but didn’t stick with it. I
was already deep into TrainerRoad and happy with it, and Zwift was still early
days and not nearly as polished as it is today.</p>
<p>I was really into cycling those days. I worked my way up on the road from
Category 5 to 3, and raced just about every chance I could anywhere in San
Diego or even across SoCal and sometimes Arizona. Eventually, I gave up racing
and dialed my cycling way back. But, I was still aware of Zwift. When the
pandemic hit, all of my cycling buddies instantly moved over to Zwift. And as
things opened up and in-person group riding became okay again, many of them
stuck with Zwift. And though I’m not as active a cyclist as I once was, I’m
still passionate about it. <em>Zwift gives me an opportunity to combine my work
with my passion for cycling.</em></p>
<p>Before Amazon, I spent 19 years making video games professionally. I chose to
leave the industry in 2015 and never regretted it. But, I did miss some of the
interesting technical challenges. Zwift is basically an MMO video game where
the controller is a connected smart trainer. <em>Working at Zwift gets me closer
to the most interesting technical challenges I know.</em></p>
<p>I spent the past five years at Amazon learning, growing, and practicing my
skills at broad leadership as a Tech IC. I’ve worked across hundreds of SDEs on
everything from the tactical minutiae to the strategic tech decisions in very
large businesses. <em>As a Distinguished Engineer at Zwift, I hope to apply what
I’ve been refining and practicing as a PE at Amazon the past five years.</em></p>
<p>It’s the combination of these three things, a passion for cycling and indoor
fitness, getting closer again to really interesting technical challenges like I
worked on in games, and landing in a role where I can influence a large tech
organization for the better, that makes me extremely excited about this new
job!</p>
Working at Amazon. A life changing experience.2021-11-19T00:00:00+00:002021-11-19T00:00:00+00:00
Unknown
https://swaits.com/incredible-amazon-experience/<p>Today is my last effective day with Amazon - my official last day is next
Friday, November 26. But I’m taking most of next week off in observance of
Thanksgiving in the US. Thus, today is really it!</p>
<p>Inevitably people will ask “why?” It’s no secret that I planned to finish my
career at Amazon; after all, my hair isn’t getting thicker or less gray as each
day goes by. And this is a damn good gig!</p>
<p>But, life is full of unexpected twists and turns. Something in the “oh wow,
I gotta go try this” category landed in my inbox. More on that later. For now,
I want to take a moment to celebrate Amazon.</p>
<p>This has been the best five years in my career, by far. Amazon was truly
life-changing for me. From getting to work with an amazing Principal Engineer
community, alongside industry luminaries, collaborating with engineering teams
to build distributed systems at unmatched scale, working with hundreds of
amazing, brilliant people in all kinds of roles who taught me new stuff
everyday, realizing the power of Amazon’s Leadership Principles and Working
Backwards… I just can’t say enough great things about the whole experience.</p>
<p>Lots of people here greatly impacted me, professionally and personally. So
many, it’s impossible to thank everyone by name, but I feel compelled to share
a few.</p>
<p>I had the three best managers any person in the world could possibly have.
Infinite thanks to <a href="https://www.linkedin.com/in/paulhorvath/">Paul Horvath</a>,
<a href="https://www.linkedin.com/in/tony-bacos-56b3a5/">Tony Bacos</a>, and <a href="https://www.linkedin.com/in/margot-johnson-95b95398/">Margot
Johnson</a>. They showed me
what truly compassionate, empathetic, people-first leadership looks like. It
turns out you can deeply care about the people you work with, be extremely
respectful and nice to each other, and still get lots of stuff done!</p>
<p>Along the way, I worked with many fantastic PEs, Sr. PEs, and even DEs. But
there were a handful that were always by my side supporting me. Thank you <a href="https://www.linkedin.com/in/jenndox/">Jenn
Lin</a>, <a href="https://www.linkedin.com/in/porter-schermerhorn-a00b141/">Porter
Schermerhorn</a>, <a href="https://www.linkedin.com/in/seanblakey/">Sean
Blakey</a>, and <a href="https://www.linkedin.com/in/simonkjohnston/">Simon
Johnston</a>. They each taught me so
much more than they probably know.</p>
<p>And a hat tip to <a href="https://www.linkedin.com/in/natewiger/">Nate Wiger</a> who
started it all when he called me up a little over five years ago and said, “hey
dude I wrote a six pager and now Amazon’s hiring in San Diego, want to check it
out?” Without him, I wouldn’t be here today.</p>
<p>How could any person be so fortunate to experience what I’ve experienced at
Amazon? I still don’t know the answer to that. Everyday at work I want to pinch
myself and verbalize: why me??? Nevertheless, I’m so grateful for it all, and
not just because it’s almost Thanksgiving. I’m genuinely, deeply grateful,
humbled, and forever appreciative.</p>
<p>To everyone I worked with at Amazon, from the bottom of my heart, thank you! ❤️</p>
A guy at work asked, where's your blog?2021-11-15T00:00:00+00:002021-11-15T00:00:00+00:00
Unknown
https://swaits.com/a-guy-asked-where-my-blog-was/<p>✅ here’s my blog</p>
<p>A guy at work said, “where’s your blog?” So, here’s a quickly hacked together
site using <a href="https://www.getzola.org">zola</a> with the
<a href="https://github.com/carpetscheme/lightspeed">lightspeed</a> theme. It’s hosted in
a private repo on <a href="https://sr.ht/">sourcehut</a> and, at least for now, built and
deployed to <a href="https://srht.site/">sourcehut pages</a>.</p>
<p>I blogged many years ago. Like, decades ago. Don’t know what I might put here,
but I’ll give it a shot!</p>