<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"><head>
<meta charset="utf-8">
<meta name="generator" content="quarto-1.5.43">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>readme</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
div.columns{display: flex; gap: min(4vw, 1.5em);}
div.column{flex: auto; overflow-x: auto;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
ul.task-list li input[type="checkbox"] {
width: 0.8em;
margin: 0 0.8em 0.2em -1em;
vertical-align: middle;
}
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { display: inline-block; text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
}
pre.numberSource { margin-left: 3em; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
</style>
<script src="README_files/libs/clipboard/clipboard.min.js"></script>
<script src="README_files/libs/quarto-html/quarto.js"></script>
<script src="README_files/libs/quarto-html/popper.min.js"></script>
<script src="README_files/libs/quarto-html/tippy.umd.min.js"></script>
<script src="README_files/libs/quarto-html/anchor.min.js"></script>
<link href="README_files/libs/quarto-html/tippy.css" rel="stylesheet">
<link href="README_files/libs/quarto-html/quarto-syntax-highlighting.css" rel="stylesheet" id="quarto-text-highlighting-styles">
<script src="README_files/libs/bootstrap/bootstrap.min.js"></script>
<link href="README_files/libs/bootstrap/bootstrap-icons.css" rel="stylesheet">
<link href="README_files/libs/bootstrap/bootstrap.min.css" rel="stylesheet" id="quarto-bootstrap" data-mode="light">
</head>
<body class="fullcontent">
<div id="quarto-content" class="page-columns page-rows-contents page-layout-article">
<main class="content" id="quarto-document-content">
<p align="left">
<img src="./artefacts/simdna.svg" alt="SimDNA Logo" width="200">
</p>
<p><em>High-performance DNA/RNA sequence encoding and decoding using SIMD instructions with automatic fallback to scalar implementations.</em></p>
<p><a href="https://crates.io/crates/simdna"><img src="https://img.shields.io/crates/v/simdna.svg" class="img-fluid" alt="Crates.io"></a> <a href="https://docs.rs/simdna"><img src="https://docs.rs/simdna/badge.svg" class="img-fluid" alt="Docs.rs"></a> <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" class="img-fluid" alt="License: MIT"></a></p>
<section id="table-of-contents" class="level2">
<h2 class="anchored" data-anchor-id="table-of-contents">Table of Contents</h2>
<ul>
<li><a href="#table-of-contents">Table of Contents</a></li>
<li><a href="#features">Features</a></li>
<li><a href="#installation">Installation</a></li>
<li><a href="#iupac-nucleotide-codes">IUPAC Nucleotide Codes</a>
<ul>
<li><a href="#standard-nucleotides">Standard Nucleotides</a></li>
<li><a href="#two-base-ambiguity-codes">Two-Base Ambiguity Codes</a></li>
<li><a href="#three-base-ambiguity-codes">Three-Base Ambiguity Codes</a></li>
<li><a href="#wildcards-and-gaps">Wildcards and Gaps</a></li>
<li><a href="#bit-rotation-property">Bit Rotation Property</a></li>
</ul></li>
<li><a href="#usage">Usage</a></li>
<li><a href="#reverse-complement">Reverse Complement</a>
<ul>
<li><a href="#iupac-ambiguity-code-complements">IUPAC Ambiguity Code Complements</a></li>
</ul></li>
<li><a href="#input-handling">Input Handling</a></li>
<li><a href="#integration">Integration</a>
<ul>
<li><a href="#working-with-seq_io">Working with seq_io</a></li>
<li><a href="#working-with-noodles">Working with noodles</a></li>
<li><a href="#working-with-rust-bio">Working with rust-bio</a></li>
<li><a href="#zero-copy-integration">Zero-Copy Integration</a></li>
</ul></li>
<li><a href="#platform-support">Platform Support</a></li>
<li><a href="#performance">Performance</a></li>
<li><a href="#testing">Testing</a>
<ul>
<li><a href="#unit-tests">Unit Tests</a></li>
<li><a href="#fuzz-testing">Fuzz Testing</a></li>
</ul></li>
<li><a href="#contributing">Contributing</a></li>
<li><a href="#changelog">Changelog</a></li>
<li><a href="#citation">Citation</a></li>
<li><a href="#license">License</a></li>
</ul>
</section>
<section id="features" class="level2">
<h2 class="anchored" data-anchor-id="features">Features</h2>
<ul>
<li><strong>4-bit encoding</strong> supporting all IUPAC nucleotide codes (16 standard + U for RNA)</li>
<li><strong>Bit-rotation-compatible encoding</strong> enabling efficient complement calculation</li>
<li><strong>SIMD-accelerated reverse complement</strong> operations</li>
<li><strong>SIMD acceleration</strong> on x86_64 (SSSE3) and ARM64 (NEON)</li>
<li><strong>Static lookup tables</strong> for branch-free encoding/decoding</li>
<li><strong>Prefetch hints</strong> for improved cache utilization on large sequences</li>
<li><strong>Automatic fallback</strong> to optimized scalar implementation</li>
<li><strong>Thread-safe</strong> pure functions with no global state</li>
<li><strong>2:1 compression</strong> ratio compared to ASCII representation</li>
<li><strong>RNA support</strong> via U (Uracil) mapping to T</li>
</ul>
</section>
<section id="installation" class="level2">
<h2 class="anchored" data-anchor-id="installation">Installation</h2>
<p>Add simdna to your <code>Cargo.toml</code>:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode toml code-with-copy"><code class="sourceCode toml"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">[dependencies]</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="dt">simdna</span> <span class="op">=</span> <span class="st">"1.0.2"</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
<p>Or install via cargo:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="ex">cargo</span> add simdna</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
</section>
<section id="iupac-nucleotide-codes" class="level2">
<h2 class="anchored" data-anchor-id="iupac-nucleotide-codes">IUPAC Nucleotide Codes</h2>
<p>simdna supports the complete IUPAC nucleotide alphabet with a bit-rotation-compatible encoding scheme. This encoding enables efficient complement calculation via a simple 2-bit rotation operation.</p>
<section id="standard-nucleotides" class="level3">
<h3 class="anchored" data-anchor-id="standard-nucleotides">Standard Nucleotides</h3>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Code</th>
<th>Meaning</th>
<th>Value</th>
<th>Complement</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>A</td>
<td>Adenine</td>
<td>0x1</td>
<td>T (0x4)</td>
</tr>
<tr class="even">
<td>C</td>
<td>Cytosine</td>
<td>0x2</td>
<td>G (0x8)</td>
</tr>
<tr class="odd">
<td>G</td>
<td>Guanine</td>
<td>0x8</td>
<td>C (0x2)</td>
</tr>
<tr class="even">
<td>T</td>
<td>Thymine</td>
<td>0x4</td>
<td>A (0x1)</td>
</tr>
<tr class="odd">
<td>U</td>
<td>Uracil (RNA → T)</td>
<td>0x4</td>
<td>A (0x1)</td>
</tr>
</tbody>
</table>
</section>
<section id="two-base-ambiguity-codes" class="level3">
<h3 class="anchored" data-anchor-id="two-base-ambiguity-codes">Two-Base Ambiguity Codes</h3>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Code</th>
<th>Meaning</th>
<th>Value</th>
<th>Complement</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>R</td>
<td>A or G (purine)</td>
<td>0x9</td>
<td>Y (0x6)</td>
</tr>
<tr class="even">
<td>Y</td>
<td>C or T (pyrimidine)</td>
<td>0x6</td>
<td>R (0x9)</td>
</tr>
<tr class="odd">
<td>S</td>
<td>G or C (strong)</td>
<td>0xA</td>
<td>S (0xA)</td>
</tr>
<tr class="even">
<td>W</td>
<td>A or T (weak)</td>
<td>0x5</td>
<td>W (0x5)</td>
</tr>
<tr class="odd">
<td>K</td>
<td>G or T (keto)</td>
<td>0xC</td>
<td>M (0x3)</td>
</tr>
<tr class="even">
<td>M</td>
<td>A or C (amino)</td>
<td>0x3</td>
<td>K (0xC)</td>
</tr>
</tbody>
</table>
</section>
<section id="three-base-ambiguity-codes" class="level3">
<h3 class="anchored" data-anchor-id="three-base-ambiguity-codes">Three-Base Ambiguity Codes</h3>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Code</th>
<th>Meaning</th>
<th>Value</th>
<th>Complement</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>B</td>
<td>C, G, or T (not A)</td>
<td>0xE</td>
<td>V (0xB)</td>
</tr>
<tr class="even">
<td>D</td>
<td>A, G, or T (not C)</td>
<td>0xD</td>
<td>H (0x7)</td>
</tr>
<tr class="odd">
<td>H</td>
<td>A, C, or T (not G)</td>
<td>0x7</td>
<td>D (0xD)</td>
</tr>
<tr class="even">
<td>V</td>
<td>A, C, or G (not T)</td>
<td>0xB</td>
<td>B (0xE)</td>
</tr>
</tbody>
</table>
</section>
<section id="wildcards-and-gaps" class="level3">
<h3 class="anchored" data-anchor-id="wildcards-and-gaps">Wildcards and Gaps</h3>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Code</th>
<th>Meaning</th>
<th>Value</th>
<th>Complement</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>N</td>
<td>Any base</td>
<td>0xF</td>
<td>N (0xF)</td>
</tr>
<tr class="even">
<td>-</td>
<td>Gap / deletion</td>
<td>0x0</td>
<td>- (0x0)</td>
</tr>
<tr class="odd">
<td>.</td>
<td>Gap (alternative)</td>
<td>0x0</td>
<td>- (0x0)</td>
</tr>
</tbody>
</table>
</section>
<section id="bit-rotation-property" class="level3">
<h3 class="anchored" data-anchor-id="bit-rotation-property">Bit Rotation Property</h3>
<p>The encoding is designed so that the complement of any nucleotide can be computed via a 2-bit rotation:</p>
<pre class="text"><code>complement = ((bits << 2) | (bits >> 2)) & 0xF</code></pre>
<p>This enables SIMD-accelerated reverse complement operations that are ~2x faster than lookup table approaches.</p>
</section>
</section>
<section id="usage" class="level2">
<h2 class="anchored" data-anchor-id="usage">Usage</h2>
<div class="sourceCode" id="cb4"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="pp">simdna::dna_simd_encoder::</span><span class="op">{</span>encode_dna_prefer_simd<span class="op">,</span> decode_dna_prefer_simd<span class="op">};</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="co">// Encode a DNA sequence with IUPAC codes</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> sequence <span class="op">=</span> <span class="st">b"ACGTNRYSWKMBDHV-"</span><span class="op">;</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> encoded <span class="op">=</span> encode_dna_prefer_simd(sequence)<span class="op">;</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a><span class="co">// The encoded data is 2x smaller (2 nucleotides per byte)</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a><span class="pp">assert_eq!</span>(encoded<span class="op">.</span>len()<span class="op">,</span> sequence<span class="op">.</span>len() <span class="op">/</span> <span class="dv">2</span>)<span class="op">;</span></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a><span class="co">// Decode back to the original sequence</span></span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> decoded <span class="op">=</span> decode_dna_prefer_simd(<span class="op">&</span>encoded<span class="op">,</span> sequence<span class="op">.</span>len())<span class="op">;</span></span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a><span class="pp">assert_eq!</span>(decoded<span class="op">,</span> sequence)<span class="op">;</span></span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-14"><a href="#cb4-14" aria-hidden="true" tabindex="-1"></a><span class="co">// RNA sequences work seamlessly (U maps to T)</span></span>
<span id="cb4-15"><a href="#cb4-15" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> rna <span class="op">=</span> <span class="st">b"ACGU"</span><span class="op">;</span></span>
<span id="cb4-16"><a href="#cb4-16" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> encoded_rna <span class="op">=</span> encode_dna_prefer_simd(rna)<span class="op">;</span></span>
<span id="cb4-17"><a href="#cb4-17" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> decoded_rna <span class="op">=</span> decode_dna_prefer_simd(<span class="op">&</span>encoded_rna<span class="op">,</span> rna<span class="op">.</span>len())<span class="op">;</span></span>
<span id="cb4-18"><a href="#cb4-18" aria-hidden="true" tabindex="-1"></a><span class="pp">assert_eq!</span>(decoded_rna<span class="op">,</span> <span class="st">b"ACGT"</span>)<span class="op">;</span> <span class="co">// U decodes as T</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
</section>
<section id="reverse-complement" class="level2">
<h2 class="anchored" data-anchor-id="reverse-complement">Reverse Complement</h2>
<p>simdna provides efficient SIMD-accelerated reverse complement operations for DNA/RNA sequences with consistent performance for both even and odd-length sequences:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="pp">simdna::dna_simd_encoder::</span><span class="op">{</span>reverse_complement<span class="op">,</span> reverse_complement_encoded<span class="op">,</span> encode_dna_prefer_simd<span class="op">};</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="co">// High-level API: ASCII in, ASCII out</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> sequence <span class="op">=</span> <span class="st">b"ACGT"</span><span class="op">;</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> rc <span class="op">=</span> reverse_complement(sequence)<span class="op">;</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a><span class="pp">assert_eq!</span>(rc<span class="op">,</span> <span class="st">b"ACGT"</span>)<span class="op">;</span> <span class="co">// ACGT is its own reverse complement</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a><span class="co">// Biological example</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> forward <span class="op">=</span> <span class="st">b"ATGCAACG"</span><span class="op">;</span></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> rc <span class="op">=</span> reverse_complement(forward)<span class="op">;</span></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a><span class="pp">assert_eq!</span>(rc<span class="op">,</span> <span class="st">b"CGTTGCAT"</span>)<span class="op">;</span></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a><span class="co">// Low-level API: operates directly on encoded data for maximum performance (~20 GiB/s)</span></span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> encoded <span class="op">=</span> encode_dna_prefer_simd(<span class="st">b"ACGT"</span>)<span class="op">;</span></span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> rc_encoded <span class="op">=</span> reverse_complement_encoded(<span class="op">&</span>encoded<span class="op">,</span> <span class="dv">4</span>)<span class="op">;</span></span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a><span class="co">// rc_encoded is the encoded form of "ACGT"</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
<section id="iupac-ambiguity-code-complements" class="level3">
<h3 class="anchored" data-anchor-id="iupac-ambiguity-code-complements">IUPAC Ambiguity Code Complements</h3>
<p>Reverse complement correctly handles all IUPAC ambiguity codes:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="pp">simdna::dna_simd_encoder::</span>reverse_complement<span class="op">;</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="co">// R (purine: A|G) complements to Y (pyrimidine: C|T)</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a><span class="pp">assert_eq!</span>(reverse_complement(<span class="st">b"R"</span>)<span class="op">,</span> <span class="st">b"Y"</span>)<span class="op">;</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a><span class="co">// Self-complementary codes: S (G|C), W (A|T), N (any)</span></span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a><span class="pp">assert_eq!</span>(reverse_complement(<span class="st">b"SWN"</span>)<span class="op">,</span> <span class="st">b"NWS"</span>)<span class="op">;</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
</section>
</section>
<section id="input-handling" class="level2">
<h2 class="anchored" data-anchor-id="input-handling">Input Handling</h2>
<ul>
<li><strong>Case insensitive</strong>: Both <code>"ACGT"</code> and <code>"acgt"</code> encode identically</li>
<li><strong>Invalid characters</strong>: Non-IUPAC characters (X, digits, etc.) encode as gap (0xF)</li>
<li><strong>Decoding</strong>: Always produces uppercase nucleotides</li>
</ul>
</section>
<section id="integration" class="level2">
<h2 class="anchored" data-anchor-id="integration">Integration</h2>
<p><strong>simdna</strong> focuses exclusively on high-performance encoding/decoding, making it composable with any FASTA/FASTQ parser or custom format. This keeps the library lightweight and lets you choose the tools that fit your workflow.</p>
<section id="working-with-seq_io" class="level3">
<h3 class="anchored" data-anchor-id="working-with-seq_io">Working with seq_io</h3>
<p><a href="https://crates.io/crates/seq_io">seq_io</a> is a fast FASTA/FASTQ parser. simdna works directly with its borrowed sequence data:</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="pp">seq_io::fasta::</span>Reader<span class="op">;</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="pp">simdna::dna_simd_encoder::</span>encode_dna_prefer_simd<span class="op">;</span></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> <span class="kw">mut</span> reader <span class="op">=</span> <span class="pp">Reader::</span>from_path(<span class="st">"genome.fasta"</span>)<span class="op">?;</span></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a><span class="cf">while</span> <span class="kw">let</span> <span class="cn">Some</span>(record) <span class="op">=</span> reader<span class="op">.</span>next() <span class="op">{</span></span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> record <span class="op">=</span> record<span class="op">?;</span></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a> <span class="co">// seq_io provides &[u8] directly - no allocation needed</span></span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> encoded <span class="op">=</span> encode_dna_prefer_simd(record<span class="op">.</span>seq())<span class="op">;</span></span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a> <span class="co">// ... use encoded data</span></span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
</section>
<section id="working-with-noodles" class="level3">
<h3 class="anchored" data-anchor-id="working-with-noodles">Working with noodles</h3>
<p><a href="https://crates.io/crates/noodles">noodles</a> is a comprehensive bioinformatics I/O library:</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="pp">noodles::</span>fasta<span class="op">;</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="pp">simdna::dna_simd_encoder::</span>encode_dna_prefer_simd<span class="op">;</span></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> <span class="kw">mut</span> reader <span class="op">=</span> <span class="pp">fasta::io::reader::Builder::</span><span class="kw">default</span>()<span class="op">.</span>build_from_path(<span class="st">"genome.fasta"</span>)<span class="op">?;</span></span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> result <span class="kw">in</span> reader<span class="op">.</span>records() <span class="op">{</span></span>
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> record <span class="op">=</span> result<span class="op">?;</span></span>
<span id="cb8-7"><a href="#cb8-7" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> encoded <span class="op">=</span> encode_dna_prefer_simd(record<span class="op">.</span>sequence()<span class="op">.</span>as_ref())<span class="op">;</span></span>
<span id="cb8-8"><a href="#cb8-8" aria-hidden="true" tabindex="-1"></a> <span class="co">// ... use encoded data</span></span>
<span id="cb8-9"><a href="#cb8-9" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
</section>
<section id="working-with-rust-bio" class="level3">
<h3 class="anchored" data-anchor-id="working-with-rust-bio">Working with rust-bio</h3>
<p><a href="https://crates.io/crates/bio">rust-bio</a> provides algorithms and data structures for bioinformatics:</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="pp">bio::io::</span>fasta<span class="op">;</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="pp">simdna::dna_simd_encoder::</span>encode_dna_prefer_simd<span class="op">;</span></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> reader <span class="op">=</span> <span class="pp">fasta::Reader::</span>from_file(<span class="st">"genome.fasta"</span>)<span class="op">?;</span></span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> result <span class="kw">in</span> reader<span class="op">.</span>records() <span class="op">{</span></span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> record <span class="op">=</span> result<span class="op">?;</span></span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> encoded <span class="op">=</span> encode_dna_prefer_simd(record<span class="op">.</span>seq())<span class="op">;</span></span>
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a> <span class="co">// ... use encoded data</span></span>
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
</section>
<section id="zero-copy-integration" class="level3">
<h3 class="anchored" data-anchor-id="zero-copy-integration">Zero-Copy Integration</h3>
<p>simdna accepts <code>&[u8]</code> slices, enabling zero-copy integration with parsers. Avoid unnecessary allocations:</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="co">// ✓ Good: Work directly with borrowed data</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> encoded <span class="op">=</span> encode_dna_prefer_simd(record<span class="op">.</span>seq())<span class="op">;</span></span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a><span class="co">// ✗ Avoid: Unnecessary allocation</span></span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> owned<span class="op">:</span> <span class="dt">Vec</span><span class="op"><</span><span class="dt">u8</span><span class="op">></span> <span class="op">=</span> record<span class="op">.</span>seq()<span class="op">.</span>to_vec()<span class="op">;</span></span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> encoded <span class="op">=</span> encode_dna_prefer_simd(<span class="op">&</span>owned)<span class="op">;</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
<p>Most FASTA/FASTQ parsers provide sequence data as <code>&[u8]</code> or types that implement <code>AsRef<[u8]></code>, which work directly with simdna’s API.</p>
</section>
</section>
<section id="platform-support" class="level2">
<h2 class="anchored" data-anchor-id="platform-support">Platform Support</h2>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Platform</th>
<th>SIMD</th>
<th>Fallback</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>x86_64</td>
<td>SSSE3</td>
<td>Scalar</td>
</tr>
<tr class="even">
<td>ARM64</td>
<td>NEON</td>
<td>Scalar</td>
</tr>
<tr class="odd">
<td>Other</td>
<td>-</td>
<td>Scalar</td>
</tr>
</tbody>
</table>
</section>
<section id="performance" class="level2">
<h2 class="anchored" data-anchor-id="performance">Performance</h2>
<p>simdna employs multiple optimization strategies:</p>
<ul>
<li><strong>Static Lookup Tables</strong>: Pre-computed encode/decode tables eliminate branch mispredictions</li>
<li><strong>SIMD Processing</strong>: Handles 32 nucleotides per iteration (two 16-byte chunks) with prefetching</li>
<li><strong>Direct Case Handling</strong>: LUT handles case-insensitivity without uppercase conversion overhead</li>
<li><strong>Optimized Scalar Path</strong>: Remainder processing uses 4-at-a-time scalar encoding</li>
<li><strong>SIMD Reverse Complement</strong>: Up to ~20 GiB/s throughput on encoded data, 4-6x faster than scalar</li>
<li><strong>Consistent Performance</strong>: Both even and odd-length sequences achieve similar throughput</li>
<li><strong>2:1 Compression</strong>: 4 bits per nucleotide vs 8 bits ASCII</li>
</ul>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="./artefacts/throughput_plot.png" class="img-fluid figure-img"></p>
<figcaption>DNA Encoding/Decoding Throughput</figcaption>
</figure>
</div>
<p><sub>Benchmarks obtained on a Mac Studio with 32GB RAM and Apple M1 Max chip running macOS Tahoe 26.1 using the Criterion.rs statistics-driven micro-benchmarking library.</sub></p>
</section>
<section id="testing" class="level2">
<h2 class="anchored" data-anchor-id="testing">Testing</h2>
<p>simdna employs a comprehensive testing strategy to ensure correctness and robustness:</p>
<section id="unit-tests" class="level3">
<h3 class="anchored" data-anchor-id="unit-tests">Unit Tests</h3>
<p>Run the standard test suite with:</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="ex">cargo</span> test</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
<p>The unit tests cover:</p>
<ul>
<li>Encoding and decoding of all IUPAC nucleotide codes</li>
<li>Case insensitivity handling</li>
<li>Invalid character handling</li>
<li>Odd and even length sequences</li>
<li>Empty input edge cases</li>
<li>SIMD and scalar implementation equivalence</li>
</ul>
</section>
<section id="fuzz-testing" class="level3">
<h3 class="anchored" data-anchor-id="fuzz-testing">Fuzz Testing</h3>
<p>simdna uses <a href="https://github.com/rust-fuzz/cargo-fuzz"><code>cargo-fuzz</code></a> for property-based fuzz testing to discover edge cases and potential bugs. The following fuzz targets are available:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 38%">
<col style="width: 61%">
</colgroup>
<thead>
<tr class="header">
<th>Target</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>roundtrip</code></td>
<td>Verifies encode→decode produces consistent output</td>
</tr>
<tr class="even">
<td><code>valid_iupac</code></td>
<td>Tests encoding of valid IUPAC sequences</td>
</tr>
<tr class="odd">
<td><code>decode_robust</code></td>
<td>Tests decoder resilience to arbitrary byte sequences</td>
</tr>
<tr class="even">
<td><code>boundaries</code></td>
<td>Tests sequence length boundary conditions</td>
</tr>
<tr class="odd">
<td><code>simd_scalar_equivalence</code></td>
<td>Verifies SIMD and scalar implementations produce identical results</td>
</tr>
<tr class="even">
<td><code>bit_rotation</code></td>
<td>Verifies bit rotation complement properties (involution, consistency)</td>
</tr>
<tr class="odd">
<td><code>reverse_complement</code></td>
<td>Tests reverse complement correctness (double-rc = original)</td>
</tr>
</tbody>
</table>
<p>Run fuzz tests with:</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="ex">cargo</span> +nightly fuzz run <span class="op"><</span>target<span class="op">></span> -- <span class="at">-max_total_time</span><span class="op">=</span>60</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
</section>
</section>
<section id="contributing" class="level2">
<h2 class="anchored" data-anchor-id="contributing">Contributing</h2>
<p>Contributions are welcome! Please see <a href="CONTRIBUTING.md">CONTRIBUTING.md</a> for guidelines on bug reports and feature requests.</p>
</section>
<section id="changelog" class="level2">
<h2 class="anchored" data-anchor-id="changelog">Changelog</h2>
<p>See <a href="CHANGELOG.md">CHANGELOG.md</a> for a history of changes to this project.</p>
</section>
<section id="citation" class="level2">
<h2 class="anchored" data-anchor-id="citation">Citation</h2>
<p>If you use simdna in your research, please cite it using the metadata in <a href="CITATION.cff">CITATION.cff</a>. GitHub can also generate citation information directly from the repository page.</p>
</section>
<section id="license" class="level2">
<h2 class="anchored" data-anchor-id="license">License</h2>
<p>This project is licensed under the MIT License - see <a href="LICENSE">LICENSE</a> for details.</p>
</section>
</main>
<script id="quarto-html-after-body" type="application/javascript">
window.document.addEventListener("DOMContentLoaded", function (event) {
const toggleBodyColorMode = (bsSheetEl) => {
const mode = bsSheetEl.getAttribute("data-mode");
const bodyEl = window.document.querySelector("body");
if (mode === "dark") {
bodyEl.classList.add("quarto-dark");
bodyEl.classList.remove("quarto-light");
} else {
bodyEl.classList.add("quarto-light");
bodyEl.classList.remove("quarto-dark");
}
}
const toggleBodyColorPrimary = () => {
const bsSheetEl = window.document.querySelector("link#quarto-bootstrap");
if (bsSheetEl) {
toggleBodyColorMode(bsSheetEl);
}
}
toggleBodyColorPrimary();
const icon = "î§‹";
const anchorJS = new window.AnchorJS();
anchorJS.options = {
placement: 'right',
icon: icon
};
anchorJS.add('.anchored');
const isCodeAnnotation = (el) => {
for (const clz of el.classList) {
if (clz.startsWith('code-annotation-')) {
return true;
}
}
return false;
}
const onCopySuccess = function(e) {
const button = e.trigger;
button.blur();
button.classList.add('code-copy-button-checked');
var currentTitle = button.getAttribute("title");
button.setAttribute("title", "Copied!");
let tooltip;
if (window.bootstrap) {
button.setAttribute("data-bs-toggle", "tooltip");
button.setAttribute("data-bs-placement", "left");
button.setAttribute("data-bs-title", "Copied!");
tooltip = new bootstrap.Tooltip(button,
{ trigger: "manual",
customClass: "code-copy-button-tooltip",
offset: [0, -8]});
tooltip.show();
}
setTimeout(function() {
if (tooltip) {
tooltip.hide();
button.removeAttribute("data-bs-title");
button.removeAttribute("data-bs-toggle");
button.removeAttribute("data-bs-placement");
}
button.setAttribute("title", currentTitle);
button.classList.remove('code-copy-button-checked');
}, 1000);
e.clearSelection();
}
const getTextToCopy = function(trigger) {
const codeEl = trigger.previousElementSibling.cloneNode(true);
for (const childEl of codeEl.children) {
if (isCodeAnnotation(childEl)) {
childEl.remove();
}
}
return codeEl.innerText;
}
const clipboard = new window.ClipboardJS('.code-copy-button:not([data-in-quarto-modal])', {
text: getTextToCopy
});
clipboard.on('success', onCopySuccess);
if (window.document.getElementById('quarto-embedded-source-code-modal')) {
const clipboardModal = new window.ClipboardJS('.code-copy-button[data-in-quarto-modal]', {
text: getTextToCopy,
container: window.document.getElementById('quarto-embedded-source-code-modal')
});
clipboardModal.on('success', onCopySuccess);
}
var localhostRegex = new RegExp(/^(?:http|https):\/\/localhost\:?[0-9]*\//);
var mailtoRegex = new RegExp(/^mailto:/);
var filterRegex = new RegExp('/' + window.location.host + '/');
var isInternal = (href) => {
return filterRegex.test(href) || localhostRegex.test(href) || mailtoRegex.test(href);
}
var links = window.document.querySelectorAll('a[href]:not(.nav-link):not(.navbar-brand):not(.toc-action):not(.sidebar-link):not(.sidebar-item-toggle):not(.pagination-link):not(.no-external):not([aria-hidden]):not(.dropdown-item):not(.quarto-navigation-tool):not(.about-link)');
for (var i=0; i<links.length; i++) {
const link = links[i];
if (!isInternal(link.href)) {
if (link.dataset.originalHref !== undefined) {
link.href = link.dataset.originalHref;
}
}
}
function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) {
const config = {
allowHTML: true,
maxWidth: 500,
delay: 100,
arrow: false,
appendTo: function(el) {
return el.parentElement;
},
interactive: true,
interactiveBorder: 10,
theme: 'quarto',
placement: 'bottom-start',
};
if (contentFn) {
config.content = contentFn;
}
if (onTriggerFn) {
config.onTrigger = onTriggerFn;
}
if (onUntriggerFn) {
config.onUntrigger = onUntriggerFn;
}
window.tippy(el, config);
}
const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]');
for (var i=0; i<noterefs.length; i++) {
const ref = noterefs[i];
tippyHover(ref, function() {
let href = ref.getAttribute('data-footnote-href') || ref.getAttribute('href');
try { href = new URL(href).hash; } catch {}
const id = href.replace(/^#\/?/, "");
const note = window.document.getElementById(id);
if (note) {
return note.innerHTML;
} else {
return "";
}
});
}
const xrefs = window.document.querySelectorAll('a.quarto-xref');
const processXRef = (id, note) => {
const stripColumnClz = (el) => {
el.classList.remove("page-full", "page-columns");
if (el.children) {
for (const child of el.children) {
stripColumnClz(child);
}
}
}
stripColumnClz(note)
if (id === null || id.startsWith('sec-')) {
const container = document.createElement("div");
if (note.children && note.children.length > 2) {
container.appendChild(note.children[0].cloneNode(true));
for (let i = 1; i < note.children.length; i++) {
const child = note.children[i];
if (child.tagName === "P" && child.innerText === "") {
continue;
} else {
container.appendChild(child.cloneNode(true));
break;
}
}
if (window.Quarto?.typesetMath) {
window.Quarto.typesetMath(container);
}
return container.innerHTML
} else {
if (window.Quarto?.typesetMath) {
window.Quarto.typesetMath(note);
}
return note.innerHTML;
}
} else {
const anchorLink = note.querySelector('a.anchorjs-link');
if (anchorLink) {
anchorLink.remove();
}
if (window.Quarto?.typesetMath) {
window.Quarto.typesetMath(note);
}
if (note.classList.contains("callout")) {
return note.outerHTML;
} else {
return note.innerHTML;
}
}
}
for (var i=0; i<xrefs.length; i++) {
const xref = xrefs[i];
tippyHover(xref, undefined, function(instance) {
instance.disable();
let url = xref.getAttribute('href');
let hash = undefined;
if (url.startsWith('#')) {
hash = url;
} else {
try { hash = new URL(url).hash; } catch {}
}
if (hash) {
const id = hash.replace(/^#\/?/, "");
const note = window.document.getElementById(id);
if (note !== null) {
try {
const html = processXRef(id, note.cloneNode(true));
instance.setContent(html);
} finally {
instance.enable();
instance.show();
}
} else {
fetch(url.split('#')[0])
.then(res => res.text())
.then(html => {
const parser = new DOMParser();
const htmlDoc = parser.parseFromString(html, "text/html");
const note = htmlDoc.getElementById(id);
if (note !== null) {
const html = processXRef(id, note);
instance.setContent(html);
}
}).finally(() => {
instance.enable();
instance.show();
});
}
} else {
fetch(url)
.then(res => res.text())
.then(html => {
const parser = new DOMParser();
const htmlDoc = parser.parseFromString(html, "text/html");
const note = htmlDoc.querySelector('main.content');
if (note !== null) {
if (note.children.length > 0 && note.children[0].tagName === "HEADER") {
note.children[0].remove();
}
const html = processXRef(null, note);
instance.setContent(html);
}
}).finally(() => {
instance.enable();
instance.show();
});
}
}, function(instance) {
});
}
let selectedAnnoteEl;
const selectorForAnnotation = ( cell, annotation) => {
let cellAttr = 'data-code-cell="' + cell + '"';
let lineAttr = 'data-code-annotation="' + annotation + '"';
const selector = 'span[' + cellAttr + '][' + lineAttr + ']';
return selector;
}
const selectCodeLines = (annoteEl) => {
const doc = window.document;
const targetCell = annoteEl.getAttribute("data-target-cell");
const targetAnnotation = annoteEl.getAttribute("data-target-annotation");
const annoteSpan = window.document.querySelector(selectorForAnnotation(targetCell, targetAnnotation));
const lines = annoteSpan.getAttribute("data-code-lines").split(",");
const lineIds = lines.map((line) => {
return targetCell + "-" + line;
})
let top = null;
let height = null;
let parent = null;
if (lineIds.length > 0) {
const el = window.document.getElementById(lineIds[0]);
top = el.offsetTop;
height = el.offsetHeight;
parent = el.parentElement.parentElement;
if (lineIds.length > 1) {
const lastEl = window.document.getElementById(lineIds[lineIds.length - 1]);
const bottom = lastEl.offsetTop + lastEl.offsetHeight;
height = bottom - top;
}
if (top !== null && height !== null && parent !== null) {
let div = window.document.getElementById("code-annotation-line-highlight");
if (div === null) {
div = window.document.createElement("div");
div.setAttribute("id", "code-annotation-line-highlight");
div.style.position = 'absolute';
parent.appendChild(div);
}
div.style.top = top - 2 + "px";
div.style.height = height + 4 + "px";
div.style.left = 0;
let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter");
if (gutterDiv === null) {
gutterDiv = window.document.createElement("div");
gutterDiv.setAttribute("id", "code-annotation-line-highlight-gutter");
gutterDiv.style.position = 'absolute';
const codeCell = window.document.getElementById(targetCell);
const gutter = codeCell.querySelector('.code-annotation-gutter');
gutter.appendChild(gutterDiv);
}
gutterDiv.style.top = top - 2 + "px";
gutterDiv.style.height = height + 4 + "px";
}
selectedAnnoteEl = annoteEl;
}
};
const unselectCodeLines = () => {
const elementsIds = ["code-annotation-line-highlight", "code-annotation-line-highlight-gutter"];
elementsIds.forEach((elId) => {
const div = window.document.getElementById(elId);
if (div) {
div.remove();
}
});
selectedAnnoteEl = undefined;
};
window.addEventListener(
"resize",
throttle(() => {
elRect = undefined;
if (selectedAnnoteEl) {
selectCodeLines(selectedAnnoteEl);
}
}, 10)
);
function throttle(fn, ms) {
let throttle = false;
let timer;
return (...args) => {
if(!throttle) { fn.apply(this, args);
throttle = true;
} else { if(timer) clearTimeout(timer); timer = setTimeout(() => {
fn.apply(this, args);
timer = throttle = false;
}, ms);
}
};
}
const annoteDls = window.document.querySelectorAll('dt[data-target-cell]');
for (const annoteDlNode of annoteDls) {
annoteDlNode.addEventListener('click', (event) => {
const clickedEl = event.target;
if (clickedEl !== selectedAnnoteEl) {
unselectCodeLines();
const activeEl = window.document.querySelector('dt[data-target-cell].code-annotation-active');
if (activeEl) {
activeEl.classList.remove('code-annotation-active');
}
selectCodeLines(clickedEl);
clickedEl.classList.add('code-annotation-active');
} else {
unselectCodeLines();
clickedEl.classList.remove('code-annotation-active');
}
});
}
const findCites = (el) => {
const parentEl = el.parentElement;
if (parentEl) {
const cites = parentEl.dataset.cites;
if (cites) {
return {
el,
cites: cites.split(' ')
};
} else {
return findCites(el.parentElement)
}
} else {
return undefined;
}
};
var bibliorefs = window.document.querySelectorAll('a[role="doc-biblioref"]');
for (var i=0; i<bibliorefs.length; i++) {
const ref = bibliorefs[i];
const citeInfo = findCites(ref);
if (citeInfo) {
tippyHover(citeInfo.el, function() {
var popup = window.document.createElement('div');
citeInfo.cites.forEach(function(cite) {
var citeDiv = window.document.createElement('div');
citeDiv.classList.add('hanging-indent');
citeDiv.classList.add('csl-entry');
var biblioDiv = window.document.getElementById('ref-' + cite);
if (biblioDiv) {
citeDiv.innerHTML = biblioDiv.innerHTML;
}
popup.appendChild(citeDiv);
});
return popup.innerHTML;
});
}
}
});
</script>
</div>
</body></html>