<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>Luke Schiefelbein</title>
<link>https://www.lukeschiefelbein.com/blog/</link>
<atom:link href="https://www.lukeschiefelbein.com/blog/index.xml" rel="self" type="application/rss+xml"/>
<description>Luke Schiefelbein writes about science, code and other things.</description>
<generator>quarto-1.10.18</generator>
<lastBuildDate>Wed, 12 Feb 2025 00:00:00 GMT</lastBuildDate>
<item>
  <title>The First Blog Post You Should Read about Gaussian Processes (With Interactive Plots)</title>
  <link>https://www.lukeschiefelbein.com/blog/gaussian-processes/</link>
  <description><![CDATA[<section class="level2" id="why-read-this-post">
<h2 class="anchored" data-anchor-id="why-read-this-post">Why Read This Post?</h2>
<p>Is this the 10th blog post you’ve read about Gaussian Processes and still don’t quite understand them?</p>
<p>When I set out to learn about “Gaussian Processes” (really Gaussian Process Regression), I ended up jumping around between many different resources and it took hours before the core idea hit me. This post should get you to <strong>core idea in ~30 minutes using interactive plots</strong>.</p>
<p>After that, I’d encourage you to read the references at the end for more detail and rigor. They’ll now be more digestible.</p>
<blockquote class="blockquote">
<p>Prerequisites: basic linear algebra and statistics.</p>
<p>If you know python/numpy you might find it helpful to look at the code under the hood: open the <a href="https://www.lukeschiefelbein.com/blog/gaussian-processes/live/">live notebook</a>.</p>
</blockquote>
</section>
<section class="level2" id="why-gaussian-process-regression">
<h2 class="anchored" data-anchor-id="why-gaussian-process-regression">Why Gaussian Process Regression?</h2>
<ol type="1">
<li>It’s a non-linear regression model</li>
<li>It also does built-in modeling of uncertainties (cool)</li>
</ol>
<p>The following plot from the sci-kit learn documentation shows the output of GP regression model. Note the beautifully curved uncertainty bands. Where does all this come from?</p>
<p><img class="img-fluid" src="https://scikit-learn.org/0.17/_images/plot_gp_regression_001.png"/></p>
</section>
<section class="level2" id="gp-regression-the-one-line-definition">
<h2 class="anchored" data-anchor-id="gp-regression-the-one-line-definition">GP Regression: The One Line Definition</h2>
<p>Turns out GP regression can be described in one (somewhat loaded) line:</p>
<p><strong>GP Regression: A Multivariate Gaussian Distribution over functions, conditioned on some training data.</strong></p>
<p>Hmm. Maybe not so terrible? But how does this connect to the idea of “a nonlinear regression that models uncertainty”?</p>
<p>Let’s unpack this definition bit by bit, starting with “distribution over functions”.</p>
</section>
<section class="level2" id="prequel-distributions-over-functions">
<h2 class="anchored" data-anchor-id="prequel-distributions-over-functions">Prequel: Distributions over Functions</h2>
<p>We all know linear regression. Given some data, we assume it has the form <img src="https://latex.codecogs.com/png.latex?y%20=%20%5Cbeta_0%20+%20%5Cbeta_1%20x%20+%20%5Cepsilon"/>. We have some data <img src="https://latex.codecogs.com/png.latex?x"/> and <img src="https://latex.codecogs.com/png.latex?y"/>, and we want to find the <img src="https://latex.codecogs.com/png.latex?%5Chat%7B%5Cbeta_0%7D"/> and <img src="https://latex.codecogs.com/png.latex?%5Chat%7B%5Cbeta_1%7D"/> (“^” means “estimated”) that describe a line of best fit. We can do this using OLS (ordinary least squares), and we end up with a linear function <img src="https://latex.codecogs.com/png.latex?f(x)%20=%20%5Chat%7B%5Cbeta_0%7D%20+%20%5Chat%7B%5Cbeta_1%7D%20x"/>, that for any <img src="https://latex.codecogs.com/png.latex?x"/> gives us a prediction for <img src="https://latex.codecogs.com/png.latex?y"/>.</p>
<p>Note that we don’t model <img src="https://latex.codecogs.com/png.latex?%5Cepsilon"/>; what if we want some notion of the uncertainty in our predictions?</p>
<p>For example, look at the two plots below:</p>
<p class="mb-feed-note"><em><a href="https://www.lukeschiefelbein.com/blog/gaussian-processes/">Interactive figure: open the post to see it.</a></em></p>
<p>Even though the data on the left is much more noisy, both linear regressions model the data with the sample simple line. There’s no measure of uncertainty. Let’s try to add some.</p>
<p>What if (just for fun) we assumed <img src="https://latex.codecogs.com/png.latex?%5Cbeta_0"/> and <img src="https://latex.codecogs.com/png.latex?%5Cbeta_1"/> were actually random variables, and that they are normally distributed with variance equal to the variance in the data? Then our linear regression equation would look like this:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ay%20=%20B_0%20+%20B_1%20x%0A"/></p>
<p>where we’ve converted our betas to random variables as follows:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AB_0%20%5Csim%20N(%5Cbeta_0,%20%5Csigma%5E2)%0A"/></p>
<p><img src="https://latex.codecogs.com/png.latex?%0AB_1%20%5Csim%20N(%5Cbeta_1,%20%5Csigma%5E2)%0A"/></p>
<p>This reads “<img src="https://latex.codecogs.com/png.latex?B_0"/> is a random variable distributed according to a Gaussian with mean <img src="https://latex.codecogs.com/png.latex?%5Cbeta_0"/> and variance <img src="https://latex.codecogs.com/png.latex?%5Csigma%5E2"/>”. We’ll set <img src="https://latex.codecogs.com/png.latex?%5Csigma%5E2"/> to be the variance of the data about the best fit line (the variance of the residuals). For the plots a and b above, then we’d have:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ay_a%20=%20B_%7B0a%7D%20+%20B_%7B1a%7D%20x%0A"/></p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ay_b%20=%20B_%7B0b%7D%20+%20B_%7B1b%7D%20x%0A"/></p>
<p>where <img src="https://latex.codecogs.com/png.latex?B_0"/> and <img src="https://latex.codecogs.com/png.latex?B_1"/> are gaussians with variance equal to the variance of the residuals in each case: <img src="https://latex.codecogs.com/png.latex?%5Csigma%5E2_a"/> and <img src="https://latex.codecogs.com/png.latex?%5Csigma%5E2_b"/></p>
<p>Now that the betas are random variables, let’s sample from them, and plot the resulting lines:</p>
<p>(Click the “New Sample” button below)</p>
<p class="mb-feed-note"><em><a href="https://www.lukeschiefelbein.com/blog/gaussian-processes/">Interactive figure: open the post to see it.</a></em></p>
<p>Each time you click the button, a new sample <img src="https://latex.codecogs.com/png.latex?%5Cbeta_0,%20%5Cbeta_1"/> are drawn from the distributions of <img src="https://latex.codecogs.com/png.latex?B_0"/> and <img src="https://latex.codecogs.com/png.latex?B_1"/> (one set of betas for the noisy data on the left and one set for the less noisy on the right), and the corresponding lines are plotted.</p>
<p>Notice what we’ve created: it’s a distribution over <em>functions</em>. Each sample from <img src="https://latex.codecogs.com/png.latex?y%20=%20B_0%20+%20B_1%20x"/> is a different function <img src="https://latex.codecogs.com/png.latex?f(x)"/>. Also notice that as you sample more and more, the “spread” or uncertainty of the original fit becomes more and more apparent. You can also see that the uncertainty is higher in the noisier data.</p>
<blockquote class="blockquote">
<p>This framework is just to develop for intuition for distributions over functions. For a more rigorous take, check out the bayesian linear regression section <a href="https://gaussianprocess.org/gpml/chapters/RW.pdf#page=26&amp;zoom=50,240,358">here</a>.</p>
</blockquote>
<p>Returning to our original definition, we’ve checked off one core idea:</p>
<p><strong>GP Regression: A Multivariate Gaussian <font color="#32a852">Distribution over functions</font>, conditioned on some training data.</strong></p>
<p>What if we want to model a non-linear relationship? Now we’re getting closer to the core idea of GPs. But of course, before we get to Gaussian processes, we have to talk about Gaussians.</p>
</section>
<section class="level2" id="gaussiansand-multivariate-gaussians">
<h2 class="anchored" data-anchor-id="gaussiansand-multivariate-gaussians">Gaussians…and Multivariate Gaussians</h2>
<p>The Gaussian, the bell curve, the normal distribution, we’ve seen it before. It’s defined by</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AY%20%5Csim%20N(%5Cmu,%20%5Csigma%5E2)%0A"/></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Cmu"/> is the mean and <img src="https://latex.codecogs.com/png.latex?%5Csigma%5E2"/> is the variance. You can play around with the parameters of the distribution below. Each time you change the sliders, 10,000 samples are drawn from the distribution and plotted as a histogram.</p>
<p class="mb-feed-note"><em><a href="https://www.lukeschiefelbein.com/blog/gaussian-processes/">Interactive figure: open the post to see it.</a></em></p>
<p>What about multivariate Gaussians? That is, a Gaussian distribution but with more than one variable. They are basically the same thing, but instead of having a single mean and variance, there’s there’s a mean vector and a covariance matrix.</p>
<p>So while a single Gaussian is this:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AY%20%5Csim%20N(%5Cmu,%20%5Csigma%5E2)%0A"/></p>
<p>Where <img src="https://latex.codecogs.com/png.latex?%5Cmu"/> is the mean and <img src="https://latex.codecogs.com/png.latex?%5Csigma"/> is the variance, a multivariate Gaussian is this:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctextbf%7BY%7D%20%5Csim%20N(%5Cboldsymbol%7B%5Cmu%7D,%20%5CSigma)%0A"/></p>
<p>Where <img src="https://latex.codecogs.com/png.latex?%5Cboldsymbol%7B%5Cmu%7D"/> is a vector of means, <img src="https://latex.codecogs.com/png.latex?%5CSigma"/> is a covariance matrix.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cboldsymbol%7B%5Cmu%7D%20=%20%5Cbegin%7Bbmatrix%7D%0A%5Cmu_1%20%5C%5C%0A%5Cmu_2%20%5C%5C%0A%5Cvdots%20%5C%5C%0A%5Cmu_n%0A%5Cend%7Bbmatrix%7D,%0A"/></p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cqquad%0A%5CSigma%20=%20%5Cbegin%7Bbmatrix%7D%0A%5Csigma_%7B11%7D%20&amp;%20%5Csigma_%7B12%7D%20&amp;%20%5Cdots%20&amp;%20%5Csigma_%7B1n%7D%20%5C%5C%0A%5Csigma_%7B21%7D%20&amp;%20%5Csigma_%7B22%7D%20&amp;%20%5Cdots%20&amp;%20%5Csigma_%7B2n%7D%20%5C%5C%0A%5Cvdots%20&amp;%20%5Cvdots%20&amp;%20%5Cddots%20&amp;%20%5Cvdots%20%5C%5C%0A%5Csigma_%7Bn1%7D%20&amp;%20%5Csigma_%7Bn2%7D%20&amp;%20%5Cdots%20&amp;%20%5Csigma_%7Bnn%7D%0A%5Cend%7Bbmatrix%7D.%0A"/></p>
<p>In the above expressions, <img src="https://latex.codecogs.com/png.latex?%5Cmu_i"/> is the mean of the <img src="https://latex.codecogs.com/png.latex?i"/> th component of <img src="https://latex.codecogs.com/png.latex?%5Ctextbf%7BY%7D"/></p>
<p>In this case when we sample from <img src="https://latex.codecogs.com/png.latex?%5Ctextbf%7BY%7D"/>, we get a vector <img src="https://latex.codecogs.com/png.latex?%5Ctextbf%7BY%7D%20=%20%5BY_1,%20Y_2,%20%5Cdots,%20Y_n%5D%5ET"/>, instead of just a single value.</p>
<p>Nothing too crazy right? Well, except that the multivariate normal distribution has a covariance <em>matrix</em>, not just a variance.</p>
<p>In the covariance matrix we specify not only the variance of each variable, but also the covariance between each variable and every other. Each of the <img src="https://latex.codecogs.com/png.latex?n"/> variables in a multivariate Gaussian can be correlated with each other. Below is an overhead view of a bunch of samples for a 2D multivariate Gaussian distribution. You can interactively change the covariance matrix to see how it affects the distribution.</p>
<p class="mb-feed-note"><em><a href="https://www.lukeschiefelbein.com/blog/gaussian-processes/">Interactive figure: open the post to see it.</a></em></p>
<p>Note that if you change the diagonal elements, the variance of one or the other variable will change (look at the scale of axes). If you change the off-diagonal elements, the covariance between the two variables will change.</p>
<p>You also probably saw that the matrix has to be symmetric and with positive diagonals. Why? Well think about what covariance is: its how two variables vary together. It doesn’t make sense for Cov(X,Y) != Cov(Y,X). And it doesn’t make sense for Cov(X,X) — that is, Var(X) — to be negative (you’ll get an error if you try).</p>
<p>Also note that while the combined joint distribution changes form, each of the individual distributions is still a Gaussian. That is to say, the distribution of <img src="https://latex.codecogs.com/png.latex?X_1"/> is still a gaussian, and the distribution of <img src="https://latex.codecogs.com/png.latex?X_2"/> is still a Gaussian. The only thing that changes is the covariance between the two variables.</p>
</section>
<section class="level2" id="gaussian-distribution-over-functions">
<h2 class="anchored" data-anchor-id="gaussian-distribution-over-functions">Gaussian Distribution Over Functions</h2>
<p>So far we’ve visualized a multivariate Gaussian of just two variables, but you can imagine taking this into many many dimensions. That is to say, many many Gaussian random variables that may or may not be correlated with each other according to some big covariance matrix.</p>
<p>In order to think about more than two dimensions, we’ll need to visualize our distributions differently. Let’s start with a 1-D Gaussian. We’ll draw samples from the distribution and plot them below:</p>
<p class="mb-feed-note"><em><a href="https://www.lukeschiefelbein.com/blog/gaussian-processes/">Interactive figure: open the post to see it.</a></em></p>
<p>Each new sample is drawn from a normal distribution with mean 0 and variance 1.</p>
<p>What if we want to sample from a multivariate normal distribution? Well, this time <img src="https://latex.codecogs.com/png.latex?%5Ctextbf%7BY%7D"/> is a <em>vector</em> of random variables. If <img src="https://latex.codecogs.com/png.latex?%5Ctextbf%7BY%7D"/> is 2-d then <img src="https://latex.codecogs.com/png.latex?%5Ctextbf%7BY%7D%20=%20%5BY_1,%20Y_2%5D%5ET"/>, so each sample is a vector of two values which we’ll plot each on their own part of the X-axis.</p>
<p class="mb-feed-note"><em><a href="https://www.lukeschiefelbein.com/blog/gaussian-processes/">Interactive figure: open the post to see it.</a></em></p>
<p>So now each time we sample from a bivariate normal distribution, we get a vector of two numbers, which we plot as two connected points. Now let’s look at a 3-D Gaussian, <img src="https://latex.codecogs.com/png.latex?%5Ctextbf%7BY%7D_%7B3D%7D%20=%20%5BY_1,%20Y_2,%20Y_3%5D%5ET"/>,</p>
<p class="mb-feed-note"><em><a href="https://www.lukeschiefelbein.com/blog/gaussian-processes/">Interactive figure: open the post to see it.</a></em></p>
<p>Now let’s take it to an extreme: a 50-D Gaussian. <img src="https://latex.codecogs.com/png.latex?%5Ctextbf%7BY%7D_%7B50D%7D%20=%20%5BY_1,%20Y_2,%20Y_3,%20%5Cdots,%20Y_%7B50%7D%5D%5ET"/>, so every sample is a vector of 50 random values.</p>
<p class="mb-feed-note"><em><a href="https://www.lukeschiefelbein.com/blog/gaussian-processes/">Interactive figure: open the post to see it.</a></em></p>
<p>Cool, so now we can visualize samples from a 50-D Gaussian in this kinda of weird way. How is this useful? And how does this relate to a Gaussian Process regression?</p>
<p>You’ll notice the above plot has a “Connect” button. If you click it, each variable in a given sample gets connected to the next.</p>
<p>Do these connected samples remind you of anything? Maybe a certain class of elementary mathematical objects?</p>
<p>If you thought “functions” then you are getting what I’m going for here. Each sample from the 50-D Gaussian is starting to look like some curve where, for any x-coordinate (which is just the index of the vector output of the multivariate gaussian) you can look up a y-value. So this kinda-sorta function can be defined as</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Af(x)%20=%20Y_%7Bx%7D%0A"/></p>
<p>given a multivariate Gaussian <img src="https://latex.codecogs.com/png.latex?Y%20=%20%5BY_1,%20Y_2,%20Y_3,%20%5Cdots,%20Y_%7Bx%7D%5D%5ET%20%5Csim%20N(%5Cboldsymbol%7B%5Cmu%7D,%20%5CSigma)"/>.</p>
<p>But remember what each different colored curve is: it’s a sample from a multivariate normal distribution. It’s almost like the 50-D Gaussian specifies a <em>distribution over functions</em>…</p>
<p><img height="200" src="https://i.kym-cdn.com/entries/icons/original/000/007/630/conspiracykeanu.jpg" width="340"/></p>
<p><em>Almost</em>. The 50-D Gaussian really only specifies a distribution over 50 discrete values, so it’s not quite a distribution over functions <img src="https://latex.codecogs.com/png.latex?f(x)"/> that can take <em>any</em> value of <img src="https://latex.codecogs.com/png.latex?x"/>. Hold that thought for now, we’ll return to this later.</p>
<p>But first you might have some questions. Remember, the above plot was made by taking a few samples from 50-D gaussian, <img src="https://latex.codecogs.com/png.latex?%5Ctextbf%7BY%7D_%7B50D%7D%20%5Csim%20N(%5Cboldsymbol%7B%5Cmu%7D,%20%5CSigma)"/></p>
<p>But I didn’t tell you what <img src="https://latex.codecogs.com/png.latex?%5Cboldsymbol%7B%5Cmu%7D"/> and <img src="https://latex.codecogs.com/png.latex?%5CSigma"/> were. Well, I actually used a mean vector of all zeros:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cboldsymbol%7B%5Cmu%7D%20=%20%20%5Cbegin%7Bbmatrix%7D%0A0%20%5C%5C%0A0%20%5C%5C%0A%5Cvdots%20%5C%5C%0A0%0A%5Cend%7Bbmatrix%7D,%0A"/></p>
<p>and for the covariance matrix I simply used the Identity matrix.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5CSigma%20=%20%5Cbegin%7Bbmatrix%7D%0A1%20&amp;%200%20&amp;%20%5Cdots%20&amp;%200%20%5C%5C%0A0%20&amp;%201%20&amp;%20%5Cdots%20&amp;%200%20%5C%5C%0A%5Cvdots%20&amp;%20%5Cvdots%20&amp;%20%5Cddots%20&amp;%20%5Cvdots%20%5C%5C%0A0%20&amp;%200%20&amp;%20%5Cdots%20&amp;%201%0A%5Cend%7Bbmatrix%7D.%0A"/></p>
<p>Since this covariance matrix is really big, let’s visualize it with a heatmap:</p>
<p class="mb-feed-note"><em><a href="https://www.lukeschiefelbein.com/blog/gaussian-processes/">Interactive figure: open the post to see it.</a></em></p>
<p>This means that each of the variables in our multivariate normal distribution are…..????</p>

<span class="hover-text">Hover or tap for answer</span>:

<span class="hidden-text">INDEPENDENT!</span>


<p>No variable has any covariance with any other, so you can think of this multivariate Gaussian as simply 50 separate Gaussians, each with mean = 0 and variance 1. This is why the curves we plotted above are so all over the place: each point is randomly bouncing up and down with no influence from its neighbors.</p>
<p>What if we add some non-zero values to the covariance matrix that are off-diagonal?</p>
<blockquote class="blockquote">
<p>Remember the diagonal elements of the matrix at row and column <img src="https://latex.codecogs.com/png.latex?i"/> is <img src="https://latex.codecogs.com/png.latex?Cov(Y_i,%20Y_i)%20=%20Var(Y_i)"/> while the off-diagonal elements at <img src="https://latex.codecogs.com/png.latex?i,j"/> are <img src="https://latex.codecogs.com/png.latex?Cov(Y_i,%20Y_j)"/>.</p>
</blockquote>
<p>For example, check out the covariance matrix below:</p>
<p class="mb-feed-note"><em><a href="https://www.lukeschiefelbein.com/blog/gaussian-processes/">Interactive figure: open the post to see it.</a></em></p>

<p>It looks very similar, but it’s “fuzzier” around the diagonal. Think about what type of values you would expect from a Gaussian with this covariance matrix.</p>
<p>(Try playing around with the mysterious “ℓ” slider, will explain later).</p>
<p>It’s saying that variables near each other are more correlated than variables far away. For example variable 1 is more correlated with variable 2 than it is with variable 50. Let visualize some samples from a 50-d Gaussian with this new covariance matrix. But before you hit the “sample” button, what do you think these new curves will look like?</p>
<p class="mb-feed-note"><em><a href="https://www.lukeschiefelbein.com/blog/gaussian-processes/">Interactive figure: open the post to see it.</a></em></p>
<p>Cool, they are now much smoother. This is because the variables near each other are more correlated, so nearby points are more likely to be similar. This smooths out the curves.</p>
<p>Now we can see that by changing the covariance matrix, we can control the shape of the functions that our multivariate normal distribution produces.</p>
<section class="level3" id="making-these-functions-into-functions">
<h3 class="anchored" data-anchor-id="making-these-functions-into-functions">Making these “functions” into Functions</h3>
<p>We’ve been throwing around the word “functions” a lot now, but we still never resolved the problem that these samples are really just 50-D vectors. Sure we can connect the points with little lines, but that’s not the same as a function. How do we get a distribution over true functions?</p>
<p>The answer lies in how we define the covariance and mean of our multivariate Gaussian. So far we’ve been manually inputting some mean vector and covariance matrix. Since these are objects with discrete, finite elements, we can’t really think of the distributions they define as functions. But what if we could redefine these objects (the mean and covariance) as functions?</p>
<p>Mathematically:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cboldsymbol%7B%5Cmu%7D%20=%20m(%5Ctextbf%7Bx%7D)%20%5C%5C%0A%5CSigma%20=%20k(%5Ctextbf%7Bx,%20x%7D)%0A"/></p>
<p>Now for whatever <img src="https://latex.codecogs.com/png.latex?x"/> we are interested in, we can sample from a multivariate Gaussian with mean <img src="https://latex.codecogs.com/png.latex?m(%5Ctextbf%7Bx%7D)"/> and covariance <img src="https://latex.codecogs.com/png.latex?k(%5Ctextbf%7Bx,%20x%7D)"/>. This is one of those mathematical tricks that’s so simple it’s hard to understand, or maybe feels like cheating, so let’s go through an example.</p>
<p>Say we want to sample from a few specific real number <img src="https://latex.codecogs.com/png.latex?%5Ctextbf%7Bx%7D%20=%20%5B-%5Cpi,%20%5Cpi,%202%5Cpi%5D%5ET"/> (some multiples of pi, as in 3.141…). First let’s define a mean function <img src="https://latex.codecogs.com/png.latex?m(%5Ctextbf%7Bx%7D)"/>. We can use anything we like, so let’s so something really simple: <img src="https://latex.codecogs.com/png.latex?m(%5Ctextbf%7Bx%7D)%20=%20%5Ctextbf%7Bx%7D"/>.</p>
<p>For the covariance function <img src="https://latex.codecogs.com/png.latex?k(%5Ctextbf%7Bx,%20x%7D)"/>, we need a function of x that generates a symmetric matrix with a positive diagonal (otherwise it would not be a valid covariance). Again let’s just do something really simple: <img src="https://latex.codecogs.com/png.latex?k(%5Ctextbf%7Bx%7D,%20%5Ctextbf%7Bx%7D)%20=%20%5Coperatorname%7Bdiag%7D(%5Ctextbf%7Bx%7D%20%5Codot%20%5Ctextbf%7Bx%7D%20)"/>, where we take <img src="https://latex.codecogs.com/png.latex?%5Codot"/> to mean the element-wise product of <img src="https://latex.codecogs.com/png.latex?%5Ctextbf%7Bx%7D"/> with <img src="https://latex.codecogs.com/png.latex?%5Ctextbf%7Bx%7D"/>.</p>
<blockquote class="blockquote">
<p>You maybe be wondering, “why define the covariance function as <img src="https://latex.codecogs.com/png.latex?k(%5Ctextbf%7Bx,%20x%7D)"/> instead of simply <img src="https://latex.codecogs.com/png.latex?k(%5Ctextbf%7Bx%7D)"/>?”. The reason is that covariance functions in general calculate a covariance between two vectors that are not necessarily the same. Here we are just calculating the covariance of <img src="https://latex.codecogs.com/png.latex?%5Ctextbf%7Bx%7D"/> with itself, but this will not always be the case…</p>
</blockquote>
<p>So if we put out test vector into these functions we get:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cboldsymbol%7B%5Cmu%7D%20=%20m(%5Ctextbf%7Bx%7D)%20=%20%5Ctextbf%7Bx%7D%20=%20%5B-%5Cpi,%20%5Cpi,%202%5Cpi%5D%5ET%5C%5C%0A"/></p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5CSigma%20=%20k(%5Ctextbf%7Bx%7D,%20%5Ctextbf%7Bx%7D)%20=%20%5Coperatorname%7Bdiag%7D(%5Ctextbf%7Bx%7D%5Codot%5Ctextbf%7Bx%7D)%20=%20%5Cbegin%7Bbmatrix%7D%0A(-%5Cpi)%5E2%20&amp;%200%20&amp;%200%20%5C%5C%0A0%20&amp;%20(%5Cpi)%5E2%20&amp;%200%20%5C%5C%0A0%20&amp;%200%20&amp;%20(2%5Cpi)%5E2%0A%5Cend%7Bbmatrix%7D%0A"/></p>
<p>Now we can sample from a multivariate Gaussian at these specific values of <img src="https://latex.codecogs.com/png.latex?x"/>:</p>
<p class="mb-feed-note"><em><a href="https://www.lukeschiefelbein.com/blog/gaussian-processes/">Interactive figure: open the post to see it.</a></em></p>

<p>But now that we’ve defined our mean and covariance functions, we can sample from a multivariate Gaussian at any value of <img src="https://latex.codecogs.com/png.latex?x"/> we want. For example, let’s sample at 50 evenly spaced real values of <img src="https://latex.codecogs.com/png.latex?x"/> between -1 and 1. All we do is plug these values into our mean and covariance functions, and then sample from the resulting multivariate Gaussian.</p>
<p class="mb-feed-note"><em><a href="https://www.lukeschiefelbein.com/blog/gaussian-processes/">Interactive figure: open the post to see it.</a></em></p>

<p>Now we’ve truly got a distribution over functions.</p>
<p>If we want the distribution at <em>any</em> value <img src="https://latex.codecogs.com/png.latex?x"/>, we can just plug it in to the mean and covariance functions and voila! We just need to define a mean and valid covariance function that we like, and we can sample from a multivariate Gaussian at any value of <img src="https://latex.codecogs.com/png.latex?x"/> we want.</p>
<p>By the way, “covariance functions” are usually referred to by a fancy name: <strong>kernel functions</strong>. Remember this plot of a covariance matrix from earlier?</p>
<p class="mb-feed-note"><em><a href="https://www.lukeschiefelbein.com/blog/gaussian-processes/">Interactive figure: open the post to see it.</a></em></p>
<p>This covariance we actually generated by using a specific kernel function called the squared exponential kernel, a.k.a. the Gaussian kernel a.k.a the radial basis function (RBF) kernel. I’ll refer to it as the RBF kernel for this post.</p>
<p>It’s defined as:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ak(%5Ctextbf%7Bx%7D)%20=%20%5Cexp%5Cleft(-%5Cfrac%7B1%7D%7B2%E2%84%93%5E2%7D%20%7C%7C%5Ctextbf%7Bx%7D%20-%20%5Ctextbf%7Bx%7D%7C%7C%5E2%5Cright)%0A"/></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%7C%7C%5Ctextbf%7Bx%7D%20-%20%5Ctextbf%7Bx%7D%7C%7C%5E2"/> is the element-wise squared difference matrix between each element of <img src="https://latex.codecogs.com/png.latex?x"/> with each other element, and <img src="https://latex.codecogs.com/png.latex?%E2%84%93"/> is an adjustable parameter.</p>
<p>No need to worry about the math here too closely. The important thing to note is that the RBF kernel is a function of <img src="https://latex.codecogs.com/png.latex?%5Ctextbf%7Bx%7D"/> that generates a positive semidefinite matrix. It’s a covariance function, a.k.a. kernel, and it happens to be one of the most useful kernels in the real-world.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># The RBF kernel, implemented in python with numpy</span></span>
<span id="cb1-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> pairwise_rbf(xa, xb, l<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.0</span>):</span>
<span id="cb1-3">    sq_norm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> l<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> sp.spatial.distance.cdist(xa, xb, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sqeuclidean"</span>)</span>
<span id="cb1-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> np.exp(sq_norm)</span></code></pre></div></div>

<p class="mb-feed-note"><em><a href="https://www.lukeschiefelbein.com/blog/gaussian-processes/">Interactive figure: open the post to see it.</a></em></p>
<p>Using this kernel function, lets sample from a multivariate Gaussian at 50 evenly spaced real values of <img src="https://latex.codecogs.com/png.latex?x"/> between -1 and 1. But this time you can adjust the slider to change the value of the kernel’s <img src="https://latex.codecogs.com/png.latex?l"/> parameter and see how it changes the shape of the covariance matrix and the resulting distribution over functions.</p>
<p class="mb-feed-note"><em><a href="https://www.lukeschiefelbein.com/blog/gaussian-processes/">Interactive figure: open the post to see it.</a></em></p>
</section>
</section>
<section class="level2" id="conditioning-on-data">
<h2 class="anchored" data-anchor-id="conditioning-on-data">Conditioning on Data</h2>
<p>Wow, we’ve come a long way. Let’s regroup here and remember our original definition of a Gaussian Process Regression.</p>
<p><strong>GP Regression: <font color="#32a852">A Multivariate Gaussian Distribution over functions</font>, conditioned on some training data.</strong></p>
<blockquote class="blockquote">
<p>In fact this highlighted part above is the definition of a Gaussian Process. But we want “Gaussian Process Regression”. (People sometimes use the two to mean the same thing, but technically GP is the distribution and GPR is the regression problem.)</p>
</blockquote>
<p>We’re more than half-way done! But we still need to condition it on some training data. Right now we’re just sampling these pretty functions, but they are completely random.</p>
<p>Conditioning means we want the probability of some outcome given some data. Mathematically we write this as:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap(y%20%7C%20x)%0A"/></p>
<p>that is, the probability of some outcome <img src="https://latex.codecogs.com/png.latex?y"/> given some other data <img src="https://latex.codecogs.com/png.latex?x"/>.</p>
<p>Let’s take a simple toy problem as a concrete example:</p>
<p>Say we are trying to predict the cost of a house along a particular road. At one end of the road, there is a nuclear power plant (yikes). We have some data on the cost of some of the houses on this road, but we want to predict the cost of a house at any location. Let’s look at the data first:</p>
<p class="mb-feed-note"><em><a href="https://www.lukeschiefelbein.com/blog/gaussian-processes/">Interactive figure: open the post to see it.</a></em></p>
<p>Right away we see a couple things. Housing costs are not just linearly increasing as we get further away from the plant; there seem to be a few dips. Maybe there is a prison 5 miles away from the plant; who knows. Regardless, we probably want some non-linear model for this data.</p>
<p>The main question at hand is given this known cost data, what is the expected cost of a house at another location <img src="https://latex.codecogs.com/png.latex?x"/>?</p>
<p>To state this mathematically, we want some function structured as follows:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap(%5Ctextbf%7Bc%7D%20%7C%20%5Ctextbf%7Bx%7D)%0A"/></p>
<p>That is, we want the probability distribution of cost of a house <img src="https://latex.codecogs.com/png.latex?c"/>, given its distance <img src="https://latex.codecogs.com/png.latex?x"/> from the nuclear plant. Since we’ve been thinking about multivariate Gaussian, let’s make an assumption that the distribution above follows a multivariate Gaussian.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap(%5Ctextbf%7Bc%7D%20%7C%20%5Ctextbf%7Bx%7D)%20%5Csim%20N(m(%5Ctextbf%7Bx%7D),%20k(%5Ctextbf%7Bx%7D,%20%5Ctextbf%7Bx%7D))%0A"/></p>
<p>Where <img src="https://latex.codecogs.com/png.latex?k(%5Ctextbf%7Bx%7D,%20%5Ctextbf%7Bx%7D)"/> is the covariance function, and <img src="https://latex.codecogs.com/png.latex?m(%5Ctextbf%7Bx%7D)"/> is the mean function. For now we’ll just assume that the mean function is zero, and for the covariance function we’ll use the RBF kernel.</p>
<p>Why these? Remember the RBF function’s effect above: it sort of “smooths” out the data. This makes sense to model housing prices, because it’s reasonable to expect that houses near each other will have similar prices.</p>
<blockquote class="blockquote">
<p>But why is the mean function zero? A: trust me bro. It just works out in practice that mean = 0 is usually good enough to model the data. For example sklearn’s GP implementation doesn’t even let you specify a mean function. For more on this see <a href="http://gaussianprocess.org/gpml/chapters/RW2.pdf">Section 2.7 here</a>.</p>
</blockquote>
<p>We really need the following if we want to condition on the known data:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap(%5Ctextbf%7Bc%7D%20%7C%20%5Ctextbf%7Bx%7D,%20%5Ctextbf%7Bc%7D_%7B%5Ctext%7Bknown%7D%7D,%20%5Ctextbf%7Bx%7D_%7B%5Ctext%7Bknown%7D%7D)%0A"/></p>
<p>Putting this into words, we want the probability distribution of prices of a house, given its location <em>and</em> given the prices of houses at some other locations.</p>
<p>Let’s assume that the known data also came from the same multivariate Gaussian distribution as the unknown. So we can write:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap(%5Ctextbf%7Bc%7D_%7B%5Ctext%7Bknown%7D%7D%7C%20%5Ctextbf%7Bx%7D_%7B%5Ctext%7Bknown%7D%7D)%20%5Csim%20N(0,%20k(%5Ctextbf%7Bx%7D_%7B%5Ctext%7Bknown%7D%7D,%20%5Ctextbf%7Bx%7D_%7B%5Ctext%7Bknown%7D%7D))%0A"/></p>
<p>And for the unknown data, remember we had:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap(%5Ctextbf%7Bc%7D%20%7C%20%5Ctextbf%7Bx%7D)%20%5Csim%20N(0,%20k(%5Ctextbf%7Bx%7D,%20%5Ctextbf%7Bx%7D))%0A"/></p>
<p>Notice that we’ve assumed both the known data and the unknown data come from multivariate Gaussians. What if we just combined them into one big multivariate gaussian? We could basically just stack the distributions on top of each other to form a big “mother” Gaussian.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap(%5Cbegin%7Bbmatrix%7D%20%5Ctextbf%7Bc%7D%20%5C%5C%20%5Ctextbf%7Bc%7D_%7B%5Ctext%7Bknown%7D%7D%20%5Cend%7Bbmatrix%7D%20%7C%20%5Cbegin%7Bbmatrix%7D%20%5Ctextbf%7Bx%7D%20%5C%5C%20%5Ctextbf%7Bx%7D_%7B%5Ctext%7Bknown%7D%7D%20%5Cend%7Bbmatrix%7D)%20%5Csim%20N%5Cleft(%5Cbegin%7Bbmatrix%7D0%20%5C%5C%200%5Cend%7Bbmatrix%7D,%20%5Cbegin%7Bbmatrix%7Dk(%5Ctextbf%7Bx%7D,%20%5Ctextbf%7Bx%7D)%20&amp;%20k(%5Ctextbf%7Bx%7D,%20%5Ctextbf%7Bx%7D_%7B%5Ctext%7Bknown%7D%7D)%20%5C%5C%20k(%5Ctextbf%7Bx%7D_%7B%5Ctext%7Bknown%7D%7D,%20%5Ctextbf%7Bx%7D)%20&amp;%20k(%5Ctextbf%7Bx%7D_%7B%5Ctext%7Bknown%7D%20%7D,%20%5Ctextbf%7Bx%7D_%7B%5Ctext%7Bknown%7D%7D)%5Cend%7Bbmatrix%7D%5Cright)%0A"/></p>
<p>The one wrinkle here is the covariance function. For the off-diagonal elements, we need to use the covariance function between the unknown data and the known data. So <em>now</em> we see why the kernel function is written as <img src="https://latex.codecogs.com/png.latex?k(%5Ctextbf%7Bx%7D,%20%5Ctextbf%7Bx%7D)"/>; in this case we need to compute the covariance between two different vectors of data to fill in the off-diagonal blocks of the covariance matrix above.</p>
<p>Can we massage the beast above into a probability distribution conditioned on the known data? Like so:</p>
<p><img src="https://latex.codecogs.com/png.latex?p(%5Ctextbf%7Bc%7D%20%7C%20%5Ctextbf%7Bx%7D,%20%5Ctextbf%7Bc%7D_%7B%5Ctext%7Bknown%7D%7D,%20%5Ctextbf%7Bx%7D_%7B%5Ctext%7Bknown%7D%7D)%20%5Csim%20???"/></p>
<p>The answer is yes. It’s the following:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap(%5Ctextbf%7Bc%7D%20%7C%20%5Ctextbf%7Bx%7D,%20%5Ctextbf%7Bc%7D_%7B%5Ctext%7Bknown%7D%7D,%20%5Ctextbf%7Bx%7D_%7B%5Ctext%7Bknown%7D%7D)%20%5Csim%20N%5Cleft(m(%5Ctextbf%7Bx%7D)%20+%20k(%5Ctextbf%7Bx%7D,%20%5Ctextbf%7Bx%7D_%7B%5Ctext%7Bknown%7D%7D)k(%5Ctextbf%7Bx%7D_%7B%5Ctext%7Bknown%7D%7D)%5E%7B-1%7D(%5Ctextbf%7Bc%7D_%7B%5Ctext%7Bknown%7D%7D%20-%20m(%5Ctextbf%7Bx%7D_%7B%5Ctext%7Bknown%7D%7D)),%20k(%5Ctextbf%7Bx%7D)%20-%20k(%5Ctextbf%7Bx%7D,%20%5Ctextbf%7Bx%7D_%7B%5Ctext%7Bknown%7D%7D)k(%5Ctextbf%7Bx%7D_%7B%5Ctext%7Bknown%7D%7D)%5E%7B-1%7Dk(%5Ctextbf%7Bx%7D_%7B%5Ctext%7Bknown%7D%7D,%20%5Ctextbf%7Bx%7D)%5Cright)%0A"/></p>
<blockquote class="blockquote">
<p>Please don’t get mad at me for just giving you the answer. It’s a kind of complicated derivation, and I don’t want us to get bogged down. If you want to go through it, see this section of <a href="http://gaussianprocess.org/gpml/chapters/RW.pdf#page=218&amp;zoom=50,240,358">Gaussian Processes for Machine Learning</a>.</p>
<p>For now, just accept that there’s a nice closed form solution to this problem.</p>
</blockquote>
<p>Wait, uh, so we’re done?</p>
<p><strong>GP Regression: <font color="#32a852">A Multivariate Gaussian Distribution over functions, conditioned on some training data.</font></strong></p>
<p>We’re done! We’ve got a nice closed form distribution over functions conditioned on some data.</p>
</section>
<section class="level2" id="actually-fitting-a-regression-model">
<h2 class="anchored" data-anchor-id="actually-fitting-a-regression-model">Actually Fitting a Regression Model</h2>
<p>Using our conditional distribution above, we can plug in our known house costs to create a conditional distribution of functions. Then we can sample from this distribution at, say, 50 evenly spaced distance from 0 to 12 miles away from the nuclear plant.</p>
<p>We have 15 known cost data points. This means that <img src="https://latex.codecogs.com/png.latex?%5Ctextbf%7Bc%7D_%7B%5Ctext%7Bknown%7D%7D"/> and <img src="https://latex.codecogs.com/png.latex?%5Ctextbf%7Bx%7D_%7B%5Ctext%7Bknown%7D%7D"/> is are vectors of length 15. We want to predicted prices at 50 evenly spaced points, so <img src="https://latex.codecogs.com/png.latex?%5Ctextbf%7Bx%7D"/> and <img src="https://latex.codecogs.com/png.latex?%5Ctextbf%7Bc%7D"/> are vectors of length 50.</p>
<p>Plugging this into the conditional distribution above, we can now sample from the conditional distribution of functions:</p>
<blockquote class="blockquote">
<p>Note: we’re using the RBF kernel here with parameter <img src="https://latex.codecogs.com/png.latex?%E2%84%93"/> set to 1.0.</p>
</blockquote>
<p class="mb-feed-note"><em><a href="https://www.lukeschiefelbein.com/blog/gaussian-processes/">Interactive figure: open the post to see it.</a></em></p>
<p>Yeah baby. Now <em>this</em> looks like a Gaussian process regression. Clearly the samples from the distribution are conditioned on known data, because all the functions we sample pass through the known data points. But in between the known data points the functions are free to somewhat randomly vary, giving us an idea of the uncertainty. How smoothly the functions vary is determined by the covariance function, which in this case is the RBF kernel.</p>
<p>In the plot below, I reveal the true underlying function I used to generate this “housing data” (pink). What if we take 500 samples from this posterior distribution?</p>
<p class="mb-feed-note"><em><a href="https://www.lukeschiefelbein.com/blog/gaussian-processes/">Interactive figure: open the post to see it.</a></em></p>
<p>Remember what there blue lines are: they are samples from a multivariate gaussian conditioned on the known data points, and sampled at 500 evenly spaced points between 0 and 12. We used the complicated formula above to find the conditional mean vector and conditional covariance matrix, and then sampled from a distribution using that mean and covariance.</p>
<p>Below are some heatmaps of the <em>conditional</em> covariance matrix and <em>conditional</em> mean vector (conditioned on the known housing data) that specify the predictive multivariate gaussian distribution.</p>
<p>Think for a moment about why this conditional mean and covariance makes sense.</p>
<p class="mb-feed-note"><em><a href="https://www.lukeschiefelbein.com/blog/gaussian-processes/">Interactive figure: open the post to see it.</a></em></p>

</section>
<section class="level2" id="outro">
<h2 class="anchored" data-anchor-id="outro">Outro</h2>
<p>You should now have the core ideas of Gaussian processes regression. If you still have some questions, like:</p>
<ul>
<li>“Do you really have to sample 100s of functions to get the confidence intervals?”
<ul>
<li>(You should now be able to figure out the answer to this.)</li>
</ul></li>
<li>“How do we choose a kernel function?”</li>
<li>“How do we choose the best parameters for the kernel function?”</li>
<li>“What if the training data is intrinsically noisy (the price of houses has some variance at a given location)”</li>
<li>“What if there are many features in my training data? E.g. Square footage of houses.”</li>
<li>“But I heard GPs are expensive to train?”</li>
</ul>
<p>…then you should check out the resources below, which should be easier to understand now that you have the basics.</p>
</section>
<section class="level2" id="references">
<h2 class="anchored" data-anchor-id="references">References</h2>
<section class="level3" id="tutorials-and-guides">
<h3 class="anchored" data-anchor-id="tutorials-and-guides">Tutorials and Guides</h3>
<ul>
<li><p><strong><a href="https://peterroelants.github.io/posts/gaussian-process-tutorial/">Gaussian Process Tutorial</a></strong> - Really awesome set of blog posts that teaches GPs using Python. I basically took this post and made it more verbose. The other posts in the series go into more detail about the process of fitting a GP and optimizing the kernel and hyperparameters.</p></li>
<li><p><strong><a href="https://distill.pub/2019/visual-exploration-gaussian-processes/">Visual Exploration of Gaussian Processes</a></strong> - More detailed, with beautiful interactive visualizations. Explores other non-RBF kernel functions more.</p></li>
<li><p><strong><a href="https://www.dominodatalab.com/blog/fitting-gaussian-process-models-python">Fitting Gaussian Process Models in Python</a></strong> - Practical guide focused on implementing GPs using scikit-learn and other Python libraries.</p></li>
</ul>
</section>
<section class="level3" id="advanced-reading">
<h3 class="anchored" data-anchor-id="advanced-reading">Advanced Reading</h3>
<ul>
<li><strong><a href="http://gaussianprocess.org/gpml/chapters/">Gaussian Processes for Machine Learning</a></strong> - <em>The book</em> on GPs, with all the detail you’ll ever need.</li>
</ul>
</section>
</section>]]></description>
  <category>statistics</category>
  <category>machine learning</category>
  <category>interactive</category>
  <guid>https://www.lukeschiefelbein.com/blog/gaussian-processes/</guid>
  <pubDate>Wed, 12 Feb 2025 00:00:00 GMT</pubDate>
  <media:content url="https://www.lukeschiefelbein.com/blog/gaussian-processes/gp-posterior.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Five Levels Of (Bioinformatics) Programming</title>
  <link>https://www.lukeschiefelbein.com/blog/biology-rust/</link>
  <description><![CDATA[<section class="level1" id="level-1-python">
<h1>Level 1: Python</h1>
<p>Lately I’ve been getting interested in bioinformatics. While most of my statistics and machine learning skills transfer pretty seamlessly to this new domain, I’ve also been learning more bioinformatics by doing <a href="https://rosalind.info/">Rosalind problems</a>. Rosalind is like <a href="https://projecteuler.net/archives">Project Euler</a> with a biology focus. I started out solving the problems in Python, the language I know best.</p>
<p>For example, in the second Rosalind <a href="http://rosalind.info/problems/rna/">problem</a> we’re asked to write a function that transcribes DNA to RNA. Here’s my Python solution:<sup>1</sup></p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># python hand-rolled</span></span>
<span id="cb1-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> transcribe(dna: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb1-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">''</span>.join([<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"U"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> char <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'T'</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> char <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> char <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> dna])</span></code></pre></div></div>
<hr/>
</section>
<section class="level1" id="level-2-rust">
<h1>Level 2: Rust</h1>
<p>I realized this would be a perfect opportunity to try out <a href="https://www.rust-lang.org/">Rust</a>, which is supposed to make writing extremely fast code safer and easier.</p>
<section class="level3" id="if-you-already-know-what-rust-is-skip-this">
<h3 class="anchored" data-anchor-id="if-you-already-know-what-rust-is-skip-this">If You Already Know What Rust Is, Skip This</h3>
<p>Rust is a “low-level language with high-level abstractions.” It’s supposed to be as fast and as fine-grained as C, but more “ergonomic” (less code to write because of the said high-level abstractions) and also much safer from bugs.</p>
<p>Rust is fast partially because it’s not garbage collected. Instead, the compiler helps you manually manage memory. To this end it requires you, the programmer, to write some extra bookkeeping annotations that say which functions are using which variables and when (so-called “ownership” with “borrowing” and “lifetimes”). I’ve seen this called “semiautomatic memory management” (as opposed to C/C++ which are fully manual; the compiler doesn’t stop you from making mistakes). This is why people often complain about “fighting the compiler” with Rust: it’s always pointing out inconsistencies in your variable management (in addition to all the usual type errors you get from more typical compilers). That said, once something does compile, you should have a memory-safe, robust, and probably very fast program.</p>
<p>There are also other low-level complexities of the language. For example, there is not one <code>int</code> type but <em>eight</em>, depending on if you want the integer to be signed and how many bits you want to represent it with. One has to understand the basics of stack versus heap memory. Strings are <a href="https://fasterthanli.me/articles/working-with-strings-in-rust">very complicated</a>. And so on.</p>
<p>The <a href="https://doc.rust-lang.org/book/">Rust book</a> is a great resource for learning the language. Rust has a steep learning curve, but I’ve also not really done low-level programming before, so a lot of the effort for me was learning <em>that</em>. Overall I’ve found Rust is like bowling with the bumpers: it can be maddening to be always bouncing back and forth down the lane, but eventually I always knock some pins over, as opposed to C++ where I’d probably never trust my code to run in a production environment, especially one as correctness-critical as biology.</p>
</section>
<section class="level2" id="rust-transcribe">
<h2 class="anchored" data-anchor-id="rust-transcribe">Rust <code>.transcribe</code></h2>
<p>The Rust implementation of <code>.transcribe</code> is pretty similar to the Python one (especially since I used Python type hints), except for the <code>&amp;</code> borrow notation and the <code>.collect</code> one has to explicitly call on the lazily evaluated <code>.chars</code> iterator.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb2-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// rust hand-rolled</span></span>
<span id="cb2-2"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">#[</span>pyfunction<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb2-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">fn</span> transcribe(dna<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">String</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb2-4">    dna</span>
<span id="cb2-5">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>chars()</span>
<span id="cb2-6">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>map(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">'T'</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">'U'</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span>)</span>
<span id="cb2-7">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>collect()</span>
<span id="cb2-8"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
<p>Additionally, I added the decorator-looking <code>#[pyfunction]</code> thing (actually a Rust macro) from the <a href="https://github.com/PyO3/pyo3">PyO3 Rust package</a> which makes it really easy to use Rust with Python<sup>2</sup>. You (more or less) just add such a prefix, and then you can call the Rust function from Python like so:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># calling rust functions from python</span></span>
<span id="cb3-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> bio_lib_string_rs</span>
<span id="cb3-3"></span>
<span id="cb3-4">dna <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ACTGACTC"</span></span>
<span id="cb3-5">bio_lib_string_rs.transcribe(dna)</span></code></pre></div></div>
<p>Since the Rust version is callable from Python, I could easily wrap both implementations in <a href="https://github.com/cyniphile/rosalind/blob/main/tests/test_benchmark.py">pytest benchmarks</a> for an initial speed test on a ~1000bp DNA string. I got the following results: <img class="img-fluid" src="https://www.lukeschiefelbein.com/blog/biology-rust/2021-11-09-17-54-55.png"/></p>
<p>Woot. The Rust version is over 17x faster<sup>3</sup>, including the overhead of parsing the Python DNA string into a Rust string. I also ran a <a href="https://github.com/cyniphile/rosalind/blob/04885c9644e1cff2287a43dce94763e80f482c39/bio-lib-string-rs/src/lib.rs#L195">pure Rust benchmark on the same data</a> (no Python involved) and Rust was over 50x faster than Python.</p>
</section>
<section class="level2" id="more-thorough-performance-comparisons">
<h2 class="anchored" data-anchor-id="more-thorough-performance-comparisons">More Thorough Performance Comparisons</h2>
<p>Now some of you Pythonistas might be foaming at the mouth and swearing at your screen right now, because the way I implemented <code>transcribe</code> wasn’t very Pythonic. I hand-rolled the following function:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># python hand-rolled </span></span>
<span id="cb4-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> transcribe(dna: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb4-3">   <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">''</span>.join([<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"U"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> char <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'T'</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> char <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> char <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> dna])</span></code></pre></div></div>
<p>when I could have just used Python’s built-in <code>.replace</code> function:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># python built-in</span></span>
<span id="cb5-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> transcribe_builtin(dna: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb5-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> dna.replace(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"T"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"U"</span>)</span></code></pre></div></div>
<p>Sure, Rust is a lot faster if we compare apples-to-apples implementations of the same algorithm, but that’s not quite fair in this case because you’d never actually use the hand-rolled implementation in Python, or even Rust for that matter (which also has a built-in <code>.replace</code>). So I <a href="https://github.com/cyniphile/rosalind/blob/main/tests/test_benchmark.py">benchmarked the built-ins</a> as well:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb6-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// rust built-in</span></span>
<span id="cb6-2"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">#[</span>pyfunction<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb6-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">fn</span> transcribe_builtin(dna<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">String</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-4">    dna<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>replace(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"T"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"U"</span>)</span>
<span id="cb6-5"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
<p>And also the Python <code>numpy</code> package built-in:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># numpy </span></span>
<span id="cb7-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> np</span>
<span id="cb7-3"></span>
<span id="cb7-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> transcribe_np(dna: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb7-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(np.char.replace(dna, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"T"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"U"</span>))  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># type: ignore</span></span></code></pre></div></div>
<p>I also set up some <a href="https://github.com/cyniphile/rosalind/blob/f8f0c3b89a34f269cdbce05e74fed93c198ace35/bio-lib-string-rs/src/lib.rs#L213">separate benchmarks</a> of the Rust functions called directly in Rust, no Python or PyO3 involved. Here are the results on the 1kbp file:</p>
<p><img class="img-fluid" src="https://www.lukeschiefelbein.com/blog/biology-rust/2021-11-09-18-22-16.png"/></p>
<p>The Python built-in <code>.replace</code> function is actually the fastest by far. It’s over twice as fast as my Rust function and the Rust <code>.replace</code> built-in.</p>
<p>This sort of makes sense since Python’s <code>.replace</code> is actually just <a href="https://github.com/python/cpython/blob/5f9247e36a0213b0dcfd43533db5cf6570895cfd/Objects/stringlib/transmogrify.h#L678">a highly optimized C function</a>, though it’s still surprising that the Rust <code>.replace</code> built-in is a lot slower<sup>4</sup>.</p>
<p>The same ranking holds over different sizes of data, though Numpy seems to eventually overcome some fixed initialization overhead.</p>
<p><img class="img-fluid" src="https://www.lukeschiefelbein.com/blog/biology-rust/2021-11-09-17-27-36.png"/> This plot was made using a <a href="https://github.com/nschloe/perfplot">perfplot</a>-based Python script, so the pure Rust functions weren’t included.</p>
</section>
<section class="level2" id="actually-speeding-something-up">
<h2 class="anchored" data-anchor-id="actually-speeding-something-up">Actually Speeding Something Up</h2>
<p>I decided to try out a more domain-specific bioinformatics task that isn’t already a Python built-in. This next Rosalind problem is to <a href="http://rosalind.info/problems/revp/">identify reverse palindromes</a> in a DNA sequence, Python answer below:<sup>5</sup></p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@dataclass</span></span>
<span id="cb8-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> PalindromeLocation:</span>
<span id="cb8-3">    start_index: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span></span>
<span id="cb8-4">    length: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span></span>
<span id="cb8-5"></span>
<span id="cb8-6"></span>
<span id="cb8-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> find_reverse_palindromes(seq: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> List[PalindromeLocation]:</span>
<span id="cb8-8">    min_len <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span></span>
<span id="cb8-9">    max_len <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span></span>
<span id="cb8-10">    locations <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb8-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(seq) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> min_len <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb8-12">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> length <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(min_len, max_len <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>):</span>
<span id="cb8-13">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> length <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(seq):</span>
<span id="cb8-14">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">continue</span></span>
<span id="cb8-15">            test_seq <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> seq[i:(i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> length)]</span>
<span id="cb8-16">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> is_reverse_palindrome(test_seq):</span>
<span id="cb8-17">                locations.append(</span>
<span id="cb8-18">                    PalindromeLocation(start_index<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, length<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>length)</span>
<span id="cb8-19">                )</span>
<span id="cb8-20">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> locations</span>
<span id="cb8-21"></span>
<span id="cb8-22"></span>
<span id="cb8-23"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> is_reverse_palindrome(seq: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>:</span>
<span id="cb8-24">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> seq <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> reverse_complement_dna(seq)</span>
<span id="cb8-25"></span>
<span id="cb8-26"></span>
<span id="cb8-27"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> reverse_complement_dna(dna_seq: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb8-28">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">''</span>.join([dna_base_complement(b) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> b <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> dna_seq[::<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]])</span>
<span id="cb8-29"></span>
<span id="cb8-30"></span>
<span id="cb8-31"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> dna_base_complement(base: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>: </span>
<span id="cb8-32">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> base <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A"</span>:</span>
<span id="cb8-33">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"T"</span></span>
<span id="cb8-34">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> base <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"T"</span>:</span>
<span id="cb8-35">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A"</span></span>
<span id="cb8-36">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> base <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"G"</span>:</span>
<span id="cb8-37">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"C"</span></span>
<span id="cb8-38">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> base <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"C"</span>:</span>
<span id="cb8-39">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"G"</span></span>
<span id="cb8-40">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb8-41">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Exception</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Non-DNA base </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\"</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> found."</span>.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">format</span>(base))</span></code></pre></div></div>
<p>The Rust implementation is very similar but uses a more functional style (<code>.fold</code> instead of an outer <code>for</code> loop)<sup>6</sup>.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb9-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">#[</span>pyclass<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb9-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">struct</span> PalindromeLocation <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb9-3">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">#[</span>pyo3<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">(</span>get<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> set<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">)]</span></span>
<span id="cb9-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pub</span> start_index<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">usize</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb9-5">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">#[</span>pyo3<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">(</span>get<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> set<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">)]</span></span>
<span id="cb9-6">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pub</span> length<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">usize</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb9-7"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb9-8"></span>
<span id="cb9-9"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">#[</span>pyfunction<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb9-10"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">fn</span> find_reverse_palindromes(seq<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Vec</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>PalindromeLocation<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb9-11">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> min_len <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb9-12">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> max_len <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb9-13">    seq<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>chars()</span>
<span id="cb9-14">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>take(seq<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>len() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> min_len <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb9-15">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>enumerate()</span>
<span id="cb9-16">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>fold(<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Vec</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span>new()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">mut</span> acc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> (i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> _)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb9-17">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> length <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (min_len<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">..</span>(max_len <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>step_by(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb9-18">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> length <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> seq<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>len() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb9-19">                    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">continue</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb9-20">                <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb9-21">                <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> test_seq <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>seq[i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">..</span>(i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> length)]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb9-22">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> is_reverse_palindrome(test_seq) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb9-23">                    acc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>push(PalindromeLocation <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb9-24">                        start_index<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb9-25">                        length<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb9-26">                    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb9-27">                <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb9-28">            <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb9-29">            acc</span>
<span id="cb9-30">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span>)</span>
<span id="cb9-31"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb9-32"></span>
<span id="cb9-33"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">fn</span> is_reverse_palindrome(seq<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">bool</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb9-34">    seq <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> reverse_complement_dna(seq)</span>
<span id="cb9-35"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb9-36"></span>
<span id="cb9-37"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">fn</span> reverse_complement_dna(dna_seq<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">String</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb9-38">    dna_seq<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>chars()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>rev()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>map(dna_base_complement)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>collect()</span>
<span id="cb9-39"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb9-40"></span>
<span id="cb9-41"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">fn</span> dna_base_complement(base<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">char</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">char</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb9-42">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">match</span> base <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb9-43">        <span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">'A'</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">'T'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb9-44">        <span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">'T'</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">'A'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb9-45">        <span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">'G'</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">'C'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb9-46">        <span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">'C'</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">'G'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb9-47">        _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">panic!</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Non-DNA base </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\"</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">{}</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\"</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> found."</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> base)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb9-48">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb9-49"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
<p>And I also added a small Python wrapper to map the Rust <code>PalindromeLocation</code> <code>struct</code> to the Python <code>dataclass</code>, which adds more language interface overhead<sup>7</sup>.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> find_reverse_palindromes_rs(seq: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> List[PalindromeLocation]:</span>
<span id="cb10-2">    ps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> bio_lib_string_rs.find_reverse_palindromes(seq)</span>
<span id="cb10-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [ </span>
<span id="cb10-4">        PalindromeLocation(</span>
<span id="cb10-5">            start_index<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>p.start_index,</span>
<span id="cb10-6">            length<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>p.length</span>
<span id="cb10-7">        )</span>
<span id="cb10-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> p <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> ps </span>
<span id="cb10-9">    ]</span></code></pre></div></div>
<p>I also made a Python implementation that uses Numpy arrays <a href="https://github.com/cyniphile/rosalind/blob/04885c9644e1cff2287a43dce94763e80f482c39/bio-lib-py/bio_lib_py/bio_lib.py#L82">(see the repo for details)</a>.</p>
<p>How did things pan out this time?</p>
<p><img class="img-fluid" src="https://www.lukeschiefelbein.com/blog/biology-rust/output.png"/></p>
<p>Rust is about 15x faster than base Python, even with all the conversion overhead! Was it worth the effort? I’d say “yes!” Writing these relatively simple Rust functions is frankly pretty easy (though Rust definitely can get a lot harder). The PyO3 crate makes it straightforward to incrementally add the extra “Rust thrust” (new viral hashtag?) when you need it. This wasn’t without paper-cuts or head-scratchers, but if this was for heavily reused code (part of a data pipeline?), it’s well worth the price of implementation.<sup>8</sup></p>
<hr/>
</section>
</section>
<section class="level1" id="level-3-algebraic-data-types">
<h1>Level 3: Algebraic Data Types</h1>
<p>So far I’ve just represented DNA as strings. This is bad, and here’s an example of why.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb11-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">fn</span> dna_base_complement(base<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">char</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">char</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb11-2">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">match</span> base <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb11-3">        <span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">'A'</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">'T'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb11-4">        <span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">'T'</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">'A'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb11-5">        <span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">'G'</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">'C'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb11-6">        <span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">'C'</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">'G'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb11-7">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Commented out for now...</span></span>
<span id="cb11-8">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// _ =&gt; panic!("Non-DNA base \"{}\" found.", base),</span></span>
<span id="cb11-9">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb11-10"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
<p>This Rust little function, when given a DNA base, returns the complementary base. Note I had to name it <code>dna_base_complement</code>, because it only works with DNA. If you understand basic biology, you know the output is also DNA (not RNA, or amino acids). However, none of this information is encoded in the logic of the function’s code.</p>
<p>I’ve commented out a line in the code above, and without it, the function actually doesn’t compile. Rust checks pattern matches for exhaustivity, and since <em>any</em> UTF-8 <code>char</code> can be passed into this function, I have to also handle the case where the <code>base</code> argument happens to not be “A”, “C”, “T”, or “G”.</p>
<p><img class="img-fluid" src="https://www.lukeschiefelbein.com/blog/biology-rust/2021-11-12-08-45-01.png"/></p>
<p>So I have to uncomment that last line, which is a catch-all case. Now if I somehow give a non-DNA character to our function at runtime, the program will panic (and crash if the panic isn’t handled). Say I accidentally pass in the RNA character “U”…uh oh! Thankfully I can completely eliminate the possibility of this kind of runtime error using <em>Algebraic Data Types</em> or ADTs.</p>
<p>Algebraic data types are simply types composed of other types. There are two main kinds of ADTs: product types and sum types. A product type is an AND group of types: for example tuples, <code>struct</code>s, or Python <code>dataclass</code>es. These are pretty obviously useful: sometimes you need to group diversely typed data together under one type, like a <code>user</code> type that has string <code>name</code> AND integer <code>age</code> fields.</p>
<p>The other common ADT, the sum type, was new to me, but I’ve realized it’s perhaps even more powerful and interesting<sup>9</sup>. A sum type is an XOR group of different types, so an instance can be one (and only one) type out of a set of given options. In Rust you create sum types with the <code>enum</code> keyword. For example:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb12-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">enum</span> DnaNucleotide <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb12-2">    A<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb12-3">    C<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb12-4">    G<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb12-5">    T<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb12-6"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
<p>This defines <code>DnaNucleotide</code> as a new type that can be one of four variants<sup>10</sup> <code>A</code>, <code>C</code>, <code>G</code>, or <code>T</code>. Why is this interesting? Well, now I can rewrite my <code>complement</code> function like so:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb15-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">fn</span> complement(base<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> DnaNucleotide) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> DnaNucleotide <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb15-2">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">match</span> base <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb15-3">          <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">DnaNucleotide::</span>A <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">DnaNucleotide::</span>T<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb15-4">          <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">DnaNucleotide::</span>T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">DnaNucleotide::</span>A<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb15-5">          <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">DnaNucleotide::</span>C <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">DnaNucleotide::</span>G<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb15-6">          <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">DnaNucleotide::</span>G <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">DnaNucleotide::</span>C<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb15-7">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb15-8"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
<p>Note I dropped the <code>dna_</code> prefix from the function name: I know I’m getting the complement of DNA because the <code>base</code> argument is of <code>DnaNucleotide</code> type, and so is the return type. It only accepts DNA, not RNA or “!” or “为” or “🌯”. And if I try to pass in a character I get a <em>compile time</em> error:</p>
<p><img class="img-fluid" src="https://www.lukeschiefelbein.com/blog/biology-rust/2021-11-12-09-45-14.webp"/></p>
<p>We aren’t even allowed to wire <code>complement</code> up to anything but its proper DNA input. We also get another neat exhaustivity check at compile time if we forget to handle one of the enumerated bases:</p>
<p><img class="img-fluid" src="https://www.lukeschiefelbein.com/blog/biology-rust/2021-11-12-09-40-15.png"/></p>
<p>This time I don’t have to add the catch-all <code>_ =&gt; panic!("Non-DNA base \"{}\" found.", base)</code> case because the compiler knows there can only be four different <code>DnaNucleotide</code> variants, and I’ve properly handled all of them. While this example might seem trivial, what about, say, a function from codons to amino acids? If I forget or duplicate one of the <img src="https://latex.codecogs.com/png.latex?4%5E3"/> codons (like I did below), the compiler tells us!</p>
<p><img class="img-fluid" src="https://www.lukeschiefelbein.com/blog/biology-rust/2021-11-12-09-49-10.png"/> Note in this example I’ve made use of <code>RnaNucleotide</code> and <code>AminoAcid</code> enums that I defined <a href="https://github.com/cyniphile/rosalind/blob/main/bio-lib-algebraic-rs/src/lib.rs">elsewhere in the code</a>.</p>
<p>This is also useful for easily adapting the software to work with <a href="https://en.wikipedia.org/wiki/Alloprotein#:~:text=An%20alloprotein%20is%20a%20novel,non%2Dnatural%22%20amino%20acids.&amp;text=The%20usual%20mechanisms%2C%20which%20produce,novel%20proteins%20the%20same%20way.">alloproteins</a> (proteins with non-natural amino acids) or <a href="https://en.wikipedia.org/wiki/D5SICS">artificial base pairs</a>. All I’d have to do is add another symbol to the “AminoAcid” or “DnaNucleotide” enums, and then a bunch of exhaustivity checking compiler errors will pop up wherever I now need to handle the new variant types.</p>
<section class="level2" id="speed">
<h2 class="anchored" data-anchor-id="speed">Speed</h2>
<p>Does all this organizational overhead make our code perform less efficiently? Well, theoretically it could actually make it more efficient. Strings (in both Rust and Python) are encoded in UTF-8 which uses a minimum of 8-bits per symbol. DNA has only four symbols and so only really needs 2 bits per base. Another consideration is parsing: if our DNA is saved in a file (say <a href="https://en.wikipedia.org/wiki/FASTA_format">FASTA format</a> which just uses character strings), we have to read the file <em>and</em> parse it into our internal enum representation. This means <a href="https://github.com/cyniphile/rosalind/blob/99c3fdb60985b09e9418b308d9bdae4a7657ecbe/bio-lib-algebraic-rs/src/lib.rs#L86">more code to write</a> and more computational overhead.</p>
<p>I wasn’t sure how the trade-off would play out, so I just benchmarked everything using the excellent <a href="https://github.com/bheisler/criterion.rs">criterion</a> package for Rust. I compared the original string <code>find_reverse_palindromes</code> function with one that operates on a vector of <code>DnaNucleotide</code> enums. I also timed the ADT/enum version both including the string-to-enum parsing step, and as a pre-parsed version where I only timed the palindrome searching part. Drumroll…</p>
<p><img class="img-fluid" src="https://www.lukeschiefelbein.com/blog/biology-rust/schermata-2021-11-16-alle-15-51-28.png"/></p>
<p>It looks like Rust’s promise of “zero-cost abstractions” is a lie, we are actually getting <em>negative</em> cost abstractions here! Even including the parsing overhead, the ADT-based version of our function is over twice as fast as the string version. The Rust compiler clearly takes advantage of the <code>enum</code> representation to make some key optimizations (though I couldn’t tell what these optimizations actually are when comparing the <a href="https://github.com/cyniphile/rosalind/blob/main/bio-lib-algebraic-rs/asm_output/find_reverse_palindromes_adt.asm">emitted assembly and LLVM IR</a> of the two functions. I leave that as an exercise for the reader 😃).</p>
</section>
<section class="level2" id="what-about-python">
<h2 class="anchored" data-anchor-id="what-about-python">What About Python?</h2>
<p>ADTs sort-of <a href="https://stackoverflow.com/questions/16258553/how-can-i-define-algebraic-data-types-in-python">exist in Python</a> while using <code>mypy</code> typechecking, which even offers hacky-feeling <a href="https://hakibenita.com/python-mypy-exhaustive-checking">exhaustivity checks</a>. However, Enum support is <a href="https://github.com/PyO3/pyo3/issues/834">not quite ready in PyO3</a>, so it’s not yet possible to call enum-based Rust functions from Python. To use Rust in Python, we’ll just have to wrap our ADT-style function with another function that accepts a string and pre-parses it into a vector of enums:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb16-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">#[</span>pyfunction<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb16-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">fn</span> find_reverse_palindrome_dna(seq<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Vec</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>PalindromeLocation<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb16-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> seq <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">DNA::</span>parse_string(seq)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-4">    find_reverse_palindromes(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>seq)</span>
<span id="cb16-5"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
<p>While this hack unfortunately sequesters all the nice ADT-related type checks to the Rust side of the code, at least it allows us to take advantage of the speed boost of using enums in Python.</p>
<hr/>
</section>
</section>
<section class="level1" id="level-4-parallelize">
<h1>Level 4: Parallelize</h1>
<p>So far I’ve been ignoring one of the most important speed factors in modern programming: parallelism. It’s always slightly painful to see my six-core Intel i7 running at, well, 1/6 capacity!</p>
<p>I decided to try out the newish <a href="https://github.com/ray-project/ray">ray</a> package to parallelize my Python function<sup>11</sup>. The code for finding reverse palindromes ended up being reasonably similar to the single-threaded version, though it requires an extra <code>BATCH_SIZE</code> parameter which needs be tuned to optimally slice up work into chunks:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb17-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> ray</span>
<span id="cb17-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> functools</span>
<span id="cb17-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> operator</span>
<span id="cb17-4"></span>
<span id="cb17-5"></span>
<span id="cb17-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> find_reverse_palindromes_par(seq: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> List[PalindromeLocation]:</span>
<span id="cb17-7">    BATCH_SIZE <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span></span>
<span id="cb17-8">    min_len <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span></span>
<span id="cb17-9">    max_len <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span></span>
<span id="cb17-10">    locations <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb17-11">    ray_seq <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ray.put(seq)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># type: ignore</span></span>
<span id="cb17-12"></span>
<span id="cb17-13">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@ray.remote</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># type: ignore</span></span>
<span id="cb17-14">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> is_palindrome(i: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> List[PalindromeLocation]:</span>
<span id="cb17-15">        seq <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ray.get(ray_seq)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># type: ignore</span></span>
<span id="cb17-16">        locations_inner <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb17-17">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(i, i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span>BATCH_SIZE):</span>
<span id="cb17-18">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> length <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(min_len, max_len <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>):</span>
<span id="cb17-19">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> length <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(seq):  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># type: ignore</span></span>
<span id="cb17-20">                    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">continue</span></span>
<span id="cb17-21">                test_seq <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> seq[i:(i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> length)]  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># type: ignore</span></span>
<span id="cb17-22">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> is_reverse_palindrome(test_seq):  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># type: ignore</span></span>
<span id="cb17-23">                    locations_inner.append(</span>
<span id="cb17-24">                        PalindromeLocation(start_index<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, length<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>length)</span>
<span id="cb17-25">                    )</span>
<span id="cb17-26">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> locations_inner</span>
<span id="cb17-27"></span>
<span id="cb17-28">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(seq) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> min_len <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, BATCH_SIZE):</span>
<span id="cb17-29">        locations.append(is_palindrome.remote(i))</span>
<span id="cb17-30">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> functools.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">reduce</span>( <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># flatten list of list</span></span>
<span id="cb17-31">        operator.iconcat, ray.get(locations), []  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># type: ignore</span></span>
<span id="cb17-32">    )</span></code></pre></div></div>
<p>Now let’s run this and take a look at our CPU monitor:</p>
<p><img class="img-fluid" src="https://www.lukeschiefelbein.com/blog/biology-rust/2021-11-24-16-46-34.webp"/> Yeah baby! All six cores fully engaged sir!</p>
<p>Parallelizing the Rust code turned out to be the first case where implementation is actually easier in Rust than in Python thanks to the excellent <a href="https://github.com/rayon-rs/rayon">rayon</a> package:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb18-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">fn</span> find_reverse_palindromes_par(seq<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>DNASlice) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Vec</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>PalindromeLocation<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb18-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> min_len <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb18-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> max_len <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb18-4">    seq<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>into_par_iter()</span>
<span id="cb18-5">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>take(seq<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>len() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> min_len <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb18-6">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>enumerate()</span>
<span id="cb18-7">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>fold(<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Vec</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span>new<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">mut</span> acc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> (i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> _)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb18-8">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> length <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (min_len<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">..</span>(max_len <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>step_by(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb18-9">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> length <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> seq<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>len() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb18-10">                    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">continue</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb18-11">                <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb18-12">                <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> test_seq <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>seq[i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">..</span>(i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> length)]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb18-13">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> is_reverse_palindrome(test_seq) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb18-14">                    acc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>push(PalindromeLocation <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb18-15">                        start_index<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb18-16">                        length<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb18-17">                    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb18-18">                <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb18-19">            <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb18-20">            acc</span>
<span id="cb18-21">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span>)</span>
<span id="cb18-22">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>reduce(</span>
<span id="cb18-23">            <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Vec</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span>new<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb18-24">            <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span>a<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Vec</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>PalindromeLocation<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;,</span> b<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Vec</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>PalindromeLocation<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;|</span> [a<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> b]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>concat()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb18-25">        )</span>
<span id="cb18-26"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
<p>It’s as simple as changing <code>.iter</code> to <code>.into_par_iter</code> and adding a <code>.reduce</code> function at the end to stitch together all the asynchronously returned results. Of course, it also needed to be wrapped in similar Python→string→enum/ADT wrapper functions to be useable from Python.</p>
<p>So now it’s time for the final showdown. How do all these “levels” compare speed-wise?</p>
<p><img class="img-fluid" src="https://www.lukeschiefelbein.com/blog/biology-rust/2021-11-23-22-01-06.png"/> Remember these results are all calls from Python.</p>
<p>While both parallel implementations are slower for smaller inputs (as expected), the Python version is <em>much</em> slower, and it eventually returns to being slower than sequential Python. This was because the <code>BATCH_SIZE</code> parameter needed some tuning, and after some tedious brute force experimentation, I ended up finding a slightly better value (that was still slower than single-threaded Rust). However Rust’s <code>rayon</code> performed excellently out of the box thanks to its built-in dynamic performance tuning.</p>
<p>It’s neat to see the performance gains for each of our “levels” of code improvement. In the end, I made my code safer, better organized, <em>and</em> faster by nearly two orders of magnitude. The overhead of learning Rust is certainly high, but hey, I’m already over that hump!</p>
<hr/>
</section>
<section class="level1" id="level-5">
<h1>Level 5?</h1>
<p>To avoid publication bias, I admit I also tried a “Level 5” improvement by using iterators more heavily. But, well, gather round…</p>
<p>I was discussing this project with a hacker friend who suggested modifying my functions to return iterators instead of vectors. This way, I could chain together various transformation functions lazily and only call <code>.collect</code> when needed. The compiler could then optimize the entire chain of transformations top to bottom instead of being forced to collect into vector at each step. Since I’m a data scientist with a Spark background, this suggestion made a lot of sense.</p>
<p>This is where implementing things in Rust got incredibly tricky, and frankly very unproductive. I had to switch over to Rust’s nightly build to be able to make use of experimental typing features. I had to rewrite one line functions as 25-line home-made iterator implementations. I had to really get in the mud with lifetimes, traits, and generics, leading to function signatures like:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb19-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">fn</span> transcribe<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">'a</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span>(seq<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> DnaIter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">'a</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">impl</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Iterator</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>Item <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> RnaNucleotide<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">'a</span></span>
<span id="cb19-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">where</span></span>
<span id="cb19-3">    DnaIter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">'a</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;:</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">'a</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb19-4"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb19-5">    seq<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>map(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span>b<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> transcribe_base(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>b))</span>
<span id="cb19-6"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
<p>In the end, my code was much <em>less</em> clean, though I could have possibly made it cleaner by using performance-sapping dynamic dispatch<sup>12</sup>.</p>
<p>I should have stopped and benchmarked sooner, but instead I struggled to port the entire set of bioinformatics functions I’d written so far to this iterators based implementation. <a href="https://github.com/cyniphile/rosalind/blob/main/bio-lib-rs/src/lib.rs">The entire monstrosity is available here</a>. And yes, in the end, the code was significantly (~10%) slower. See one benchmark below:</p>
<p><img class="img-fluid" src="https://www.lukeschiefelbein.com/blog/biology-rust/2021-11-24-16-12-39.png"/></p>
<p>It would have needed to be a lot faster to justify all the extra work and codebase messiness. Given it was pretty hard to find examples of people using this iterator pattern online, I’m guessing it’s pretty much an anti-pattern. The Rust compiler is clearly worse at optimizing code written this way. In the Rust community, there’s a lot of talk about “idiomatic” code, and I see the value. Unless you’re fluent in assembly and compilers (I’m not), just writing Rust idiomatically seems like an important heuristic for also getting performant Rust. In this case, I violated a universal idiom of all programming languages: “if you’re writing lots of complicated code to do simple things, you’re probably doing it wrong.”</p>
<p>I guess 4 levels of improvement was enough…</p>
</section>
<section class="level1" id="tldr">
<h1>TL;DR</h1>
<ul>
<li>Rust is faster than Python, but not necessarily for very simple things.</li>
<li>It’s pretty easy to incrementally add fast Rust functions to an existing Python codebase.</li>
<li>Use algebraic data types like <code>enum</code>s instead of strings. ADT-based code is cleaner, safer, and faster.</li>
<li>Parallelization is important, and it’s easy(er) to do in Rust.</li>
<li>Benchmark, don’t theorize. <a href="https://github.com/flamegraph-rs/flamegraph#humans-are-terrible-at-guessing-about-performance">“Humans are terrible at guessing about performance!”</a></li>
<li>Writing Rust simply and idiomatically will probably result in the fastest code (as well as the cleanest).</li>
</ul>
<p>Special thanks to <a href="https://github.com/jgavris"><span class="citation" data-cites="jgavris">@jgavris</span></a> of <a href="https://github.com/jgavris/rs-git-fsmonitor">rs-git-fsmonitor</a> fame for various tips and pointers on this post.</p>
</section>
<section class="level1" id="notes">
<h1>Notes</h1>
</section>
<section class="footnotes footnotes-end-of-document" id="footnotes">
<hr/>
<ol>
<li id="fn1"><p>DNA sequence data are stored as the coding strand (not the template strand), so “transcription” really does mean “replace T with U” not “find the RNA complement strand”↩︎</p></li>
<li id="fn2"><p>For a more detailed tutorial <a href="https://depth-first.com/articles/2020/08/10/python-extensions-in-pure-rust-with-pyo3/">see this guide</a>. Installing and using PyO3 had some paper-cuts:</p>
<ul>
<li>I needed to add <a href="https://stackoverflow.com/questions/28124221/error-linking-with-cc-failed-exit-code-1">a mysterious config</a> to get it to compile on Mac:</li>
<li>The VSCode <a href="https://marketplace.visualstudio.com/items?itemName=matklad.rust-analyzer">rust-analyzer</a> has a bug where it <a href="https://github.com/rust-analyzer/rust-analyzer/issues/6716">shows</a> fake <a href="https://stackoverflow.com/questions/65223576/what-does-the-rust-analyzer-error-could-not-resolve-macro-crateformat-args">errors</a> in PyO3 macros.<br/>
</li>
<li>With PyO3 installed, my project began to show some of Rust’s infamous slow compile times (it went from a couple of seconds ~22s).</li>
</ul>
↩︎</li>
<li id="fn3"><p>It’s important to use the super-optimized (and slow compiling) <code>--release</code> flag here. Otherwise, Rust compiles using the default fast-compiling/slow-performing <code>--debug</code> setting. Check out the performance difference for the <code>transcribe</code> function: <img class="img-fluid" src="https://www.lukeschiefelbein.com/blog/biology-rust/2021-11-09-12-46-33.png"/>↩︎</p></li>
<li id="fn4"><p>Perhaps this is because CPython is compiled with gcc, <a href="https://news.ycombinator.com/item?id=20944403">which can sometimes emit faster instructions than Rust’s LLVM-based compiler</a>. Or perhaps something else; I didn’t look into it too closely.↩︎</p></li>
<li id="fn5"><p>Yes, I know, this is not the best algorithm. That’s not the point. The point is to compare the same algorithm in Rust and Python. Side note: I wanted to try out Python 3.10’s new <a href="https://www.python.org/dev/peps/pep-0636/">pattern matching</a>, but I couldn’t install some dependencies (SciPy), so I had to go back to version 3.9.↩︎</p></li>
<li id="fn6"><p>This was the first time I ran into not-so-nice problems with PyO3. I started with a Rust implementation <a href="https://github.com/cyniphile/rosalind/blob/04885c9644e1cff2287a43dce94763e80f482c39/bio-lib-string-rs/src/lib.rs#L139">nearly identical to the Python one</a> but I ran into a weird GIL deadlock when benchmarking with <code>perfplot</code> (which apparently does some multithreaded stuff). I managed to resolve this by not having any <code>mut</code> or borrowed variables in the function body, but this was a quick hack fix. I have not yet gone deep on the <a href="https://pyo3.rs/v0.15.0/types.html?highlight=gil#gil-lifetimes-mutability-and-python-object-types">GIL and mutability</a> w.r.t. PyO3.↩︎</p></li>
<li id="fn7"><p>We could define the Python <code>dataclass</code> <a href="https://depth-first.com/articles/2020/08/10/python-extensions-in-pure-rust-with-pyo3/">purely in Rust</a>, but I wanted to simulate the effect of adding Rust to an existing Python project, where maybe you don’t want to move a class definition to Rust.↩︎</p></li>
<li id="fn8"><p>Btw, what about PyPy? I didn’t try it because it still just <a href="https://scikit-learn.org/stable/faq.html#do-you-support-pypy">don’t seem ready</a> as a viable CPython alternative for data science.↩︎</p></li>
<li id="fn9"><p><a href="https://youtu.be/FnBPECrSC7o?t=1867">This talk</a> by Ron Minsky of Jane Street Capital has some really interesting examples of using algebraic data types to write more robust code (using OCaml in the context of securities trading).↩︎</p></li>
<li id="fn10"><p>In Rust, variants of an enum aren’t actually types, so you can’t so something like <code>fn f(s: DnaNucleotide::A) {}</code>. You also can’t write polymorphic code with enums like so:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb13-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">enum</span> Nucleotide<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb13-2">    RnaNucleotide<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb13-3">    DnaNucleotide<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb13-4"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb13-5"></span>
<span id="cb13-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">fn</span> foo(s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> Nucleotide) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>complement()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;}</span></span></code></pre></div></div>
<p>even if all the variants in the enum implement a <code>.complement</code> method. Instead you have to do <a href="https://stackoverflow.com/questions/51188460/how-can-i-structure-destructure-an-enum-of-enums">some wrapper/destructuring stuff</a> (which is pretty messy) or you have to use <a href="https://doc.rust-lang.org/rust-by-example/generics/bounds.html">trait bounds</a> like so:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb14-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">trait</span> Nucleotide <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb14-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">fn</span> complement(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Self</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb14-3"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb14-4"></span>
<span id="cb14-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">fn</span> foo(s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">impl</span> Nucleotide) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>complement()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;}</span></span></code></pre></div></div>
↩︎</li>
<li id="fn11"><p>I ran into some gotchas, but the <a href="https://docs.ray.io/en/latest/ray-design-patterns/fine-grained-tasks.html">tutorials</a> were generally helpful.↩︎</p></li>
<li id="fn12"><p>I ran into basically every problem listed in <a href="https://depth-first.com/articles/2020/06/22/returning-rust-iterators/">this post</a>.↩︎</p></li>
</ol>
</section>]]></description>
  <category>programming</category>
  <category>rust</category>
  <category>biology</category>
  <guid>https://www.lukeschiefelbein.com/blog/biology-rust/</guid>
  <pubDate>Wed, 01 Dec 2021 00:00:00 GMT</pubDate>
  <media:content url="https://www.lukeschiefelbein.com/blog/biology-rust/2021-11-12-09-45-14.webp" medium="image" type="image/webp"/>
</item>
<item>
  <title>The State That Commits More Election Fraud Than Michigan (By One Popular Metric)</title>
  <link>https://www.lukeschiefelbein.com/blog/voter-fraud/</link>
  <description><![CDATA[<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img alt="fraud" class="img-fluid figure-img" src="https://www.lavocedinewyork.com/wp-content/uploads/2020/11/democracy-watch.jpg"/></p>
<figcaption>Photo: New Hampshire Public Radio. https://www.lavocedinewyork.com/wp-content/uploads/2020/11/democracy-watch.jpg</figcaption>
</figure>
</div>
<p><em>Note: I have never voted in a Presidential election, and am not registered as a member of any political party</em></p>
<p>“The 2020 election was so…2020”, that’s the tagline we’ve settled on as a nation. Joe Biden’s margin of victory was paper-thin in key states, often thinner than Trump’s margins last election. Of course, those 2016 results were widely claimed to be suspicious, which lead to an investigation that <a href="https://www.justice.gov/storage/report.pdf">uncovered interference from</a> (but <a href="https://taibbi.substack.com/p/russiagate-is-wmd-times-a-million">debunked many widely alleged theories about direct collusion with</a>) Russia.</p>
<div class="table">
<p>
<strong>Margin of Victory in Key States</strong>
</p>
<table style="width: 100%; max-width: 400px">
<thead>
<tr>
<th>
 
</th>
<th>
2020
</th>
<th>
2016
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
Wisconsin
</td>
<td class="dem">
0.62%
</td>
<td class="rep">
0.82%
</td>
</tr>
<tr>
<td>
Michigan
</td>
<td class="dem">
2.73%
</td>
<td class="rep">
0.24%
</td>
</tr>
<tr>
<td>
Georgia
</td>
<td class="dem">
0.29%
</td>
<td class="rep">
5.32%
</td>
</tr>
<tr>
<td>
Arizona
</td>
<td class="dem">
0.35%
</td>
<td class="rep">
3.78%
</td>
</tr>
<tr>
<td>
Pennsylvania
</td>
<td class="dem">
0.79%
</td>
<td class="rep">
0.75%
</td>
</tr>
</tbody>
</table>
<p>
<em>Data Source: New York Times Election Maps</em>
</p>
</div>
<p>This time it’s the same story, with the party tables turned and the focus on internal voter fraud. The Trump campaign has already <a href="https://www.telegraph.co.uk/news/2020/11/11/what-happening-trump-lawsuit-recounts-postal-votes-us-election-2020/">filed several lawsuits</a> in swing-states alleging election misconduct. Of course, anyone who’s spent time on social media has seen their share of people <a href="https://twitter.com/jackposobiec/status/1325125670599495681?s=10">wondering aloud</a> about the <a href="https://twitter.com/critica18495985/status/1325307257056342016?s=10">integrity</a> of the <a href="https://twitter.com/cernovich/status/1325168281259442184?s=10">election</a>. Meanwhile the New York Times (along with other mainstream media) claims that <a href="https://www.nytimes.com/2020/11/10/us/politics/voting-fraud.html">next-to-zero fraud occurred</a> (which is actually consistent with their <a href="https://www.nytimes.com/2016/12/18/us/voter-fraud.html">2016 views on the matter</a>).</p>
<p>Given the focus on fraud, this election has been busy for hordes of indie data pundits (like me). Some are making <a href="https://twitter.com/monkeyjones1984/status/1325596977602519040?s=10">Deep State connections</a> to hacked election machines, often in connection to the so-called <a href="https://joannenova.com.au/2020/11/cia-programs-designed-to-interfere-in-elections-hammer-and-scorecard/">Hammer and Scorecard</a> CIA election interference software that we’ve allegedly turned against ourselves. Others are just hunting for <a href="https://twitter.com/VC4351/status/1325554847031025665">oddities and anomalies</a>. One analyst made minor news when he raised over $200k via GoFundMe for investigating voting irregularities, only to have <a href="https://www.politico.com/news/2020/11/06/gofundme-removes-conservative-fundraiser-election-misinformation-434800">GoFundMe cancel his campaign</a> citing that he was engaging in “attempts to spread misleading information.”</p>
<p>One data investigation that caught my eye in particular (and the eyes of many others) was that of <a href="https://en.wikipedia.org/wiki/Shiva_Ayyadurai">Dr. Shiva Ayyadurai</a>. Dr. Ayyadurai claims to have invented email<sup>1</sup>, ran as a Republican for US senator in MA (but lost in primaries), and has almost 200k YouTube subscribers. His latest hour-long video was about a case of possible election fraud in Michigan.</p>
<div class="quarto-video ratio ratio-16x9"><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" data-external="1" frameborder="0" src="https://www.youtube.com/embed/Ztu5Y5obWPk" title=""></iframe></div>
<p>In the video, Ayyadurai outlines his team’s analysis of the Michigan counties of Oakland, Macomb, and Kent, where voting data behaves “as though an algorithm was there…that’s what we see here”</p>
<p>The video got a quarter-million views in less than 24 hours. Soon investing legend Michael Burry (Chrisian Bale’s character in <em>The Big Short</em>) was <a href="https://twitter.com/michaeljburry/status/1326416897903128577">retweeting</a>.</p>
<blockquote class="twitter-tweet blockquote">
<p dir="ltr" lang="en">
Consider these slides. Worth watching Shiva’s entire presentation for proper context, and deciding for yourself. <a href="https://twitter.com/hashtag/Election2020results?src=hash&amp;ref_src=twsrc%5Etfw">#Election2020results</a> are full of statistical anomalies that do not find easy explanation, but this is one possible explanation. <a href="https://twitter.com/hashtag/Question?src=hash&amp;ref_src=twsrc%5Etfw">#Question</a> <a href="https://twitter.com/hashtag/Election2020?src=hash&amp;ref_src=twsrc%5Etfw">#Election2020</a> <a href="https://t.co/3vpfZzqYUF">pic.twitter.com/3vpfZzqYUF</a>
</p>
— Michael Burry MD (<span class="citation" data-cites="michaeljburry">@michaeljburry</span>) <a href="https://twitter.com/michaeljburry/status/1326416897903128577?ref_src=twsrc%5Etfw">November 11, 2020</a>
</blockquote>
<p>(He deleted these tweets soon after this article’s writing)</p>
<p>I was intrigued and went down the rabbit hole myself. To get an idea for Ayyadurai’s thesis, you can <a href="https://youtu.be/Ztu5Y5obWPk?t=781">watch his video stating at 13:05</a>, but I’ll also explain his setup below.</p>
<section class="level2" id="straight-ticket-voting-an-electoral-oddity">
<h2 class="anchored" data-anchor-id="straight-ticket-voting-an-electoral-oddity">Straight Ticket Voting: An Electoral Oddity</h2>
<p>Ayyadurai’s analysis centers around an odd aspect of Michigan voting: <a href="https://en.wikipedia.org/wiki/Straight-ticket_voting">straight ticket (or straight party) voting</a>. In a few states, Michigan one of them, you can skip voting for individual candidates for president, senate, congress, and other downballot offices. Instead, you can just check one box to vote for “The Democratic Party” (or “The Republican Party”), which automatically casts your votes to the Democrat (or Republican) candidates for every office on the ballot. It’s basically a “select all” option.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img class="img-fluid figure-img" src="https://www.lukeschiefelbein.com/blog/voter-fraud/stright-ticket-ballot.jpg"/></p>
<figcaption>A ballot with a ‘straight-party’ option. https://commons.wikimedia.org/wiki/File:Larsz_-_US_election_08_(by-sa).jpg</figcaption>
</figure>
</div>
<p>Ayyadurai’s team created a data visualization by plotting the difference between the straight-ticket voting compared with the regular candidate-by-candidate voting (which I’ll refer to as “mixed-ticket”, since you can mix which parties you vote for when voting in this way). First, they organized precinct voting data as follows:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 9%"/>
<col style="width: 19%"/>
<col style="width: 37%"/>
<col style="width: 34%"/>
</colgroup>
<thead>
<tr class="header">
<th>Precinct</th>
<th>Straight Ticket Votes</th>
<th>Overall Votes (Straight and Mixed Ticket)</th>
<th>Difference, Overall vs Straight Ticket</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>precinct 1</td>
<td>60% Trump</td>
<td>68% Trump</td>
<td>8%</td>
</tr>
<tr class="even">
<td>precinct 2</td>
<td>50% Trump</td>
<td>48% Trump</td>
<td>-2%</td>
</tr>
</tbody>
</table>
<p>For each precinct in a county, they show (2nd column from the left) what percentage of straight ticket votes went to Trump. Then they calculate how the overall vote went (3rd column from left), and finally look at the difference between the two. They visualize this data with a plot like so (here showing the example data from above):</p>
<p><img class="img-fluid" src="https://www.lukeschiefelbein.com/blog/voter-fraud/example-plot.png"/></p>
<p>Their thesis is that <strong>the straight-ticket voting patterns should be similar to the overall voting patterns</strong>. So in a given precinct, if 60% of the straight-ticket votes were for Trump, Ayyadurai pretty reasonably theorizes that around 60% of the mixed party votes should also go to Trump, giving us a flat horizontal line in our plot.</p>
<p>However, when Ayyadurai looked at the actual data from several Michigan counties, he found something much different: <a href="https://youtu.be/Ztu5Y5obWPk?t=1404">a steep and highly significant downward trend</a>, implying that the more Republican a precinct is, the more it tended to flip toward Biden.</p>
<blockquote class="blockquote">
<p>It looks like his votes counts start getting linearly reduced and if you look at this curve and I’ve looked at lots of curves…anyone of us who’s in the pattern analysis business when you see this…you say something’s up. This is too structured, right? Too perfect.</p>
</blockquote>
<p>He’s right: this level of trend is something that anyone who’s done data science usually loves to see: a very (very) strong correlation between two variables.</p>
<p>What’s going on? This puzzled me too at first. Perhaps it’s caused by “Romney Republicans” that don’t like Trump? Well, Ayyadurai has an answer to that too: if Republicans showed a distaste for Trump, we still ought to see a flat chart, just shifted down. Something else is going on, and for Ayyadurai, the only option left to consider is a malicious algorithm.</p>
<blockquote class="blockquote">
<p>And remember we’ve talked about it the election systems…they have the feature of being able to manipulate vote counts from one person to another. It’s called a weighted race feature.</p>
</blockquote>
</section>
<section class="level2" id="why-are-the-most-republican-precincts-flipping-toward-biden">
<h2 class="anchored" data-anchor-id="why-are-the-most-republican-precincts-flipping-toward-biden">Why Are The Most Republican Precincts Flipping Toward Biden?</h2>
<p>That problem is, he’s gotten slightly confused about what he’s looking at. We are NOT actually comparing precincts by “how Republican they are”. We are comparing <em>straight-ticket results</em> to overall results. This is a small but very important difference. Why? When someone goes to the polls in Michigan, they first choose to either vote straight-ticket or mixed-ticket. <strong>And they don’t pick randomly</strong>. This is what’s called a <a href="https://en.wikipedia.org/wiki/Selection_bias#:~:text=Selection%20bias%20is%20the%20bias,to%20as%20the%20selection%20effect.">selection bias</a> in data analysis.</p>
<p>The voters that show up to the polls and check the “all Democrats” or “all Republicans” box are, by definition, more partisan voters than the voters that decide to pick and choose between both parties. If a precinct’s straight-ticket votes (the votes from the <em>most</em> partisan voters in the area) go very Trump, then yes, you would expect the definitionally <em>less</em> partisan voters in the area (the mixed-ticket voters) to vote less strongly that way.</p>
<p>But won’t some Biden supporters switch to Trump too? Yes, but not as many, because in a very Republican area Trump would have more to lose and less to gain. Let’s go through a quick example.</p>
<p>Say a precinct has 1000 constituents: 800 Republican, 200 Democrat. If 10% of the Republicans decide to flip, Trump loses 80 votes. If 10% of the Democrats decide to flip, Trump gains 20 votes, for a net loss of 60 votes. Given the selection bias at work, most of these flipping voters are going to do a mixed-ticket vote…if you are a Republican that hates Trump, you probably still want to vote GOP for Senate, etc.</p>
<p><img class="img-fluid" src="https://www.lukeschiefelbein.com/blog/voter-fraud/rep-precinct-example.png"/></p>
<p>Ayyadurai would say here, 60 votes <em>got moved</em> to Biden. But it’s actually just that mixed-ticket voters are less likely (by definition) to vote with precinct norms, and will tend to move overall results back towards the center.</p>
<p>All of this works the same way for more Democratic precincts; just do the same math but switch “Republican” and “Democrat”, and “Trump” and “Biden”. And if a precinct is split 50/50, there flipping votes net to zero. How does all this look on the precinct plots we’ve been looking at?</p>
<p><img class="img-fluid" src="https://www.lukeschiefelbein.com/blog/voter-fraud/all-flipping.webp"/></p>
<p>It’s a nice downward sloping line. I’ve only done three data points, but you can see how the rest of the line fills in depending on how Republican or Democrat a precinct is. You can get a steeper line by increasing the percentage of mixed voters that flip (I used a rate of 20% in the example above), or shift the line up or down by making the flip percentage assymentical (e.g. 20% of Republicans flip, but only 10% of Democrats do). Add in some random variation, and this is exactly what we’re seeing in Dr. Shiva’s Michigan chart. It’s theoretically totally normal<sup>2</sup>.</p>
</section>
<section class="level2" id="do-real-elections-actually-work-this-way">
<h2 class="anchored" data-anchor-id="do-real-elections-actually-work-this-way">Do Real Elections Actually Work This Way?</h2>
<p>So we’ve got ourselves a theory about why these downward trends might exist, no fraud required. Let’s now do what Dr. Shiva didn’t do and look at more data. If our theory is right, we’d expect most counties to show this downward trend, and if Dr. Shiva’s is right, this downward trend should almost never appear.</p>
<p>Remember, only a few states do straight-ticket voting, and it turned out to be either quite a pain or impossible to get at their data. <a href="http://ok.gov/">Ok.gov</a> didn’t seem to work at all, much less have a detailed voting records API. Yikes.</p>
<p>Of course, we do know Michigan’s data is available, so lets take the first minimal step of verification Dr. Shiva didn’t bother to take: look at the same “suspicious” counties in the 2016 election, when Trump won Michigan. Is 2020 really an anomaly? Let’s look at Oakland county in 2016:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img class="img-fluid figure-img" src="https://www.lukeschiefelbein.com/blog/voter-fraud/oakland-county-2016-small.png"/></p>
<figcaption>Data: https://results.enr.clarityelections.com/MI/Oakland/63990/184040/en/summary.html.</figcaption>
</figure>
</div>
<p>It’s the same super statistically significant downward linear trend. It’s not as steep as in 2020, but still equally “suspicious” according to Ayyadurai’s theory. What about the following counties:</p>
<p><img class="img-fluid" src="https://www.lukeschiefelbein.com/blog/voter-fraud/randolph.png"/> <img class="img-fluid" src="https://www.lukeschiefelbein.com/blog/voter-fraud/jackson.png"/> <img class="img-fluid" src="https://www.lukeschiefelbein.com/blog/voter-fraud/walker.png"/> <img class="img-fluid" src="https://www.lukeschiefelbein.com/blog/voter-fraud/autauga.png"/> <img class="img-fluid" src="https://www.lukeschiefelbein.com/blog/voter-fraud/geneva.png"/></p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img class="img-fluid figure-img" src="https://www.lukeschiefelbein.com/blog/voter-fraud/fayette.png"/></p>
<figcaption>Data: https://www.sos.alabama.gov/alabama-votes/voter/election-data</figcaption>
</figure>
</div>
<p>Do these charts get your pattern researcher taste-buds salivating? Ready to take to the Twitters and yell fraud? Before you do, I should say this is data from Randoph, Jackson, Walker, Autauga, Geneva, and Fayette counties…in Alabama (2016 election)<sup>3</sup>. This is obviously not the first state that comes to mind when you think of election hacking targets; the state hasn’t voted for a Democrat president since Jimmy Carter in ’76.</p>
<p>I admit, I cherry-picked some of the “worst” counties above, but the same trend holds throughout. Running Ayyadurai’s analysis on all Alabama counties in 2016, <strong>70% of them showed a negative, statistically significant slope</strong> (in other words β &lt; 0 and p &lt; 0.05). In fact, only 13 of the 67 counties were “normal” by Dr. Shiva’s definition, showing no significant trend either way.</p>
</section>
<section class="level2" id="sonow-what">
<h2 class="anchored" data-anchor-id="sonow-what">So…Now What?</h2>
<p>Now we are at a crossroads. We’ve got a theory for why such patterns could exist completely legitimately, and the data shows these “suspicious” patterns seem to be the norm.</p>
<p><img class="img-fluid" src="https://www.lukeschiefelbein.com/blog/voter-fraud/meme.jpg"/></p>
<p>I’d go with “there was no fraud” (at least of this kind). Dr. Shiva essentially cooked up a graph of “do mixed-party voters vote less partisan than fully partisan voters?” It’s almost a tautology, and if considered carefully, you’d expect a trend. I don’t know if my explanation is 100% correct, but it’s certainly much better supported by the data than Dr. Shiva’s theory that “trends are anomalous and signify fraud.”</p>
<p>Is Dr. Shiva lying to us then? Probably not. The selection bias he overlooked is easy to miss, and even the best data gurus out there make mistakes in identifying them.</p>
<p>Was Dr. Shiva doing good science and just happened to mess up? No, absolutely not. He and his team didn’t do even the most basic checks to support their theory, i.e. the checks I did above with 2016 election data (by myself in less than a day’s work). Even so, they decided to go ahead and claim there was a clear sign of fraud in Michigan, and publish this to their 200k trusting subscribers. Nothing could be more scientifically irresponsible, especially for a man that claims over and over again to be following the principles of science. Now I’m starting to wonder…did he really invent email?</p>
<p><em>Code and data used to generate this analysis can be <a href="https://github.com/cyniphile/vote-investigation">found on Github</a></em>.</p>
</section>
<section class="footnotes footnotes-end-of-document" id="footnotes">
<hr/>
<ol>
<li id="fn1"><p>The invention of email is generally credited to Ray Tomlinson around 1970, though Ayyadurai does hold <a href="https://techland.time.com/2011/11/15/the-man-who-invented-email/">the first copyright</a> to the term dated 1982.↩︎</p></li>
<li id="fn2"><p>We are making a couple of big assumptions here: 1) that the ratio of split-ticket/mixed-ticket votes is uncorrelated with a precinct’s party leaning. 2) that the proportion of voters who flip is uncorrelated with a precinct’s party leaning. If either assumption is violated in the right way, you could see flat graphs like Dr. Shiva is showing. Ultimately, we simply need to look to the data to see if graphs with lots of slope are the norm or not.↩︎</p></li>
<li id="fn3"><p>Alabama was the only state I could find with both straight-ticket voting and (sort of) readily available voting data. 2020 data was not yet available at the time of this writing. If you find more, let me know!↩︎</p></li>
</ol>
</section>]]></description>
  <category>election fraud</category>
  <category>politics</category>
  <guid>https://www.lukeschiefelbein.com/blog/voter-fraud/</guid>
  <pubDate>Thu, 12 Nov 2020 00:00:00 GMT</pubDate>
  <media:content url="https://www.lukeschiefelbein.com/blog/voter-fraud/stright-ticket-ballot.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>The Slant Fueling The Abortion War Must End</title>
  <link>https://www.lukeschiefelbein.com/blog/abortion/</link>
  <description><![CDATA[<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img alt="abortion" class="img-fluid figure-img" src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/9e/Abortion_Hurts_Women_%2832676869635%29.jpg/1280px-Abortion_Hurts_Women_%2832676869635%29.jpg"/></p>
<figcaption>Which sign makes you angry? Photo: James McNellis from Washington, DC, United States. https://commons.wikimedia.org/wiki/File:Abortion_Hurts_Women_(32676869635).jpg)</figcaption>
</figure>
</div>
<p>Election time is upon us and abortion is both as important and as divisive as ever. The debate over Trump’s nomination of Amy Coney Barret to the Supreme Court revolved heavily around her probable pro-life<sup>1</sup> stance. Meanwhile, in the first presidential debate, Joe Biden explicitly left open the possibility of <a href="https://edition.cnn.com/2020/09/30/politics/joe-biden-court-packing/index.html">packing the court</a> in response.</p>
<p>Abortion comes with such a permissive license to righteous anger that most people avoid the festering topic. No wonder a <a href="https://news.gallup.com/poll/1576/abortion.aspx">Gallup poll</a> indicates that in the past 25 years the American people have only gotten more divided on the issue, of late jittering around a 50-50 split between pro-life and pro-choice. Facebook feeds are crawling with memes about Bible-thumping, misogynistic white men, or organ harvesting Mengeles at Planned Parenthood. In the abortion debate, once you choose a side, the 150 million people in this country with the opposing view suddenly become some mix of stupid and Satan.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img alt="abortion split" class="img-fluid figure-img" src="https://content.gallup.com/origin/gallupinc/GallupSpaces/Production/Cms/POLL/902aelfr70k9wvxxkig2fg.png"/></p>
<figcaption>Chart: https://news.gallup.com/poll/1576/abortion.aspx</figcaption>
</figure>
</div>
<p>Like all politics, the issue of abortion has suffered from the mainstream media polarization described so well in Matt Taibbi’s book <a href="https://www.amazon.com/Hate-Inc-Todays-Despise-Another/dp/1949017257">Hate Inc.</a> The book’s thesis is simple: hate sells. Rage is no longer just a toxic byproduct of our media; it’s the new unnatural life-blood. Now most publications have morphed from forums of fruitful public discourse into profitable echo chambers.</p>
<blockquote class="twitter-tweet blockquote">
<p dir="ltr" lang="en">
"We live in a time of incredible political division… It’s become difficult to have an argument in the traditional sense. People with differing opinions are often no longer even working from the same commonly-accepted set of facts." <a href="https://t.co/PBREDO6vgd">https://t.co/PBREDO6vgd</a>
</p>
— Matt Taibbi (<span class="citation" data-cites="mtaibbi">@mtaibbi</span>) <a href="https://twitter.com/mtaibbi/status/1307351126870757381?ref_src=twsrc%5Etfw">September 19, 2020</a>
</blockquote>
<p>Abortion is perhaps the ultimate case study of this communication breakdown. There’s no definitive answer to the issue’s fundamental question “when does a fetus become human?”, however abortion “debate” rarely goes so far as to discuss its own fundamental crux. This couldn’t have been illustrated more perfectly in the 2020 vice-presidential debate, with Kamala Harris proudly showing support for “for a woman’s right to make a decision about her own body” while Mike Pence “couldn’t be more proud to serve as vice president to a president who stands without apology for the sanctity of human life.”</p>
<div class="quarto-video ratio ratio-16x9"><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" data-external="1" frameborder="0" src="https://www.youtube.com/embed/_80d95hMjC4?start=386" title=""></iframe></div>
<p>Note that this missed connection comes with a district difference in terminology: “make a decision” vs “sanctity of life”. The abortion debate is riddled with such partisan wordplay that reflects each group’s very well-intentioned goals (women’s health vs fetal rights), but they also embody not-so-subtle digs at their opponents. It’s not even possible to reference the parties by name without stirring controversy. If you’re opposed to the “Pro-life” movement, as it calls itself, does that mean you are “not pro-life”? This sounds strange, and so the term “anti-abortion” is used instead, at least by detractors of the movement. By using one set of terminology you unmistakably identify your “side” and immediately either ingratiate yourself with, or aggravate, your audience.</p>
<p>For example this podcast in the New York Times: <a href="https://www.nytimes.com/2020/09/23/podcasts/the-daily/supreme-court-trump-roe-v-wade.html">“An Anti-Abortion Leader on Trump’s Supreme Court Battle”</a> (the title was later changed). A pro-life advocate couldn’t even get past the first two words of the title without feeling slant.</p>
<p>Being a data scientist, I decided to investigate usage of this slanted terminology in a more quantitative way. Knowing that certain terms like “right to life” or “reproductive rights” are almost exclusively partisan and rarely context-dependent, I decided to compile a list of such terms and do a mass search for them in thousands of recent abortion-related news articles (2732 to be exact). By assigning each slanted term a certain weight (“right-to-choose” slants pro-choice, “sanctity of life” slants pro-life) and counting them up, we can uncover which publications lean which way and how strongly. I made the results into an interactive plot below, where you can compare slant across publications and individual articles. You can also customize how terms are weighted based on your personal perception. If you have ideas for other partisan terms, post them in the comments below and I’ll try to add them to the visualization. While this setup is certainly simple<sup>2</sup>, it nonetheless provides a reasonable high-level picture of the bias extant in journalism today.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>The interactive chart for this post is no longer online.</p>
</div>
</div>
<p>Few publications are balanced, and some of the most mainstream papers are very heavy on pro-choice rhetoric. For example, only 25% of articles from the New York Times used a net right-of-center word choice. The Washington Post was much more balanced, and the Bismarck Tribune (from pro-life North Dakota) was extremely right-leaning in its relatively small sample of articles (click on the “Scatter Plot” option to see individual articles).</p>
<p>While partisan writing might function as a call-to-arms for ideological battle, “ideological battle” is an oxymoronic term. The rational brain is suppressed in confrontation, making it almost <a href="https://hbr.org/2015/12/calming-your-brain-during-conflict">physically impossible to change a person’s mind if they sense an attack</a>. The “winner” of such a “battle” is just the one with that last word, not the one who spread his or her message successfully. Is it better to incrementally rally the troops to the polls, or to flip the vast mother-lode of votes in the opposition?</p>
<p>Take the mantra “my body, my choice”. It’s easy to chant and rings proudly of a struggle for individual freedom over the implied oppressive patriarchy. And it misses the point. “It’s not a choice, it’s a child” is the memorized pro-life rebuttal. The pro-life movement is not a cabal of men in Colonel Sanders attire meeting in secret cigar lounges to strategize the next move in white male world dominance. While the religious and traditional culture that is correlated with a pro-life stance is sometimes frustratingly sexist, it’s a causation fallacy to conclude such sexism is fundamental to being pro-life. According to the same <a href="https://news.gallup.com/poll/1576/abortion.aspx">Gallup Poll</a>, 41% of women, 24% of Democrats, and 34% of those who “seldom or never” attend religious services self-describe as “pro-life”–substantial minorities.</p>
<p>But above all, attempts to “prove” that the pro-life movement is hatefully misogynistic are worse than useless: they are inflammatory. Ditto for the pro-life movement’s penchant for portraying pro-choicers as murderous eugenicists<sup>3</sup> or irresponsible sluts. Humans generally believe themselves to be good people and hold their beliefs for what they perceive as good reasons. Declaring either side (tens of millions of Americans) to be stupid and evil is simplistic and displays a gross lack of empathy, a fundamental quality needed for effective debate and negotiation according to <a href="https://www.masterclass.com/articles/how-to-use-tactical-empathy-to-negotiate">master negotiator Chris Voss.</a> Conversion requires deep trust in the converter; it’s the only way to bypass the deeply instinctual fight-or-flight response that is normal when one’s basic ideals are challenged.</p>
<p>Not every abortion advocate is a screaming nihilist femmenismo with 6-inch gauge earrings (though alt-right Twitter is fond of portraying them as such). And they are not “anti-life” but pro-life with a focus on women’s health. The outcomes of abortion can be enormously beneficial both for women<sup>4</sup> and their families (by avoiding the obvious physical, emotional, and economic trauma that go with both childbirth) and <a href="https://freakonomics.com/podcast/abortion/">society at large</a>. The cost is ending the existence of a pre-sentient being. Or at least <em>usually</em> pre-sentient.</p>
<p>Ultimately, the moral reasonableness of abortion goes up closer to conception, a fact that pro-lifers won’t admit. And the reasonableness of abortion goes down the closer to birth, a fact pro-choicers won’t admit. This idea of incrementalism was fairly, though crudely, expressed by Josh Zepps on the Joe Rogan podcast:</p>
<div class="quarto-video ratio ratio-16x9"><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" data-external="1" frameborder="0" src="https://www.youtube.com/embed/F84l0qDi-wQ?start=271" title=""></iframe></div>
<blockquote class="blockquote">
<p>[abortion has] become so polarized in the United States that both positions are bullshit, and people on both sides know that both positions are bullshit. It’s bullshit to say that it is “just a women’s health issue and has no ethical implications whatsoever” even when it’s, as you say, about cutting a woman open at nine months and stabbing the embryo in the head<sup>5</sup>. It’s also bullshit to say that the instant an egg is fertilized, that is a person that should have all of the rights to life that an adult should have and that it’s murder kill a blastocyst that’s smaller than the size of the head of a pin. Both positions are stupid. This is an incremental situation.</p>
</blockquote>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img alt="timeofabortion" class="img-fluid figure-img" src="https://content.gallup.com/origin/gallupinc/GallupSpaces/Production/Cms/POLL/b2mm7nepm0ebj1pxkar6xa.png"/></p>
<figcaption>Chart: https://content.gallup.com/origin/gallupinc/GallupSpaces/Production/Cms/POLL/b2mm7nepm0ebj1pxkar6xa.png</figcaption>
</figure>
</div>
<p>Polls show that nearly <a href="https://news.gallup.com/poll/235469/trimesters-key-abortion-views.aspx">60% of Americans are fine with first-trimester abortions</a>, even though only 46% say they are pro-life. Conversely, only 13% of respondents thought that abortion should be legal in the last trimester. Intuitively, this makes sense: early on the fetus is tiny, doesn’t look that human, and most importantly, can’t feel pain until about 24 weeks gestation. It’s easy to see why pro-life rhetoric often revolves around late-term abortions, a favorite tack of Trump himself.</p>
<blockquote class="twitter-tweet blockquote">
<p dir="ltr" lang="en">
Biden and Democrats just clarified the fact that they are fully in favor of (very) LATE TERM ABORTION, right up until the time of birth, and beyond - which would be execution. Biden even endorsed the Governor of Virginia, who stated this clearly for all to hear. GET OUT &amp; VOTE!!!
</p>
— Donald J. Trump (<span class="citation" data-cites="realDonaldTrump">@realDonaldTrump</span>) <a href="https://twitter.com/realDonaldTrump/status/1313445293912920064?ref_src=twsrc%5Etfw">October 6, 2020</a>
</blockquote>
<p>While only a small minority (around 1%) of abortions are later term (post 21 weeks gestation) and the practice is restricted in many states, abortions are indeed federally legal up to the moment of birth for any reason (a system much less restrictive than that of <a href="https://en.wikipedia.org/wiki/Abortion_in_the_United_States_by_state#Current_legal_status_nationwide">most European countries</a>). Additionally, the vast majority of these later-term abortions are <a href="https://onlinelibrary.wiley.com/doi/10.1363/4521013">not for health reasons of the mother or fetus</a>. While women seeking late-term abortions usually have understandable motives for not getting one sooner (such as lack of access, lack of money and insurance, or recent domestic abuse) this does not erase the fact that a life is being ended that is alarmingly human. Interviewed in a <a href="https://youtu.be/a6BEDSohBoY?t=1127">Science Vs</a> podcast, one abortion doctor described the process of “dilation and extraction” (known as “partial-birth abortion” in pro-life lingo) in gory detail, and that “those <a href="https://www.google.com/search?q=dilation+and+extraction+procedure&amp;sxsrf=ALeKk01J0EvU2SLMT-wZAbtyIKHz4PQkCQ:1603732178529&amp;source=lnms&amp;tbm=isch&amp;sa=X&amp;ved=2ahUKEwj3gcP239LsAhUjNOwKHdjCCEUQ_AUoAXoECA4QAw&amp;biw=1440&amp;bih=797">posters that anti-abortion groups hold around abortion clinics [link possibly NSFW]</a>, those photos in them can actually be pretty accurate.” Is it really so crazy to be very uneasy with this? When is it right to inflict pain that you cannot feel, to stop a heart that is not yours, on an obviously human form? Is a big quality-of-life improvement a good enough reason?</p>
<p>For example, families that have children with disabilities can have an increased risk of <a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4624231/">divorce</a> and increased <a href="https://researchers.mq.edu.au/en/publications/how-much-does-intellectual-disability-really-cost-first-estimates">financial struggle</a>, and disabled children also put a huge financial load on their communities for special education programs and services. From an economic and quality of life perspective, it’s a no-brainer to “terminate” this burden. Now, most pro-choice folks agree “termination” <em>after</em> birth is not morally acceptable at all, but pro-lifers justifiably see the two cases as the same thing. After all, the only difference is a few inches out of the birth canal.</p>
<p>Given the intense moral quandary, is it really beneficial to the pro-choice movement (or even morally sound in general) to die on the hill of federally unrestricted late-term abortion? Conversely, should pro-lifers be willing to die on the hill of limiting access to early-term abortion? While many hold religious beliefs that life is sacred from conception, even if it is just a few cells, is that a reasonable belief to impose on society at large? Could early-term abortions be closer to “a sin that’s legal” like contraception<sup>6</sup>?</p>
<p>These are the questions that should be discussed instead of simply claiming moral high-ground <em>a priori</em> and then shaming our opponents for not doing so also. The resulting lack of trust has left us ramming legislation down each other’s throats to the beat of partisan power cycles, always pushing for as much as possible so that as much as possible remains after the other side gets its shot at claw-backs.</p>
<p>Remember, Galileo, “the father of modern science”, wasn’t imprisoned for simply advocating that the earth revolves around the sun, which was counter to Catholic teaching at the time. It was because the book he wrote on the subject <a href="https://en.wikipedia.org/wiki/Galileo_affair">used slanted language that offended the Pope</a>, who quickly banned the book and put Galileo under house arrest. Only 400 years later did the Church finally admit fault. How much sooner could this have been resolved had Galileo been more tactful, even though he was right?</p>
<p>Likewise, if we drop contentious rhetoric and meme warfare from our abortion debate and adopt an atmosphere of trust via <a href="https://en.wiktionary.org/wiki/steelman">“steel man” argumentation</a>, perhaps we could soon be in a state similar to Western Europe…where people aren’t screaming at each other about abortion nearly as much.</p>
<section class="footnotes footnotes-end-of-document" id="footnotes">
<hr/>
<ol>
<li id="fn1"><p>Even using the term “pro-life” (instead of “anti-abortion”) can be seen as political and triggering. We’ll get to this in detail.↩︎</p></li>
<li id="fn2"><p>Data Notes. <br/></p>
<p>This is a visualization and is not meant to present scientific fact. As Henry Clay said, “Statistics are no substitute for judgment”. <br/> <br/> Data was sourced from ProQuest (via the New York Public Library) by doing a keyword search on US-based news articles that contained the word “abortion” at least twice, to filter out those that reference abortion only in passing (e.g. celebrity gossip). Some publications (e.g. Fox News, HuffPost) either are not in the ProQuest database or don’t provide access to full article text, so they aren’t represented in this dataset. I merged certain closely related publications under one label, e.g. New York Times and New York Times Magazine. Partisan terms were searched for using regular expressions, so “pro life” and “Pro-life”, etc. are all matched. Source code is available on <a href="https://github.com/cyniphile/abortion">Github</a>. <br/> <br/> There are of course some cases where the usage of these slant terms does not reflect the author’s bias, for example when the author quotes someone else or when referenced organization names include slant terms (e.g. The National Right to Life Committee). This is a limitation of this type of analysis, but not, I think, a fatal one, as even these references still in some way present the rhetoric of the other side.↩︎</p></li>
<li id="fn3"><p>Though it is true Planned Parenthood founder Margaret Sanger supported eugenics, the vast majority of pro-choice advocates today most-likely would be strongly opposed to such ideas.↩︎</p></li>
<li id="fn4"><p>The pro-life camp often asserts there are a myriad of health risks that come with abortion, especially mental health risks, though there is <a href="https://www.ansirh.org/research/turnaway-study?fbclid=IwAR2o4AOwj1SuBiqdpFJNY499e1z-203KRSgWB4IwE88ApeOASbWTHNUmU6E">little-to-no evidence</a> to support this.↩︎</p></li>
<li id="fn5"><p>Fact check: the procedure for late-term abortion (dilation and extraction) does not actually involve cutting the women open, but it does involve puncturing the fetal head <a href="https://www.npr.org/2006/02/21/5168163/partial-birth-abortion-separating-fact-from-spin">“using scissors or another sharp instrument”</a>.↩︎</p></li>
<li id="fn6"><p>In fact, “Some prominent theologians, such as John Chrysostom and Thomas Sanchez, believed that post-quickening<a href="https://en.wikipedia.org/wiki/Catholic_Church_and_abortion#Early_writings"> [late term] abortion was <bold>less</bold> sinful</a> than deliberate contraception.”↩︎</p></li>
</ol>
</section>]]></description>
  <category>abortion</category>
  <category>politics</category>
  <guid>https://www.lukeschiefelbein.com/blog/abortion/</guid>
  <pubDate>Tue, 20 Oct 2020 00:00:00 GMT</pubDate>
  <media:content url="https://www.lukeschiefelbein.com/blog/abortion/abortion-hurts-women-32676869635.jpg" medium="image" type="image/jpeg"/>
</item>
</channel>
</rss>
