<?xml version='1.0' encoding='utf-8'?>
<?xml-stylesheet type="text/xsl" href="/sheet.xsl"?><rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>Math ∩ Programming</title><link>https://www.jeremykun.com/</link><description>Recent content on Math ∩ Programming</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Wed, 29 Apr 2026 05:25:44 -0700</lastBuildDate><atom:link href="https://www.jeremykun.com/index.xml" rel="self" type="application/rss+xml"/><item><title>Featured Posts</title><link>https://www.jeremykun.com/2011/06/20/featured-posts/</link><pubDate>Mon, 20 Jun 2011 18:31:45 +0000</pubDate><guid>https://www.jeremykun.com/2011/06/20/featured-posts/</guid><description> My next book will be Practical Math for Programmers A High-Level Overview of Fully Homomorphic Encryption Searching for Riemann Hypothesis Counterexamples Linear Programming and Healthy Diets Hybrid Images Bezier Curves and Picasso</description></item><item><title>CKKS — Polynomials, the Canonical Embedding, and Encoding</title><link>https://www.jeremykun.com/2026/04/29/ckks-polynomials-the-canonical-embedding-and-encoding/</link><pubDate>Wed, 29 Apr 2026 05:25:44 -0700</pubDate><guid>https://www.jeremykun.com/2026/04/29/ckks-polynomials-the-canonical-embedding-and-encoding/</guid><description>Table of Contents
In this tutorial series, I will introduce the CKKS homomorphic encryption scheme from the ground up, in rather intricate detail. Each article in this series corresponds to a pull request on a GitHub repository. The code for this article is in this pull request. Follow along by cloning the repository and checking out the code at the relevant commit.
This first article will cover some of the mathematical background necessary in the formulation of the CKKS encryption scheme, specifically the polynomial ring used in the most basic version of CKKS, and the canonical embedding used to encode cleartext messages as plaintexts.</description><ns0:encoded xmlns:ns0="http://purl.org/rss/1.0/modules/content/">&lt;article id="main" class="content-container look-sheet article-pad-v h-entry" itemscope="" itemtype="https://schema.org/Article" morss_own_score="8.300830431288508" morss_score="14.591979918606372"&gt;&lt;h1&gt;CKKS — Polynomials, the Canonical Embedding, and Encoding&lt;/h1&gt;&lt;span&gt;2026-04-29&lt;/span&gt;&lt;div itemprop="articleBody" id="content" class="article-body margin-top-2em" morss_own_score="5.582298974635726" morss_score="219.4938250231284"&gt;&lt;p&gt;&lt;a href="https://github.com/j2kun/ckks-tutorial#ckks-tutorial"&gt;Table of Contents&lt;/a&gt;&lt;/p&gt;&lt;p&gt;In this tutorial series, I will introduce the CKKS homomorphic encryption
scheme from the ground up, in rather intricate detail. Each article in this
series corresponds to a pull request on &lt;a href="https://github.com/j2kun/ckks-tutorial"&gt;a GitHub
repository&lt;/a&gt;. The code for this article
is in &lt;a href="https://github.com/j2kun/ckks-tutorial/pull/2"&gt;this pull request&lt;/a&gt;.
Follow along by cloning the
repository and checking out the code at the relevant commit.&lt;/p&gt;&lt;p&gt;This first article will cover some of the mathematical background necessary in
the formulation of the CKKS encryption scheme, specifically the polynomial ring
used in the most basic version of CKKS, and the canonical embedding used to
encode cleartext messages as plaintexts.&lt;/p&gt;&lt;p&gt;This series will contain plenty of mathematics, but I may abbreviate some
verbose definitions, especially those that I would expect to be familiar to
readers of this blog (such as the formal definition of a ring). In other words,
I’ll assume basic undergraduate mathematics familiarity, with some reminders. A
good accompaniment for this series would be &lt;a href="https://fhetextbook.github.io/"&gt;The Beginner’s Textbook for Fully
Homomorphic Encryption&lt;/a&gt; by Ronny Ko, which
complements this series in giving more complete (albeit terse) definitions,
formulas, and proofs.&lt;/p&gt;&lt;h2&gt;A brief history of CKKS&lt;/h2&gt;&lt;p&gt;Some of the terms used in this section may make more sense if you’ve read
my &lt;a href="https://www.jeremykun.com/2024/05/04/fhe-overview/"&gt;high-level technical overview of homomorphic encryption&lt;/a&gt;.
We will re-cover all of this in detail in future articles.&lt;/p&gt;&lt;p&gt;The original CKKS homomorphic encryption scheme was introduced in the 2016
paper &lt;a href="https://eprint.iacr.org/2016/421"&gt;Homomorphic Encryption for Arithmetic of Approximate
Numbers&lt;/a&gt; by Jung Hee Cheon, Andrey Kim, Miran
Kim, and Yongsoo Song as a joint collaboration between Seoul National
University and UC San Diego.&lt;sup&gt;&lt;a href="https://www.jeremykun.com/2026/04/29/ckks-polynomials-the-canonical-embedding-and-encoding/#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; Its primary innovation was to handle
approximate arithmetic on real or complex numbers, rather than prior schemes
which only handled exact arithmetic on integers. This is relevant in contexts
like neural network inference, where the calculations can be inexact and still
useful. In particular, CKKS allows the inexactness of fixed-point arithmetic
to coexist with the error introduced by the homomorphic encryption scheme itself.&lt;/p&gt;&lt;p&gt;After its initial publication, several followup papers made improvements to
CKKS that elevated it to the state of the art. First, and most importantly, a
bootstrapping procedure &lt;a href="https://eprint.iacr.org/2018/153"&gt;was found in 2018&lt;/a&gt;
that made CKKS “fully homomorphic.” Subsequent years saw a plethora of additional
improvements and variants to CKKS bootstrapping. Experts would even say
there are too many variants to keep track of.&lt;/p&gt;&lt;p&gt;The second major improvement was the introduction of the &lt;a href="https://eprint.iacr.org/2018/931"&gt;residue number system
variant of CKKS&lt;/a&gt; in 2017. The original CKKS
scheme used large integer arithmetic, in particular doing arithmetic modulo
hundred-bit or even thousand-bit moduli. Using a residue number system (RNS)
allows one to replace the (inherently serial) carry propagation required for
large-precision arithmetic with parallel operations on vectors of 64-bit
values.&lt;sup&gt;&lt;a href="https://www.jeremykun.com/2026/04/29/ckks-polynomials-the-canonical-embedding-and-encoding/#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&lt;p&gt;Combining RNS with bootstrapping produces what, in my view, is the “baseline”
version of CKKS that most new works extend or use for contrast.&lt;/p&gt;&lt;h2&gt;Plaintexts and a polynomial ring&lt;/h2&gt;&lt;p&gt;The main setting for CKKS is a particular polynomial ring. We start with
some ring $R$ of coefficients for the polynomials $R[x]$;
sometimes $R$ will be the integers, reals,
but more often it will be the field of integers modulo a prime.&lt;/p&gt;&lt;p morss_own_score="6.0" morss_score="12.0"&gt;CKKS is an encryption scheme, and in every encryption scheme there are three
distinct spaces: the &lt;em&gt;cleartext&lt;/em&gt; space, the &lt;em&gt;plaintext&lt;/em&gt; space, and the
&lt;em&gt;ciphertext&lt;/em&gt; space.&lt;/p&gt;&lt;p&gt;Cleartexts describe the atomic message units
(e.g., a vector of 32-bit integers of a fixed size).
The user must decide how to split their larger program data into cleartext units, say, by chunking.
Plaintexts describe preprocessing required to make a cleartext compatible with the encryption scheme.
And ciphertexts are the form of the messages after they are encrypted.&lt;/p&gt;&lt;p&gt;I spell all this out because, while many encryption schemes don’t have major differences
between cleartext and plaintext space, CKKS uses a sophisticated transformation.
This article focuses purely on the conversion between the cleartext and plaintext space.&lt;/p&gt;&lt;p&gt;Fix a parameter $N$, a power of two, which will be used to define the polynomial ring.
A CKKS cleartext is a vector of $N/2$ complex numbers.&lt;sup&gt;&lt;a href="https://www.jeremykun.com/2026/04/29/ckks-polynomials-the-canonical-embedding-and-encoding/#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt;
A CKKS plaintext is an element of the ring&lt;/p&gt;\[ (\mathbb{Z}/Q\mathbb{Z})[x] \Big / (x^N+1) \]&lt;p&gt;As a reminder, the coefficients $\mathbb{Z}/Q\mathbb{Z}$ form the ring of integers
with arithmetic done modulo $Q$. If $Q$ were a prime, this would form a field, but
in most cases $Q$ is composite.&lt;/p&gt;&lt;p&gt;As a second reminder, the polynomial modulus converts the ring of polynomials
mod $Q$ into a quotient ring where two polynomials $p(x)$ and $q(x)$ are equivalent
if they have the same remainder when dividing by $x^N + 1$. Some features that
are important for computation:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Elements of this ring have degree bounded by $N-1$
(when choosing their minimal-degree coset representative).
So a polynomial can be viewed as a vector of $N$ entries,
the coefficients at degrees 0 to $N-1$.&lt;/li&gt;&lt;li&gt;One can identify $x^N$ with $-1$, and this gives a baseline method to reduce
larger polynomials to their canonical representative: take each coefficient
at degree $k \geq N$ and add it with a sign flip to the coefficient at degree
$k - N$.&lt;/li&gt;&lt;li&gt;Because of the previous item, multiplying a polynomial in this ring by a
monomial can be implemented by &lt;em&gt;cyclically&lt;/em&gt; rotating the coefficient vector
and sign-flipping the values that wrap around an odd number of times.
This operation is also known as &lt;em&gt;negacyclic rotation&lt;/em&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;There are other important structures of this ring for cryptographic
reasons.&lt;sup&gt;&lt;a href="https://www.jeremykun.com/2026/04/29/ckks-polynomials-the-canonical-embedding-and-encoding/#fn:4"&gt;4&lt;/a&gt;&lt;/sup&gt; For one, the value of $N$ being a power of two
ensures this polynomial forms a &lt;em&gt;number field&lt;/em&gt;. I don’t want to go
too deeply into Galois theory here, but the basic idea of a number field is that
you start from the rational numbers $\mathbb{Q}$,
pick a finite number of elements $\alpha_1, \dots, \alpha_r$ not in $\mathbb{Q}$
(in our case they will be complex roots of unity),
and “add them” to $\mathbb{Q}$,
forming an &lt;em&gt;extension field&lt;/em&gt; $\mathbb{Q}(\alpha_1, \dots, \alpha_r)$
by also including all the derived quantities required to satisfy the field axioms
(inverses, sums and products, sums of products, etc.).&lt;sup&gt;&lt;a href="https://www.jeremykun.com/2026/04/29/ckks-polynomials-the-canonical-embedding-and-encoding/#fn:5"&gt;5&lt;/a&gt;&lt;/sup&gt;
In order to be a number field, these elements need to have some
finite algebraic formula that gets them back to zero.
In other words, the degree of $\mathbb{Q}(\alpha_1, \dots, \alpha_r)$
as a $\mathbb{Q}$-vector space must be finite.
The simplest example is $\mathbb{Q}(\sqrt{2})$, which has degree 2 because
$\sqrt{2}$ is a root of the polynomial $x^2 - 2$.&lt;/p&gt;&lt;p&gt;Back to CKKS, the polynomial ring $(\mathbb{Z}/Q\mathbb{Z})[x] \Big / (x^N+1)$ is not
obviously a number field. You have to do a bit of work to first identify $\mathbb{Z}[x] \Big / (x^N+1)$
with $K = \mathbb{Q}(\omega_{2N})$, where $\omega_{2N}$ is a primitive $2N$-th
root of unity.&lt;sup&gt;&lt;a href="https://www.jeremykun.com/2026/04/29/ckks-polynomials-the-canonical-embedding-and-encoding/#fn:6"&gt;6&lt;/a&gt;&lt;/sup&gt; Once you do, taking a quotient by $Q$ in the
coefficients translates to a quotient ring $K / QK$.
Another angle is to start from $\mathbb{Z}[x] / (x^N + 1)$, identify that
as the ring of integers of
the number field $\mathbb{Q}[x] / (x^N + 1) = \mathbb{Q}(\omega_{2N})$,
and take a quotient by the modulus $Q$.&lt;/p&gt;&lt;p&gt;We will touch on this more later in the series. The choice of $N$ and $Q$
implies a particular structure of this quotient ring, which impacts how we
implement various homomorphic operations. In particular, it affects the efficiency
of the number theoretic transform. But for this article, what matters is mainly
that the plaintexts are polynomials and that their coefficients are discrete.
This implies two obstacles:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;We must transform a vector of complex numbers into a polynomial.&lt;/li&gt;&lt;li&gt;We must discretize an inherently continuous quantity, complex numbers.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;The canonical embedding&lt;/h2&gt;&lt;p&gt;The tool that CKKS uses to solve both of these problems is called
&lt;em&gt;the canonical embedding&lt;/em&gt;. This term has a lot of abstract definitions
in different parts of mathematics, but for our purposes the canonical
embedding has a simple definition.&lt;/p&gt;&lt;p morss_own_score="7.0" morss_score="11.0"&gt;&lt;strong&gt;Definition:&lt;/strong&gt; Let $N$ be a power of two, and let $p(x)$ be a polynomial
in $\mathbb{C}[x] / (x^N + 1)$. Then the &lt;em&gt;canonical embedding&lt;/em&gt;
of $p(x)$ in $\mathbb{C}^N$ is the vector of evaluations of $p(x)$
at the roots of $x^N + 1$. In particular, for a primitive $2N$-th root of unity
$\omega = e^{2 \pi i / (2N)} = e^{\pi i / N}$
(which generates the roots of $x^N + 1$),
the canonical embedding of $p(x)$ is the vector&lt;/p&gt;\[ \sigma_N(p) = (p(\omega), p(\omega^3), p(\omega^5), \dots, p(\omega^{2N - 1})) \]&lt;p&gt;Define the &lt;em&gt;canonical embedding&lt;/em&gt; $\sigma_N$ to map polynomials to their
evaluations at the odd powers of the complex $2N$-th roots of unity.&lt;sup&gt;&lt;a href="https://www.jeremykun.com/2026/04/29/ckks-polynomials-the-canonical-embedding-and-encoding/#fn:7"&gt;7&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&lt;p&gt;Let’s prove some properties of the canonical embedding.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Homomorphism:&lt;/strong&gt; Evaluating a polynomial at a fixed value is a homomorphism
with respect to addition and scaling of the polynomials ($(p+q)(x) = p(x) +
q(x)$ by definition), and the same is true componentwise for different
evaluations, so $\sigma_N$ is a homomorphism from $\mathbb{C}[x] / (x^N + 1)
\to \mathbb{C}^N$.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Well-defined&lt;/strong&gt;: Let $p(x)$ and $q(x)$ have the property that $x^N + 1$ divides $p(x) - q(x)$.
Then for any root $r$ of $x^N + 1$ we have $p(r) - q(r) = 0$. Hence,&lt;/p&gt;\[ \sigma_N(p) - \sigma_N(q) = (p(\omega^k) - q(\omega^k))_{k=1, 3, \dots, 2n-1} = (0, 0, \dots, 0) \]&lt;p&gt;&lt;strong&gt;Conjugate symmetry when coefficients are real:&lt;/strong&gt; when the input $p(x)$
to the canonical embedding happens to have real coefficients,
it holds that $p(x)$ commutes with complex conjugation of its inputs, i.e.,
$p(\overline{x}) = \overline{p(x)}$. Combine this with the fact
that the roots of $x^N + 1$ come in conjugate pairs:&lt;/p&gt;\[
\begin{aligned}
\omega^1 &amp;amp;= \overline{\omega^{2N-1}} \\
\omega^3 &amp;amp;= \overline{\omega^{2N-3}} \\
&amp;amp;\vdots \\
\omega^{N-1} &amp;amp;= \overline{\omega^{N+1}} \\
\end{aligned}
\]&lt;p&gt;And you get that, in this special case of real coefficients,
the canonical embedding has a special conjugate symmetry:
the second half of the vector’s entries are the reversed-complex conjugates
of the first half.&lt;/p&gt;\[
\sigma_N(p) = (
p(\omega^1),
p(\omega^3),
\dots,
p(\omega^{N-1}),
\overline{p(\omega^{N-1})},
\dots,
\overline{p(\omega^3)},
\overline{p(\omega^1)}
)
\]&lt;p&gt;This property has been named the “Hermitian” property,
and given a name: $\mathbb{H}^N$ is defined as the set of complex vectors
in $\mathbb{C}^N$ whose second half is the reversed-complex conjugates
of the first half.&lt;/p&gt;&lt;p&gt;You might think that, because the second half of $\mathbb{H}^N$ is uniquely
determined by the first half, that $\mathbb{H}^N$ is isomorphic to $\mathbb{C}^{N/2}$.
You’d be right, but you have to be careful.
Because despite having complex-valued entries,
$\mathbb{H}^N$ is not a vector space over $\mathbb{C}$ at all.
Scalar multiplication by a complex number does not preserve the conjugate symmetry.
It only does so if the scalar is real.
So $\mathbb{H}^N$ and $\mathbb{C}^{N/2}$ are isomorphic,
but only as $\mathbb{R}$-vector spaces.&lt;/p&gt;&lt;p&gt;The above limitation is no problem, however,
because we actually &lt;em&gt;want&lt;/em&gt; our input vectors to be real-valued polynomials
(so we can round them to integer-coefficient plaintexts).
This leads us to the next fact, which is the converse of the “Conjugate
symmetry when coefficients are real” fact above.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Proposition:&lt;/strong&gt; Let $v \in \mathbb{H}^N$,
and $\sigma_N : \mathbb{C}[x] \Big / (x^N + 1) \to \mathbb{C}^N$
be the canonical embedding.
Then $\sigma_N^{-1}(v) \in \mathbb{R}[x] \Big / (x^N + 1)$,
i.e., has real-valued coefficients.&lt;/p&gt;&lt;p&gt;Finally, the last property, which I will not prove here (see Appendix C of
&lt;a href="https://eprint.iacr.org/2011/535"&gt;Damgård-Pastro-Smart-Zakarias&lt;/a&gt;),
relates the geometry of the input and output of the canonical embedding.
This is useful when analyzing the noise growth of CKKS ciphertexts.
In fact, as far as I can tell this was one of the core reasons
the original CKKS authors bothered with all this machinery:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Proposition:&lt;/strong&gt; Fix $N$ and let $\sigma = \sigma_N$ be the canonical
embedding as defined above. Let $\left \| x \right \|$ denote the infinity-norm
of $x$ (the magnitude of the largest component). Then for all $p(x), q(x)$,&lt;/p&gt;\[
\left \| \sigma(p) \cdot \sigma(q) \right \| \leq
\left \| \sigma(p) \right \| \cdot \left \| \cdot \sigma(q) \right \|
\]&lt;p&gt;Moreover, there is a constant $c$ (depending only on $N$) such that
for every $p(x)$,&lt;/p&gt;\[
\left \| p \right \| \leq c \left \| \sigma(p) \right \|
\]&lt;p&gt;These facts allow one to measure the growth of polynomial
error in the CKKS scheme by analyzing the growth of the canonical
embeddings. We will come back to that topic in future articles.&lt;/p&gt;&lt;h2&gt;Implementing the canonical embedding and its inverse&lt;/h2&gt;&lt;p&gt;In this section we’ll implement the canonical embedding and its inverse in
Python. Reminder, the code can be found &lt;a href="https://github.com/j2kun/ckks-tutorial/pull/2/changes/386f028f079c91968a90846f80500321c60930b7"&gt;in commit 386f028&lt;/a&gt;
of &lt;a href="https://github.com/j2kun/ckks-tutorial/pull/2"&gt;this pull request&lt;/a&gt; for the
overall tutorial series.&lt;/p&gt;&lt;p&gt;Because the canonical embedding involves evaluating a polynomial
at a set of complex roots of unity, we naturally turn to the
Fast Fourier Transform. See &lt;a href="https://www.jeremykun.com/2022/11/16/polynomial-multiplication-using-the-fft/"&gt;“Polynomial Multiplication Using the FFT”&lt;/a&gt;
and &lt;a href="https://www.jeremykun.com/2022/12/09/negacyclic-polynomial-multiplication/"&gt;“Negacyclic Polynomial Multiplication&lt;/a&gt;
for more details of why this is a good approach.
In particular, the canonical embedding and its inverse
reduce to particular invocations of &lt;code&gt;fft&lt;/code&gt; and &lt;code&gt;ifft&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;We start with a simple &lt;code&gt;Polynomial&lt;/code&gt; class that wraps the coefficients
and $N$.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;class&lt;/span&gt; &lt;span&gt;Polynomial&lt;/span&gt;:
&lt;/span&gt;    &lt;span&gt;"""A univariate polynomial with a ring modulus x^N + 1."""&lt;/span&gt;

&lt;span&gt;    &lt;span&gt;def&lt;/span&gt; __init__(self, coefficients: np&lt;span&gt;.&lt;/span&gt;ndarray, modulus_degree: int):
&lt;/span&gt;&lt;span&gt;        self&lt;span&gt;.&lt;/span&gt;coefficients &lt;span&gt;=&lt;/span&gt; coefficients
&lt;/span&gt;&lt;span&gt;        self&lt;span&gt;.&lt;/span&gt;modulus_degree &lt;span&gt;=&lt;/span&gt; modulus_degree
&lt;/span&gt;        &lt;span&gt;# ... &amp;lt;validations&amp;gt; ...&lt;/span&gt;

    &lt;span&gt;# ... __eq__, __repr__, etc. ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The above doesn’t include any actual polynomial operators yet, since these
functions will mutate the underlying coefficients directly.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt; &lt;span&gt;canonical_embedding&lt;/span&gt;(poly: Polynomial) &lt;span&gt;-&amp;gt;&lt;/span&gt; np&lt;span&gt;.&lt;/span&gt;ndarray:
&lt;/span&gt;    &lt;span&gt;"""Computes the canonical embedding of a polynomial."""&lt;/span&gt;
&lt;span&gt;    poly_coeffs &lt;span&gt;=&lt;/span&gt; poly&lt;span&gt;.&lt;/span&gt;coefficients
&lt;/span&gt;&lt;span&gt;    N &lt;span&gt;=&lt;/span&gt; poly&lt;span&gt;.&lt;/span&gt;modulus_degree
&lt;/span&gt;
    &lt;span&gt;# 2N-point FFT evaluates at all 2N-th roots: omega^0, omega^1, ...,&lt;/span&gt;
    &lt;span&gt;# omega^{2N-1}. But return only the odd entries for the primitive roots.&lt;/span&gt;
&lt;span&gt;    padded &lt;span&gt;=&lt;/span&gt; np&lt;span&gt;.&lt;/span&gt;concatenate([poly_coeffs, np&lt;span&gt;.&lt;/span&gt;zeros(N)])
&lt;/span&gt;&lt;span&gt;    fft_result &lt;span&gt;=&lt;/span&gt; np&lt;span&gt;.&lt;/span&gt;fft&lt;span&gt;.&lt;/span&gt;ifft(padded) &lt;span&gt;*&lt;/span&gt; (&lt;span&gt;2&lt;/span&gt; &lt;span&gt;*&lt;/span&gt; N)
&lt;/span&gt;&lt;span&gt;    &lt;span&gt;return&lt;/span&gt; fft_result[np&lt;span&gt;.&lt;/span&gt;arange(&lt;span&gt;1&lt;/span&gt;, &lt;span&gt;2&lt;/span&gt; &lt;span&gt;*&lt;/span&gt; N, &lt;span&gt;2&lt;/span&gt;)]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This method is slightly inefficient: to get the &lt;code&gt;numpy.fft.fft/ifft&lt;/code&gt;
functions to correspond to evaluations at $2N$-th roots of unity,
we need to have a vector of length $2N$ as input. Then afterward
to get the odd evaluations, we need to filter by the appropriate range.&lt;/p&gt;&lt;p&gt;To be more efficient, production CKKS implementations
implement a custom FFT routine here that avoids this extra work
in two ways: first by operating on the odd powers directly,
and second taking advantage of the additional conjugate symmetry
of the odd powers. For one such reference, see the
&lt;a href="https://github.com/openfheorg/openfhe-development/blob/1306d14f8c26bb6150d3e6ad54f28dfe1007689e/src/core/lib/math/dftransform.cpp#L241"&gt;FFTSpecial&lt;/a&gt;
invocation in
&lt;a href="https://github.com/openfheorg/openfhe-development/blob/1306d14f8c26bb6150d3e6ad54f28dfe1007689e/src/pke/lib/encoding/ckkspackedencoding.cpp#L238"&gt;OpenFHE’s CKKS encoding routine&lt;/a&gt;,
which implements Algorithm 1 of
&lt;a href="https://eprint.iacr.org/2018/1043"&gt;Chen-Chillotti-Song 2018&lt;/a&gt;.&lt;sup&gt;&lt;a href="https://www.jeremykun.com/2026/04/29/ckks-polynomials-the-canonical-embedding-and-encoding/#fn:8"&gt;8&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="https://github.com/j2kun/ckks-tutorial/pull/2/changes/386f028f079c91968a90846f80500321c60930b7"&gt;Commit 386f028&lt;/a&gt;
also includes a more direct implementation using a matrix-vector multiplication
by the &lt;a href="https://github.com/j2kun/ckks-tutorial/pull/2/changes/fb58601d16e55bc8a5a4ce3c0a99c1e10d879329#diff-3e74404a7218b087c3cd6ff47dd023a496e36d441a1d7fe9cbcdec91aa8b05ccR77"&gt;Vandermonde matrix&lt;/a&gt;.
In the tests for this commit,
we include equivalence testing of the two methods.&lt;/p&gt;&lt;h2&gt;Encoding and decoding&lt;/h2&gt;&lt;p&gt;With all the hard work done,
we turn to encoding.
The inverse of the canonical embedding
allows us to map a complex (Hermitian) vector
to a polynomial with real coefficients.
However, to get a polynomial with integer coefficients (our desired plaintext
space), we need to round. That raises the question of precision.&lt;/p&gt;&lt;p&gt;CKKS’s solution is the same as traditional fixed-point arithmetic.
That is, we choose a scaling factor $\Delta$,
and multiply the polynomial’s coefficients by $\Delta$ before rounding
to the nearest integer.
The message can be recovered by dividing by $\Delta$.&lt;/p&gt;&lt;p&gt;Fixed-point arithmetic will have major implications for CKKS.
Specifically, it introduces an application-dependent decision
for how to set parameters. Applications that can afford to be
a bit less precise (like neural networks) can use smaller
scaling factors, which leads to more efficient programs.
We will return to this topic in fine detail later.
For now, it gives us our final encoding algorithm:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Apply the inverse canonical embedding.&lt;/li&gt;&lt;li&gt;Multiply by the scale $\Delta$.&lt;/li&gt;&lt;li&gt;Round to the nearest integer mod $Q$.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Because scaling by $\Delta$ is followed by rounding
mod $Q$, the choice of $Q$ implies a limit to the choice
of $\Delta$: the scaled coefficients after step 2
cannot exceed $Q/2$, or else they will wrap around mod $Q$
and the original value will be lost.&lt;/p&gt;&lt;p&gt;The code in &lt;a href="https://github.com/j2kun/ckks-tutorial/pull/2/changes/2410f9da9781752b399bc589abd1df03dc4a6ea7"&gt;commit 2410f9d&lt;/a&gt;
demonstrates this.&lt;/p&gt;&lt;p&gt;First we have a dataclass for parameters&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;Cleartext &lt;span&gt;=&lt;/span&gt; np&lt;span&gt;.&lt;/span&gt;ndarray
&lt;/span&gt;&lt;span&gt;Plaintext &lt;span&gt;=&lt;/span&gt; Polynomial
&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@dataclass&lt;/span&gt;(frozen&lt;span&gt;=&lt;/span&gt;&lt;span&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;span&gt;&lt;span&gt;class&lt;/span&gt; &lt;span&gt;EncodingParams&lt;/span&gt;:
&lt;/span&gt;&lt;span&gt;    scale: float
&lt;/span&gt;&lt;span&gt;    poly_modulus_degree: int
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then encoding is&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt; &lt;span&gt;encode&lt;/span&gt;(message: Cleartext, params: EncodingParams) &lt;span&gt;-&amp;gt;&lt;/span&gt; Plaintext:
&lt;/span&gt;    &lt;span&gt;"""Encode a vector of complex numbers into a plaintext polynomial."""&lt;/span&gt;
    &lt;span&gt;# Pad with zeros up to N / 2&lt;/span&gt;
&lt;span&gt;    num_zeros &lt;span&gt;=&lt;/span&gt; params&lt;span&gt;.&lt;/span&gt;poly_modulus_degree &lt;span&gt;//&lt;/span&gt; &lt;span&gt;2&lt;/span&gt; &lt;span&gt;-&lt;/span&gt; message&lt;span&gt;.&lt;/span&gt;shape[&lt;span&gt;0&lt;/span&gt;]
&lt;/span&gt;&lt;span&gt;    &lt;span&gt;if&lt;/span&gt; num_zeros:
&lt;/span&gt;&lt;span&gt;        message &lt;span&gt;=&lt;/span&gt; np&lt;span&gt;.&lt;/span&gt;concatenate(
&lt;/span&gt;&lt;span&gt;            [message, np&lt;span&gt;.&lt;/span&gt;zeros(num_zeros, dtype&lt;span&gt;=&lt;/span&gt;message&lt;span&gt;.&lt;/span&gt;dtype)]
&lt;/span&gt;&lt;span&gt;        )
&lt;/span&gt;
    &lt;span&gt;# Concat with flipped conjugate to make Hermitian&lt;/span&gt;
&lt;span&gt;    hermitian_msg &lt;span&gt;=&lt;/span&gt; np&lt;span&gt;.&lt;/span&gt;concatenate(
&lt;/span&gt;&lt;span&gt;        [message, np&lt;span&gt;.&lt;/span&gt;flip(np&lt;span&gt;.&lt;/span&gt;conjugate(message))]
&lt;/span&gt;&lt;span&gt;    )
&lt;/span&gt;
    &lt;span&gt;# Result of inverse canonical_embedding is guaranteed to&lt;/span&gt;
    &lt;span&gt;# be real-valued.&lt;/span&gt;
&lt;span&gt;    polynomial &lt;span&gt;=&lt;/span&gt; inverse_canonical_embedding(hermitian_msg)
&lt;/span&gt;&lt;span&gt;    rounded_scaled_coeffs &lt;span&gt;=&lt;/span&gt; np&lt;span&gt;.&lt;/span&gt;round(
&lt;/span&gt;&lt;span&gt;        np&lt;span&gt;.&lt;/span&gt;real(polynomial&lt;span&gt;.&lt;/span&gt;coefficients) &lt;span&gt;*&lt;/span&gt; params&lt;span&gt;.&lt;/span&gt;scale
&lt;/span&gt;&lt;span&gt;    )
&lt;/span&gt;&lt;span&gt;    &lt;span&gt;return&lt;/span&gt; Polynomial(rounded_scaled_coeffs, params&lt;span&gt;.&lt;/span&gt;poly_modulus_degree)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Similarly, decoding divides by the scale, applies the canonical embedding,
and then returns the first $N/2$ slots.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt; &lt;span&gt;decode&lt;/span&gt;(plaintext: Plaintext, params: EncodingParams) &lt;span&gt;-&amp;gt;&lt;/span&gt; Cleartext:
&lt;/span&gt;    &lt;span&gt;"""Decode a CKKS plaintext into a vector of complex numbers."""&lt;/span&gt;
&lt;span&gt;    scale_removed &lt;span&gt;=&lt;/span&gt; Polynomial(
&lt;/span&gt;&lt;span&gt;        coefficients&lt;span&gt;=&lt;/span&gt;plaintext&lt;span&gt;.&lt;/span&gt;coefficients &lt;span&gt;/&lt;/span&gt; params&lt;span&gt;.&lt;/span&gt;scale,
&lt;/span&gt;&lt;span&gt;        modulus_degree&lt;span&gt;=&lt;/span&gt;plaintext&lt;span&gt;.&lt;/span&gt;modulus_degree,
&lt;/span&gt;&lt;span&gt;    )
&lt;/span&gt;&lt;span&gt;    unembedded &lt;span&gt;=&lt;/span&gt; canonical_embedding(scale_removed)
&lt;/span&gt;&lt;span&gt;    &lt;span&gt;return&lt;/span&gt; unembedded[: params&lt;span&gt;.&lt;/span&gt;poly_modulus_degree &lt;span&gt;//&lt;/span&gt; &lt;span&gt;2&lt;/span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Investigating precision&lt;/h2&gt;&lt;p&gt;To understand the precision loss of CKKS encoding in a bit more detail,
let’s plot it.&lt;/p&gt;&lt;p&gt;&lt;a href="https://github.com/j2kun/ckks-tutorial/pull/2/changes/0df56503d2490810d9032279b3b59cab62e0ff79"&gt;Commit 0df5650&lt;/a&gt;
provides the code, and for $N=32$ with scaling factors ranging from
$2$ to $2^{40}$ (the latter is a typical CKKS scaling factor seen in the wild),
this is the plot of absolute and relative errors.&lt;/p&gt;&lt;figure&gt;&lt;a href="https://www.jeremykun.com/img/2026/ckks-encoding-precision.png"&gt;&lt;img src="https://www.jeremykun.com/img/2026/ckks-encoding-precision.png"&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p&gt;The top plot shows that absolute precision scales linearly with the scaling
factor. The bottom plot shows the relative deviation from the theoretical
bound, and even for a smallish $N=32$ the fit is pretty good. The “theoretical”
line plotted corresponds to the average expected root-mean-squared precision
loss, by following heuristic reasoning.&lt;/p&gt;&lt;p&gt;For $N$ random values, rounding introduces a uniform error in $[-0.5, 0.5]$
for each of the coefficients. Decoding sums these errors, and the resulting
distribution is Gaussian with mean roughly $\sqrt{N}$.&lt;/p&gt;&lt;p&gt;We can also plot this as $N$ grows (&lt;a href="https://github.com/j2kun/ckks-tutorial/pull/2/changes/090147f2b4a08abea7953f5284e838ad8986c8af"&gt;commit 090147f&lt;/a&gt;).&lt;/p&gt;&lt;figure&gt;&lt;a href="https://www.jeremykun.com/img/2026/ckks-encoding-precision-vs-N.png"&gt;&lt;img src="https://www.jeremykun.com/img/2026/ckks-encoding-precision-vs-N.png"&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p&gt;The theoretical bound still holds, except that when $N$ is sufficiently large
(and when the scaling factor is sufficiently large), then, as best I can tell,
the floating point errors in the FFT routine itself are of comparable magnitude
to the precision loss due to rounding, which would explain the positive bias
in error. At least, if I reduce the scaling factor to $2^{20}$, this positive
bias disappears:&lt;/p&gt;&lt;figure&gt;&lt;a href="https://www.jeremykun.com/img/2026/ckks-encoding-precision-vs-N-2-20.png"&gt;&lt;img src="https://www.jeremykun.com/img/2026/ckks-encoding-precision-vs-N-2-20.png"&gt;&lt;/a&gt;&lt;/figure&gt;&lt;h2&gt;Wrapping it up&lt;/h2&gt;&lt;p&gt;Recapping, the two obstacles we faced at the start of the article were:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;We must transform a vector of complex numbers into a polynomial.&lt;/li&gt;&lt;li&gt;We must discretize an inherently continuous quantity, complex numbers.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The canonical embedding solves the first obstacle by providing an isomorphism
between vectors of complex numbers and polynomials. The use of fixed-point
arithmetic and a scaling factor solves the second.&lt;/p&gt;&lt;p&gt;While CKKS encoding does introduce errors in its encoding process, the standard
intended application&lt;sup&gt;&lt;a href="https://www.jeremykun.com/2026/04/29/ckks-polynomials-the-canonical-embedding-and-encoding/#fn:9"&gt;9&lt;/a&gt;&lt;/sup&gt; of CKKS is to machine learning inference. In this context,
programs are naturally tolerant of error, and the encoding error is a small
one-time cost. As we will see throughout the rest of the tutorial, encoding
error will become dominated by rescaling and bootstrapping noise. But either
way, it’s important not to forget about this source of error.&lt;/p&gt;&lt;h2&gt;Acknowledgements&lt;/h2&gt;&lt;p&gt;Thanks to &lt;a href="https://scholar.google.com/citations?user=ztrus-YAAAAJ&amp;amp;hl=en"&gt;Asra Ali&lt;/a&gt;,
&lt;a href="https://edwjchen.com/"&gt;Edward Chen&lt;/a&gt;,
&lt;a href="https://jianmingtong.github.io/"&gt;Jianming Tong&lt;/a&gt;, and
&lt;a href="https://hongrenzhe.ng/"&gt;Hongren Zheng&lt;/a&gt; for feedback on a draft of this
article.&lt;/p&gt;&lt;hr&gt;&lt;ol morss_own_score="2.8413461538461537" morss_score="12.795908886315864"&gt;&lt;li&gt;&lt;p&gt;UC San Diego is involved because Miran Kim was doing a postdoc
there at the time. Now she is a professor at Hanyang University in South
Korea. I bring this up mainly to note that Korea has been a powerhouse of
innovation in homomorphic encryption for the past decade, and particularly
for its contributions to CKKS, its variants, and the variety of startups
that have sprung up around it. &lt;a href="https://www.jeremykun.com/2026/04/29/ckks-polynomials-the-canonical-embedding-and-encoding/#fnref:1"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;As my colleague Hongren Zheng reminded me, the original
high-precision modulus for CKKS was a large power of two. So the switch
to RNS-CKKS also required switching to a modulus that was a product of
machine-word-sized primes. &lt;a href="https://www.jeremykun.com/2026/04/29/ckks-polynomials-the-canonical-embedding-and-encoding/#fnref:2"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;As far as I can tell, except for some specialized research papers
or hand-compiled applications, the extra complex structure is not used in
applications. That is, most CKKS users treat the cleartext space as a
vector of double-precision floating point values. &lt;a href="https://www.jeremykun.com/2026/04/29/ckks-polynomials-the-canonical-embedding-and-encoding/#fnref:3"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Indeed, if you’re paying attention to any of the work on post-quantum
cryptography (which all of FHE is built on top of), you’ll see this ring
again in the Kyber/ML-KEM scheme, though it is important to note that the
choice of parameters $Q, N$ are meaningfully different there. &lt;a href="https://www.jeremykun.com/2026/04/29/ckks-polynomials-the-canonical-embedding-and-encoding/#fnref:4"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Many math texts give the existential definition “the smallest field
containing $\mathbb{Q}$ and the added elements.” &lt;a href="https://www.jeremykun.com/2026/04/29/ckks-polynomials-the-canonical-embedding-and-encoding/#fnref:5"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;This is basically a first course in Galois theory. I enjoyed
&lt;a href="https://amzn.to/3OllR79"&gt;Ian Stewart’s textbook&lt;/a&gt; on the subject. But if
you squint you can kind of see it: $x^N + 1$ is a factor of $x^{2N} - 1 =
(x^N-1)(x^N+1)$, and the complex $2N$-th roots of unity are the roots of
$x^{2N} - 1$, with the primitive root generating all the rest and being
(any one of) the roots of the irreducible part $x^N + 1$. This
irreducibility requires $N$ is a power of two, but you can do the same
logic for any $N$ if you spend more time working out the $N$-th cyclotomic
polynomial. &lt;a href="https://www.jeremykun.com/2026/04/29/ckks-polynomials-the-canonical-embedding-and-encoding/#fnref:6"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;It may be worth a sneak preview here: the choice of which primitive
root and the order of the evaluations is arbitrary, and choosing a
different root and order will be useful for CKKS in that it will allow
us to define &lt;em&gt;slot rotation&lt;/em&gt; as a homomorphic operation. At that point
we’ll have to make some slight tweaks to the encoding algorithm here.
Having a precise diff of the code changes will make those differences
clearer later, I hope. &lt;a href="https://www.jeremykun.com/2026/04/29/ckks-polynomials-the-canonical-embedding-and-encoding/#fnref:7"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Interestingly, Algorithm 1 was designed for evaluating the
encoding/decoding algorithm of CKKS &lt;em&gt;homomorphically&lt;/em&gt; (as part of
bootstrapping, which we’ll see later in this series in detail), but the
referenced OpenFHE code uses it for cleartext evaluation for encoding. &lt;a href="https://www.jeremykun.com/2026/04/29/ckks-polynomials-the-canonical-embedding-and-encoding/#fnref:8"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;For more, see &lt;a href="https://www.jeremykun.com/fhe-in-production/"&gt;FHE in production&lt;/a&gt;. &lt;a href="https://www.jeremykun.com/2026/04/29/ckks-polynomials-the-canonical-embedding-and-encoding/#fnref:9"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;hr&gt;&lt;p&gt;Want to respond? &lt;a href="mailto:mathintersectprogramming@gmail.com"&gt;Send me an email&lt;/a&gt;,
&lt;a href="https://webmention.io/www.jeremykun.com/webmention"&gt;post a webmention&lt;/a&gt;,
or find me &lt;a href="https://www.jeremykun.com/about/"&gt;elsewhere on the internet&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;This article is syndicated on:&lt;/p&gt;&lt;hr&gt;&lt;/div&gt;&lt;/article&gt;</ns0:encoded></item><item><title>Unusual uses of OEIS sequences on GitHub</title><link>https://www.jeremykun.com/shortform/2026-04-13-0700/</link><pubDate>Mon, 13 Apr 2026 07:00:00 -0700</pubDate><guid>https://www.jeremykun.com/shortform/2026-04-13-0700/</guid><description>I went hunting for references to the OEIS in open source code, and found some weird ones.
There are not one, but two live-coding music frameworks that use OEIS sequences as a source for &amp;ldquo;anything that can be sequenced&amp;rdquo; in music. I&amp;rsquo;m guessing that&amp;rsquo;s used for choosing pseudorandom melodies, interesting rhythyms, or how to overlap tracks in different ways.
The first project is called mercury, which is advertised as having &amp;ldquo;an extensive library of algorithms to generate or transform numbersequences that can modulate parameters.</description><ns0:encoded xmlns:ns0="http://purl.org/rss/1.0/modules/content/">&lt;article id="main" class="content-container look-sheet article-pad-v h-entry" itemscope="" itemtype="https://schema.org/Article" morss_own_score="7.825065274151436" morss_score="13.999862022118915"&gt;&lt;h1&gt;Unusual uses of OEIS sequences on GitHub&lt;/h1&gt;&lt;span&gt;2026-04-13&lt;/span&gt;&lt;div itemprop="articleBody" id="content" class="article-body margin-top-2em" morss_own_score="5.349593495934959" morss_score="42.152420463825734"&gt;&lt;p&gt;I went hunting for references to the OEIS in open source code,
and found some weird ones.&lt;/p&gt;&lt;p&gt;There are not one, but two live-coding music frameworks
that use OEIS sequences as a source for “anything that can be sequenced”
in music. I’m guessing that’s used for choosing pseudorandom melodies,
interesting rhythyms, or how to overlap tracks in different ways.&lt;/p&gt;&lt;p&gt;The first project is called &lt;a href="https://github.com/tmhglnd/mercury"&gt;mercury&lt;/a&gt;,
which is advertised as having “an extensive library of algorithms to generate
or transform numbersequences that can modulate parameters.”&lt;/p&gt;&lt;p&gt;As far as OEIS sequences, they have A000045 (Fibonacci), A006190 (Fibonacci-like),
A000032 (Lucas), A000129 (Pell), which are all Fibonacci-like.&lt;/p&gt;&lt;p&gt;Then there’s &lt;a href="https://github.com/amiika/ziffers"&gt;ziffers&lt;/a&gt;,
which is an extension for Sonic Pi. In their
ziffers/lib/enumerables.rb
there are a lot more, and weirder sequences.&lt;/p&gt;&lt;p&gt;It has the de Bruijn sequence (A000695),
Recamán’s sequence (A005132),
Thue-Morse (A010060),
Dress’s sequence (A001316), and many more.
There are a bunch of 10-adic decimal expansions like A225410,
the 10-adic integer x such that $x^3 = 7/9$,
which seems&amp;amp;mldr;music-theory ish?&lt;/p&gt;&lt;p&gt;And then there’s the Inventory Sequence, &lt;a href="http://oeis.org/A342585"&gt;A342585&lt;/a&gt;
(oh goodness what is going on there), which seems very much NOT music-theory ish.&lt;/p&gt;&lt;p&gt;My real question is: how does music that relies on these weird sequences
actually sound? I can’t imagine a melody decided by the Inventory Sequence sounds very good.
Every time someone does music based on the digits of pi,
it’s kind of meh.
But let me know if you’ve tried this.&lt;/p&gt;&lt;p&gt;The Kobo e-reader has a document viewing program called &lt;a href="https://github.com/baskerville/plato/"&gt;Plato&lt;/a&gt;.
It has a pen tool for markup, and for whatever reason,
they use A000041 (the number of partitions of n) as the options for pen size.
No reasoning was given in the commit/PR that added this.&lt;/p&gt;&lt;p&gt;Finally, the &lt;a href="https://github.com/GCWizard/GCWizard"&gt;GC wizard&lt;/a&gt;
is a geocaching app that serves as
“an offline tool to support geocachers with in-field mysteries and riddles.”&lt;/p&gt;&lt;p&gt;There are many hard-coded OEIS sequences and formulas in it,
which leads to the amusing mental image of cache hunters standing in the wilderness,
trying to decode a clue based on the look-and-say sequence,
maybe using a stick to draw formulas in the dirt.&lt;/p&gt;&lt;hr&gt;&lt;p&gt;Want to respond? &lt;a href="mailto:mathintersectprogramming@gmail.com"&gt;Send me an email&lt;/a&gt;,
&lt;a href="https://webmention.io/www.jeremykun.com/webmention"&gt;post a webmention&lt;/a&gt;,
or find me &lt;a href="https://www.jeremykun.com/about/"&gt;elsewhere on the internet&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;This article is syndicated on:&lt;/p&gt;&lt;hr&gt;&lt;/div&gt;&lt;/article&gt;</ns0:encoded></item><item><title>The OEIS meta sequence and subway stations</title><link>https://www.jeremykun.com/shortform/2026-04-09-0556/</link><pubDate>Thu, 09 Apr 2026 06:55:17 -0700</pubDate><guid>https://www.jeremykun.com/shortform/2026-04-09-0556/</guid><description>A051070 is a sequence about OEIS sequences. a(n) is the n-th term in sequence A_n (or -1 if A_n doesn&amp;rsquo;t have enough terms).
So the first term in A051070 is 1 because A000001 is the number of groups of order n, and that sequence has 1 as its entry in index 1. A000002 is the Kolakoski sequence (what? For another time) and has value 2 in entry 2. The sequence continues: 1, 2, 1, 0, 2, 3, 0, 7, 8, 4, 63, 1, 316, &amp;hellip;</description><ns0:encoded xmlns:ns0="http://purl.org/rss/1.0/modules/content/">&lt;article id="main" class="content-container look-sheet article-pad-v h-entry" itemscope="" itemtype="https://schema.org/Article" morss_own_score="8.10179640718563" morss_score="14.367228505951061"&gt;&lt;h1&gt;The OEIS meta sequence and subway stations&lt;/h1&gt;&lt;span&gt;2026-04-09&lt;/span&gt;&lt;div itemprop="articleBody" id="content" class="article-body margin-top-2em" morss_own_score="5.530864197530864" morss_score="32.04374298540965"&gt;&lt;p&gt;&lt;a href="https://oeis.org/A051070"&gt;A051070&lt;/a&gt; is a sequence about OEIS sequences.
a(n) is the n-th term in sequence A_n (or -1 if A_n doesn’t have enough
terms).&lt;/p&gt;&lt;p&gt;So the first term in A051070 is 1 because A000001 is the number of
groups of order n, and that sequence has 1 as its entry in index 1. A000002 is
the Kolakoski sequence (what? For another time) and has value 2 in entry 2.
The sequence continues: 1, 2, 1, 0, 2, 3, 0, 7, 8, 4, 63, 1, 316, &amp;amp;mldr;&lt;/p&gt;&lt;p&gt;At first you might think, “what in the Gödel?” What if the arbitrary indexing
of the OEIS changes over time? Aren’t these sequences supposed to be defined by
mathematical rules?&lt;/p&gt;&lt;p&gt;Not the fun ones, apparently. In the comments, Pontus von Brömssen noted
that a(58) has 58669977298272603 digits, so it’s too large to include
in the database entry for A051070. a(66) is the first unknown value,
because A000066 (Smallest number of vertices in trivalent graph with girth (shortest cycle) = n)
is only known up to 12 vertices.
And then we get to my two favorite quirks about this sequence.&lt;/p&gt;&lt;p&gt;The first is that the first time a(n) = -1 occurs, it’s for n = 53 and 54,
quoting the OEIS, “in both cases because the relevant New York subway lines do not have enough stops.”
What? Why are New York Subway lines involved?
Turns out, the OEIS has roughly a dozen sequences of numbered stops on train lines.
A000053 is “Local stops on New York City 1 Train (Broadway-7 Avenue Local) subway.”
A001049 is “Numbered stops in Manhattan on the Lexington Avenue subway.”
Of course, this chips away even further at the idea that OEIS sequences
need to have a mathematical definition removed from worldly messiness.
Digging around, I could only find a short note in &lt;a href="https://www.youtube.com/watch?v=ydn7s9-3GRc"&gt;this Numberphile video&lt;/a&gt;
where Neil Sloane (who created OEIS and added these entries)
mentioned that they’re commonly used on math quizzes and tests.
If you know someone who has used train lines on their quizzes,
and didn’t already know about these OEIS entries,
please let me know. I need this to be a common organic experience.&lt;/p&gt;&lt;p&gt;The second quirk is that A051070 leaves open the question of
what the value of a(51070) is.
It gets worse with &lt;a href="https://oeis.org/A102288"&gt;A102288&lt;/a&gt;, which is defined as
1 + the n-th term in sequence A_n. (There are some slight differences about offsets here, but I’m using A102288 because it has juicier comments)
Even if there was a default value for the 102288-th entry in this sequence,
it would contradict its own definition.&lt;/p&gt;&lt;p&gt;There is an argument in the comments section, which starts with an unattributed “What is a(102288)?!”
M. F. Hasler complained in 2017: “The term a(102288) has no possible value according to the present definition, so the definition of this term should be changed.”
Neil Sloane replied the same day: “I disagree with the previous comment! I prefer the present, deliberately paradoxical, definition.”
In the age-old battle between whimsy and well-definedness, whimsy wins again.&lt;/p&gt;&lt;hr&gt;&lt;p&gt;Want to respond? &lt;a href="mailto:mathintersectprogramming@gmail.com"&gt;Send me an email&lt;/a&gt;,
&lt;a href="https://webmention.io/www.jeremykun.com/webmention"&gt;post a webmention&lt;/a&gt;,
or find me &lt;a href="https://www.jeremykun.com/about/"&gt;elsewhere on the internet&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;This article is syndicated on:&lt;/p&gt;&lt;hr&gt;&lt;/div&gt;&lt;/article&gt;</ns0:encoded></item><item><title>Deterministic Primality Testing for Limited Bit Width</title><link>https://www.jeremykun.com/2026/04/07/deterministic-miller-rabin/</link><pubDate>Tue, 07 Apr 2026 06:00:00 -0700</pubDate><guid>https://www.jeremykun.com/2026/04/07/deterministic-miller-rabin/</guid><description>Problem: Determine if a 32-bit number is prime (deterministically)
Solution: (in C++)
// Bases to test. Using the first 4 prime bases makes the test deterministic // for all 32-bit integers. See https://oeis.org/A014233. int64_t bases[] = {2, 3, 5, 7}; inline int countTrailingZeros(uint64_t n) { if (n == 0) return 64; return __builtin_ctzll(n); } int64_t modularExponentiation(int64_t base, int64_t exponent, int64_t modulus) { int64_t res = 1; int64_t b = base % modulus; int64_t e = exponent; while (e &amp;gt; 0) { if (e &amp;amp; 1) { // Doesn&amp;#39;t overflow because we assume 32-bit integer inputs res = (res * b) % modulus; } b = (b * b) % modulus; e &amp;gt;&amp;gt;= 1; } return res; } bool isPrime(int64_t n) { if (n &amp;lt; 2) return false; if (n &amp;lt; 4) return true; if (!</description><ns0:encoded xmlns:ns0="http://purl.org/rss/1.0/modules/content/">&lt;article id="main" class="content-container look-sheet article-pad-v h-entry" itemscope="" itemtype="https://schema.org/Article" morss_own_score="7.475247524752475" morss_score="13.52879882125755"&gt;&lt;h1&gt;Deterministic Primality Testing for Limited Bit Width&lt;/h1&gt;&lt;span&gt;2026-04-07&lt;/span&gt;&lt;div itemprop="articleBody" id="content" class="article-body margin-top-2em" morss_own_score="5.107102593010146" morss_score="48.75939493597984"&gt;&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Determine if a 32-bit number is prime (deterministically)&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; (in C++)&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;// Bases to test. Using the first 4 prime bases makes the test deterministic
&lt;/span&gt;&lt;span&gt;// for all 32-bit integers. See https://oeis.org/A014233.
&lt;/span&gt;&lt;span&gt;&lt;span&gt;int64_t&lt;/span&gt; bases[] &lt;span&gt;=&lt;/span&gt; {&lt;span&gt;2&lt;/span&gt;, &lt;span&gt;3&lt;/span&gt;, &lt;span&gt;5&lt;/span&gt;, &lt;span&gt;7&lt;/span&gt;};
&lt;/span&gt;
&lt;span&gt;&lt;span&gt;inline&lt;/span&gt; &lt;span&gt;int&lt;/span&gt; &lt;span&gt;countTrailingZeros&lt;/span&gt;(&lt;span&gt;uint64_t&lt;/span&gt; n) {
&lt;/span&gt;&lt;span&gt;  &lt;span&gt;if&lt;/span&gt; (n &lt;span&gt;==&lt;/span&gt; &lt;span&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;span&gt;    &lt;span&gt;return&lt;/span&gt; &lt;span&gt;64&lt;/span&gt;;
&lt;/span&gt;&lt;span&gt;  &lt;span&gt;return&lt;/span&gt; __builtin_ctzll(n);
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;
&lt;span&gt;&lt;span&gt;int64_t&lt;/span&gt; &lt;span&gt;modularExponentiation&lt;/span&gt;(&lt;span&gt;int64_t&lt;/span&gt; base, &lt;span&gt;int64_t&lt;/span&gt; exponent, &lt;span&gt;int64_t&lt;/span&gt; modulus) {
&lt;/span&gt;&lt;span&gt;  &lt;span&gt;int64_t&lt;/span&gt; res &lt;span&gt;=&lt;/span&gt; &lt;span&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;span&gt;  &lt;span&gt;int64_t&lt;/span&gt; b &lt;span&gt;=&lt;/span&gt; base &lt;span&gt;%&lt;/span&gt; modulus;
&lt;/span&gt;&lt;span&gt;  &lt;span&gt;int64_t&lt;/span&gt; e &lt;span&gt;=&lt;/span&gt; exponent;
&lt;/span&gt;
&lt;span&gt;  &lt;span&gt;while&lt;/span&gt; (e &lt;span&gt;&amp;gt;&lt;/span&gt; &lt;span&gt;0&lt;/span&gt;) {
&lt;/span&gt;&lt;span&gt;    &lt;span&gt;if&lt;/span&gt; (e &lt;span&gt;&amp;amp;&lt;/span&gt; &lt;span&gt;1&lt;/span&gt;) {
&lt;/span&gt;      &lt;span&gt;// Doesn't overflow because we assume 32-bit integer inputs
&lt;/span&gt;&lt;span&gt;      res &lt;span&gt;=&lt;/span&gt; (res &lt;span&gt;*&lt;/span&gt; b) &lt;span&gt;%&lt;/span&gt; modulus;
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;    b &lt;span&gt;=&lt;/span&gt; (b &lt;span&gt;*&lt;/span&gt; b) &lt;span&gt;%&lt;/span&gt; modulus;
&lt;/span&gt;&lt;span&gt;    e &lt;span&gt;&amp;gt;&amp;gt;=&lt;/span&gt; &lt;span&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;span&gt;  &lt;span&gt;return&lt;/span&gt; res;
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;
&lt;span&gt;&lt;span&gt;bool&lt;/span&gt; &lt;span&gt;isPrime&lt;/span&gt;(&lt;span&gt;int64_t&lt;/span&gt; n) {
&lt;/span&gt;&lt;span&gt;  &lt;span&gt;if&lt;/span&gt; (n &lt;span&gt;&amp;lt;&lt;/span&gt; &lt;span&gt;2&lt;/span&gt;) &lt;span&gt;return&lt;/span&gt; false;
&lt;/span&gt;&lt;span&gt;  &lt;span&gt;if&lt;/span&gt; (n &lt;span&gt;&amp;lt;&lt;/span&gt; &lt;span&gt;4&lt;/span&gt;) &lt;span&gt;return&lt;/span&gt; true;
&lt;/span&gt;&lt;span&gt;  &lt;span&gt;if&lt;/span&gt; (&lt;span&gt;!&lt;/span&gt;(n &lt;span&gt;&amp;amp;&lt;/span&gt; &lt;span&gt;1&lt;/span&gt;)) &lt;span&gt;return&lt;/span&gt; false;
&lt;/span&gt;
&lt;span&gt;  &lt;span&gt;int64_t&lt;/span&gt; d &lt;span&gt;=&lt;/span&gt; n &lt;span&gt;-&lt;/span&gt; &lt;span&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;span&gt;  &lt;span&gt;unsigned&lt;/span&gt; s &lt;span&gt;=&lt;/span&gt; countTrailingZeros(d);
&lt;/span&gt;&lt;span&gt;  d &lt;span&gt;=&lt;/span&gt; d &lt;span&gt;&amp;gt;&amp;gt;&lt;/span&gt; s;
&lt;/span&gt;
&lt;span&gt;  &lt;span&gt;for&lt;/span&gt; (&lt;span&gt;uint64_t&lt;/span&gt; a : bases) {
&lt;/span&gt;&lt;span&gt;    &lt;span&gt;if&lt;/span&gt; (n &lt;span&gt;&amp;lt;=&lt;/span&gt; a) &lt;span&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;span&gt;    &lt;span&gt;int64_t&lt;/span&gt; x &lt;span&gt;=&lt;/span&gt; modularExponentiation(a, d, n);
&lt;/span&gt;&lt;span&gt;    &lt;span&gt;if&lt;/span&gt; (x &lt;span&gt;==&lt;/span&gt; &lt;span&gt;1&lt;/span&gt; &lt;span&gt;||&lt;/span&gt; x &lt;span&gt;==&lt;/span&gt; n &lt;span&gt;-&lt;/span&gt; &lt;span&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;span&gt;      &lt;span&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;span&gt;    &lt;span&gt;bool&lt;/span&gt; composite &lt;span&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;span&gt;    &lt;span&gt;for&lt;/span&gt; (&lt;span&gt;unsigned&lt;/span&gt; r &lt;span&gt;=&lt;/span&gt; &lt;span&gt;1&lt;/span&gt;; r &lt;span&gt;&amp;lt;&lt;/span&gt; s; &lt;span&gt;++&lt;/span&gt;r) {
&lt;/span&gt;      &lt;span&gt;// Doesn't overflow because it is at most n &amp;lt; 32 bits
&lt;/span&gt;&lt;span&gt;      x &lt;span&gt;=&lt;/span&gt; (x &lt;span&gt;*&lt;/span&gt; x) &lt;span&gt;%&lt;/span&gt; n;
&lt;/span&gt;&lt;span&gt;      &lt;span&gt;if&lt;/span&gt; (x &lt;span&gt;==&lt;/span&gt; n &lt;span&gt;-&lt;/span&gt; &lt;span&gt;1&lt;/span&gt;) {
&lt;/span&gt;&lt;span&gt;        composite &lt;span&gt;=&lt;/span&gt; false;
&lt;/span&gt;&lt;span&gt;        &lt;span&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;    &lt;span&gt;if&lt;/span&gt; (composite)
&lt;/span&gt;&lt;span&gt;      &lt;span&gt;return&lt;/span&gt; false;
&lt;/span&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;span&gt;  &lt;span&gt;return&lt;/span&gt; true;
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Discussion:&lt;/strong&gt; In the late 1980’s Gary Miller and Michael Rabin came up with
their now-famous Miller-Rabin primality test. See
&lt;a href="https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test"&gt;Wikipedia&lt;/a&gt;
and &lt;a href="https://www.jeremykun.com/2013/06/16/miller-rabin-primality-test/"&gt;my 2013 Program Gallery entry&lt;/a&gt;.
It was notable in that it provided a randomized algorithm to check if an
integer is prime, which had a tunable parameter to increase the probability of
being correct.&lt;/p&gt;&lt;p&gt;The intervening 40 years has seen a huge body of research improving both
randomized and deterministic primality testing. In 2002, &lt;a href="https://en.wikipedia.org/wiki/AKS_primality_test"&gt;Agrawal, Kayal, and
Saxena found&lt;/a&gt; a
condition-free deterministic polynomial-time algorithm, and the
&lt;a href="https://en.wikipedia.org/wiki/Baillie%E2%80%93PSW_primality_test"&gt;Ballie-PSW&lt;/a&gt;
test, designed around the same time as Miller-Rabin, is still often used today
in conjunction with Miller-Rabin. One of my favorite papers showing the
importance of doing primality testing properly is in cryptography, Albrecht et
al’s 2018 paper, &lt;a href="https://dl.acm.org/doi/10.1145/3243734.3243787"&gt;Prime and Prejudice: Primality Testing Under Adversarial
Conditions&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;In relation to &lt;a href="https://heir.dev"&gt;my work on homomorphic encryption&lt;/a&gt;, I found
myself needing to generate a list of 40 primes, all roughly 32-bit in size,
with particular properties. I stumbled across OEIS
&lt;a href="https://oeis.org/A014233"&gt;A014233&lt;/a&gt;, through which I learned about the study
of strong pseudoprimes.&lt;/p&gt;&lt;p&gt;A &lt;em&gt;strong pseudoprime&lt;/em&gt; is a composite number that passes Miller-Rabin’s
deterministic test for a particular prime base $a$. That is, a number $n$ of
the form $d \cdot 2^s + 1$ such that $a^d \equiv 1 \mod n$ or $a^{d \cdot 2^r}
\equiv -1 \mod n$ for some $0 \leq r &amp;lt; s$.
For example, if you only check Miller-Rabin with a base of 2,
$2047 = 23 \cdot 89$ will pass the test.&lt;/p&gt;&lt;p&gt;If you test multiple bases on the same input, you’ll hedge against hitting
small strong pseudoprimes. Testing 2, 3, and 5, means the smallest pseudoprime
that confounds your test is 25326001. The code above demonstrates that if you
add 7 to this list, you get a deterministic test for all 32-bit integers.&lt;/p&gt;&lt;p&gt;To the best of my knowledge, the idea to track the growth rate of strong
pseudoprimes for the purpose of fast primality testing was first published in
&lt;a href="https://doi.org/10.1090/S0025-5718-1980-0572872-7"&gt;a 1980 &lt;em&gt;Mathematics of Computation&lt;/em&gt; journal paper&lt;/a&gt; by Carl
Pomerance, J. L. Selfridge and Samuel S. Wagstaff, Jr., titled “The
Pseudoprimes to $25 \cdot 10^9$.”&lt;/p&gt;&lt;p&gt;The &lt;a href="https://miller-rabin.appspot.com/"&gt;SPRP bases&lt;/a&gt; website has a nice little
competition: who can find the best set of bases (not necessarily prime)
for deterministic primality testing? For each cardinality (number of bases),
the site lists the set of bases that produces the largest minimal pseudoprime
to those bases. For covering all 64-bit integers, Jim Sinclair found this
set of 7 bases.&lt;/p&gt;$$ \{ 2, 325, 9375, 28178, 450775, 9780504, 1795265022 \} $$&lt;p&gt;For 32-bits, it can be done with three 64-bit bases&lt;/p&gt;$$ \{ 4230279247111683200, 14694767155120705706, 16641139526367750375 \} $$&lt;p&gt;though the code above would need to be modified to account for overflow.&lt;/p&gt;&lt;p&gt;Performance-wise, the naive implementation of deterministic Miller-Rabin is
decently fast. It can test primality of all 32-bit numbers in about 2 minutes
on a Macbook (single-threaded). That said, there are far faster
implementations, such as Kim Walisch’s
&lt;a href="https://github.com/kimwalisch/primesieve"&gt;&lt;code&gt;primesieve&lt;/code&gt;&lt;/a&gt;, which can generate all
32-bit primes in 60 milliseconds. Notably, it does &lt;em&gt;not&lt;/em&gt; use deterministic
Miller-Rabin, opting instead for a &lt;a href="https://github.com/kimwalisch/primesieve/blob/master/doc/ALGORITHMS.md"&gt;sieve-based
method&lt;/a&gt;
with lots of cache optimizations and multithreading.&lt;/p&gt;&lt;p&gt;I am not a CPU performance tuning expert, but if you are it might be fun to try
to beat that runtime using this method. The &lt;a href="https://miller-rabin.appspot.com/"&gt;SPRP
bases&lt;/a&gt; also includes a section where they
discuss using hashing to reduce the compute cost of the compositeness test in
Miller-Rabin, which seems like it would be a useful component.
I
&lt;a href="https://github.com/kimwalisch/primesieve/issues/180#issuecomment-4164369713"&gt;asked&lt;/a&gt;
Kim Walisch, the author of &lt;code&gt;primesieve&lt;/code&gt;, and they replied that they had not tried
a deterministic Miller-Rabin variant.&lt;/p&gt;&lt;p&gt;Finally, it is also worth noting&lt;sup&gt;&lt;a href="https://www.jeremykun.com/2026/04/07/deterministic-miller-rabin/#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; that the
&lt;a href="https://en.wikipedia.org/wiki/Baillie%E2%80%93PSW_primality_test"&gt;Ballie-PSW&lt;/a&gt;
test is also randomized, and also is known to be deterministic up to 64 bits.
In fact, there is no known composite number that passes the Ballie-PSW test.
I mainly chose not to implement it here because it’s a bit more complicated
than Miller-Rabin.&lt;/p&gt;&lt;p&gt;The code above is also &lt;a href="https://github.com/j2kun/deterministic-miller-rabin"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;&lt;hr&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;User &lt;code&gt;less_less&lt;/code&gt; on HackerNews mentioned this, which I realize now was
an oversight not to mention. &lt;a href="https://www.jeremykun.com/2026/04/07/deterministic-miller-rabin/#fnref:1"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;hr&gt;&lt;p&gt;Want to respond? &lt;a href="mailto:mathintersectprogramming@gmail.com"&gt;Send me an email&lt;/a&gt;,
&lt;a href="https://webmention.io/www.jeremykun.com/webmention"&gt;post a webmention&lt;/a&gt;,
or find me &lt;a href="https://www.jeremykun.com/about/"&gt;elsewhere on the internet&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;This article is syndicated on:&lt;/p&gt;&lt;hr&gt;&lt;/div&gt;&lt;/article&gt;</ns0:encoded></item></channel></rss>