#complexnumbers

2025-04-18

Decagon (fractal version)

\(z_{n+1}=fold(z_n)^2+c\)

where fold is a generalized absolute value function. A complex number has two components: a real and an imaginary part.
If we take the absolute value of one of these parts, we can interpret this as a fold in the complex plane. For example, |re(z)| causes a fold of the complex plane around the imaginary axis, which means that the left half ends up on the right half. If we do this for the imaginary component |im(z)|, we fold the complex plane around the real axis which means that the bottom half ends up on the top half.
These two operations are quite similar, because the imaginary fold is just like the real fold of the plane, except that it was previously rotated 90 degrees (z * i). But what if we rotate the plane by an arbitrary number of degrees?
An arbitrary rotation of the complex plane can be expressed as rot(z, radians) = z * (cos(radians) + sin(radians) * i), where radians encodes the rotation.

The image here is produced, by rotating the plane exactly five times, and folding the imaginary part each time.

I found this algorithm in the Fractal Formus under the name “Correction for the Infinite Burning Ship Fractal Algorithm”.
It can be seen as a generalization of the burning ship obtained by folding the complex plane twice with a rotation of 90 degrees, i.e. folding both the real and the imaginary part.

#fractalfriday #fractal #burningship #mandelbrot #complexplane #complexnumbers #mathart #math #escapetimefractals

Black fractal structures on a mostly green background, rendered using distance estimation.
TechGeeksApparelTechGeeksApparel
2025-03-26

Some relationships just don’t add up.
But in math? It’s all about real and imaginary compatibility.

🔗 techgeeksapparel.com/its-compl

2025-03-03

#mathartmarch II: On a square grid

A third degree Newton fractal zoom in.

Domain coloring technique adapted from this beauty: shadertoy.com/view/wld3zl
Sorry for the not-so-high quality, I'm too lazy to export it to a direct video at the moment.

#grid #newton #fractal #complexnumbers #complexplane #glsl #shader

2025-01-14
2025-01-14
2024-12-28

Every polynomial with real coefficients factors into linear and quadratic terms.

How much machinery is needed to show this?

If it crosses the X-axis then it has a linear term.

If it doesn't cross the X-axis then it is of even degree, and the roots come in complex conjugate pairs.

What the minimum needed to see this?

#maths #math
#algebra #ComplexNumbers
#MathsChat #MathChat

2024-12-05

Faffing about with complex numbers again

Integers have these things called complex conjugates. This is something I have just been learning about. Or not. You’ll soon see my mathematical shortcomings. This thing is, I think, a way to express them as complex numbers. (I think that was the takeaway of the videos I just watched).

I’d better quote the wiki because that feels like a rubbish explanation:

In mathematics, the complex conjugate of a complex number is the number with an equal real part and an imaginary part equal in magnitude but opposite in sign.

Complex conjugate, Wikipedia

The first five look like this according to Google’s AI

IntergerComplex conjugate1122-0i33-i4455

I’m only actually interested in the first three because we could do something fun with those.

I previously showed my broken work as I wondered about the Collatz Conjecture over the complex plane. Mostly I wanted to know if complex numbers could be odd or even.

It’s possible to define even and odd Gaussian integers* in such a way that many of the familiar properties of the plain old integers are preserved, but it’s a little counterintuitive at first. The definition is that a+bi is even when a^2+b^2 is even (this is the norm of a+bi) and that a+bi is odd if it’s not even.

An answer from Mastodon that I shared here

So this got the old thinker ticking again. What if, I wondered, instead of 3x+1 or x/2, we used the complex numbers and went (3-i)x+1 and x/(2-0i) would that yield something interesting?

The Collatz Conjecture is that if you play a game where if a number is odd you times by three and add one and if even you divide by two that eventually you always get to one which loops to 4, 2, and back to 1. This is the only loop.

Test number 1 – powers of two

I asked Wolfram Alpha what 64/(2-0i) was. It is 32. Apply again and get 16. This seems to work all the way down.

Test number 2 – arbitrary odd numbers

I chose 7 – (3-i)7+1. The answer is 22-7i. That’s interesting.

Then I stopped and asked myself is 7 as a complex whatnot still odd? I asked what the complex conjugate of 7 is and, apparently, it is 7. So, I assume that’s the same as 7+0i but at this point, I clearly have no idea what I am talking about.

I’ve never let that stop me so let’s assume I am correct and have not utterly failed to understand some stuff.

7+0i is even if 7^2+0^2 is even. 49 is not even so, no 7 is not even on the complex doo-dah.

I’m not sure if this is good or bad but we have the Collatz step from 7 to 22 but with an extra -7i as baggage.

Test 3 – just run some numbers

I’m going to pick some smallish real integers and do this more complicated thing to them and see how long the real part stays correct for the simple version of Collatz.

Remember, I’m going (3-i)x+1 for complex odds and x/(2-0i) for evens.

7 -> 22-7i That’s odd for a complex number (the sum of the powers is 533)

22 should have gone to 11. Thus we have already deviated. Our new game leads us to 60-43i

And… I stopped.

First Conclusion

This game does not seem to play well over the complex plane. By “play well”, I mean do fun things.

Just out of idle curiosity, I asked what 22-7i over 2-0i was 11-3.5i. 11(3-i)+1 is 34-11i -> 17-5.5i…

Second conclusion

Well, I just got all excited over nothing. I truly hoped there was a nice pattern to find adding complex numbers to the Collatz thingy. Maybe there is but it might require writing some bad python to make a chart or something.

Ideas?

#CollatzConjecture #complexNumbers

2024-10-09
Творческий коллектив Complex Numbers выпустил бету новой оперы 2084.
Опера состоит из 40 треков.
Ключевые темы: радикальное продление жизни, глобальные угрозы новому человечеству, перешивка психики под полную рациональность, искусственное управление эмоциями, технологии всеобщего счастья, проблема единства и неделимости сознания, панпсихизм, открытый индивидуализм, проблема сосуществования и доверия человека и ИИ, проблема использования ИИ для решения философских проблем, критическая оценка законов Азимова применительно к реальным сверхинтеллектуальным системам, обратная приоритетность этих законов (3 закон как важнейший), мир без войн, границ и традиционной политики, всеобщая прозрачность/слежка, опасности глубинных исследований механизмов сознания, утилитрониум, гедониум, теория игр, утилитаризм, психопанк, технокоммунизм, всеобщая любовь

Можно скачать тут https://drive.google.com/drive/folders/1XNr98DseYZswwKRiFXzCLUs00s1OnMAQ

Кто хочет больше - на сайте доступны предыдущие оперы:

2032: https://complexnumbers.ru/2032

Русалочка: https://complexnumbers.ru/merm

Подробности: https://vk.com/music/playlist/-23865151_83082490_ed2c7aba898e1ea31c

#ComplexNumbers #КомплексныеЧисла #ТехноОпера #музыка
:mima_rule: Mima-samamima@makai.chaotic.ninja
2024-06-04
Prof. Sally KeelyInteGreat@mathstodon.xyz
2024-03-17

Happy St. Paddy's Day!

"On October 16, 1843, the great Irish mathematician William Rowan Hamilton was walking along the Royal Canal in Dublin. He had been pondering for a long time whether complex numbers could be extended to higher dimensions. During his perambulation, he realized the answer was “yes”, and carved his solution on the Brougham Bridge. The plaque shown [below] commemorates the discovery." #ComplexNumbers #quaternions

galileospendulum.org/2013/03/1

The plaque on Brougham Bridge in Dublin, Ireland commemorating the discovery of quaternions by William Rowan Hamilton.
2024-01-28

Up and Atom has a nice video on number systems, and I was particularly intrigued to learn of number systems with imaginary or complex bases: youtube.com/watch?v=JS40jPaogM

See en.wikipedia.org/wiki/Complex- for more of the mathematical nitty-gritty if you’re so inclined

#ComplexNumbers #mathematics #counting #tallying #Babylonians #Knuth

Zvavybir 🇪🇺🇺🇳zvavybir@qauoc.mooo.com
2023-11-14
Does anybody know good names for the real and complex numbers? The standard ones are absolutely terrible ("complex" sounds as if they were difficult and the "real"/"imaginary" pair always causes stupid philosophical arguments of whether they exist, which's hindering learning).

"lateral" seems to be pretty established for the "imaginary" numbers, but I couldn't find any good names for the other two.

The best I could come up with myself are for the complex numbers "rotational"/"circular" (due to their ability to describe rotations) and "gaussian" (similar to gaussian integers) and for the real numbers "scaling" (due to them having a pure scaling effect compared to the complex numbers that can also rotate numbers).

#math #mathematics #complexnumbers #terminology
2023-09-20

Table of Contents

In a previous article we defined folding functions, and used them to enable some canonicalization and the sccp constant propagation pass for the poly dialect. This time we’ll see how to add more general canonicalization patterns.

The code for this article is in this pull request, and as usual the commits are organized to be read in order.

Why is Canonicalization Needed?

MLIR provides folding as a mechanism to simplify an IR, which can result in simpler, more efficient ops (e.g., replacing multiplication by a power of 2 with a shift) or removing ops entirely (e.g., deleting $y = x+0$ and using $x$ for $y$ downstream). It also has a special hook for constant materialization.

General canonicalization differs from folding in MLIR in that it can transform many operations. In the MLIR docs they will call this “DAG-to-DAG” rewriting, where DAG stands for “directed acyclic graph” in the graph theory sense, but really just means a section of the IR.

Canonicalizers can be written in the standard way: declare the op has a canonicalizer in tablegen and then implement a generated C++ function declaration. The official docs for that are here. Or you can do it all declaratively in tablegen, the docs for that are here. We’ll do both in this article.

Aside: there is a third way, to use a new system called PDLL, but I haven’t figured out how to use that yet. It should be noted that PDLL is under active development, and in the meantime the tablegen-based approach in this article (called “DRR” for Declarative Rewrite Rules in the MLIR docs) is considered to be in maintenance mode, but not yet deprecated. I’ll try to cover PDLL in a future article.

Canonicalizers in C++

Reusing our poly dialect, we’ll start with the binary polynomial operations, adding let hasCanonicalizer = 1; to the op base class in this commit, which generates the following method headers on each of the binary op classes

// PolyOps.h.incstatic void getCanonicalizationPatterns(  ::mlir::RewritePatternSet &results, ::mlir::MLIRContext *context);

The body of this method asks to add custom rewrite patterns to the input results set, and we can define those patterns however we feel in the C++.

The first canonicalization pattern we’ll write in this commit is for the simple identity $x^y – y^2 = (x+y)(x-y)$, which is useful because it replaces a multiplication with an addition. The only caveat is that this canonicalization is only more efficient if the squares have no other downstream uses.

// Rewrites (x^2 - y^2) as (x+y)(x-y) if x^2 and y^2 have no other uses.struct DifferenceOfSquares : public OpRewritePattern {  DifferenceOfSquares(mlir::MLIRContext *context)      : OpRewritePattern(context, /*benefit=*/1) {}  LogicalResult matchAndRewrite(SubOp op,                                 PatternRewriter &rewriter) const override {    Value lhs = op.getOperand(0);    Value rhs = op.getOperand(1);    if (!lhs.hasOneUse() || !rhs.hasOneUse()) {       return failure();    }    auto rhsMul = rhs.getDefiningOp();    auto lhsMul = lhs.getDefiningOp();    if (!rhsMul || !lhsMul) {      return failure();    }    bool rhsMulOpsAgree = rhsMul.getLhs() == rhsMul.getRhs();    bool lhsMulOpsAgree = lhsMul.getLhs() == lhsMul.getRhs();    if (!rhsMulOpsAgree || !lhsMulOpsAgree) {      return failure();    }    auto x = lhsMul.getLhs();    auto y = rhsMul.getLhs();    AddOp newAdd = rewriter.create(op.getLoc(), x, y);    SubOp newSub = rewriter.create(op.getLoc(), x, y);    MulOp newMul = rewriter.create(op.getLoc(), newAdd, newSub);    rewriter.replaceOp(op, {newMul});    // We don't need to remove the original ops because MLIR already has    // canonicalization patterns that remove unused ops.    return success();  }};

The test in the same commit shows the impact:

// Input:func.func @test_difference_of_squares(  %0: !poly.poly<3>, %1: !poly.poly<3>) -> !poly.poly<3> {  %2 = poly.mul %0, %0 : !poly.poly<3>  %3 = poly.mul %1, %1 : !poly.poly<3>  %4 = poly.sub %2, %3 : !poly.poly<3>  %5 = poly.add %4, %4 : !poly.poly<3>  return %5 : !poly.poly<3>}// Output:// bazel run tools:tutorial-opt -- --canonicalize $FILEfunc.func @test_difference_of_squares(%arg0: !poly.poly<3>, %arg1: !poly.poly<3>) -> !poly.poly<3> {  %0 = poly.add %arg0, %arg1 : !poly.poly<3>  %1 = poly.sub %arg0, %arg1 : !poly.poly<3>  %2 = poly.mul %0, %1 : !poly.poly<3>  %3 = poly.add %2, %2 : !poly.poly<3>  return %3 : !poly.poly<3>}

Other than this pattern being used in the getCanonicalizationPatterns function, there is nothing new here compared to the previous article on rewrite patterns.

Canonicalizers in Tablegen

The above canonicalization is really a simple kind of optimization attached to the canonicalization pass. It seems that this is how many minor optimizations end up being implemented, making the --canonicalize pass a heavyweight and powerful pass. However, the name “canonicalize” also suggests that it should be used to put the IR into a canonical form so that later passes don’t have to check as many cases.

So let’s implement a canonicalization like that as well. We’ll add one that has poly interact with the complex dialect, and implement a canonicalization that ensures complex conjugation always comes after polynomial evaluation. This works because of the identity $f(\overline{z}) = \overline{f(z)}$ for all polynomials $f$ and all complex numbers $z$.

This commit adds support for complex inputs in the poly.eval op. Note that in MLIR, complex types are forced to be floating point because all the op verifiers that construct complex numbers require it. The complex type itself, however, suggests a complex is perfectly legal, so it seems nobody in the MLIR community needs Gaussian integers yet.

The rule itself is implemented in tablegen in this commit. The main tablegen code is:

include "PolyOps.td"include "mlir/Dialect/Complex/IR/ComplexOps.td"include "mlir/IR/PatternBase.td"def LiftConjThroughEval : Pat<  (Poly_EvalOp $f, (ConjOp $z)),  (ConjOp (Poly_EvalOp $f, $z))>;

The two new pieces here are the Pat class (source) that defines a rewrite pattern, and the parenthetical notation that defines sub-trees of the IR being matched and rewritten. The source documentation on the Pattern parent class is quite well written, so read that for extra detail, and the normal docs provide a higher level view with additional semantics.

But the short story here is that the inputs to Pat are two “IR tree” objects (MLIR calls them “DAG nodes”), and each node in the tree is specified by parentheses ( ) with the first thing in the parentheses being the name of an operation (the tablegen name, e.g., Poly_EvalOp which comes from PolyOps.td), and the remaining arguments being the op’s arguments or attributes. Naturally, the nodes can nest, and that corresponds to a match applied to the argument. I.e., (Poly_EvalOp $f, (ConjOp $z)) means “an eval op whose first argument is anything (bind that to $f) and whose second argument is the output of a ConjOp whose input is anything (bind that to $z).

When running tablegen with the -gen-rewriters option, this generates this code, which is not much more than a thorough version of the pattern we’d write manually. Then in this commit we show how to include it in the codebase. We still have to tell MLIR which pass to add the generated patterns to. You can add each pattern by name, or use the populateWithGenerated function to add them all.

As another example, this commit reimplements the difference of squares pattern in tablegen. This one uses three additional features: a pattern that generates multiple new ops (which uses Pattern instead of Pat), binding the ops to names, and constraints that control when the pattern may be run.

// PolyPatterns.tddef HasOneUse: Constraint, "has one use">;// Rewrites (x^2 - y^2) as (x+y)(x-y) if x^2 and y^2 have no other uses.def DifferenceOfSquares : Pattern<  (Poly_SubOp (Poly_MulOp:$lhs $x, $x), (Poly_MulOp:$rhs $y, $y)),  [    (Poly_AddOp:$sum $x, $y),    (Poly_SubOp:$diff $x, $y),    (Poly_MulOp:$res $sum, $diff),  ],  [(HasOneUse:$lhs), (HasOneUse:$rhs)]>;

The HasOneUse constraint merely injects the quoted C++ code into a generated if guard, with $_self as a magic string to substitute in the argument when it’s used.

But then notice the syntax of (Poly_MulOp:$lhs $x, $x), the colon binds $lhs to refer to the op as a whole (or, via method overloads, its result value), so that it can be passed to the constraint. Similarly, the generated ops are all given names so they can be fed as the arguments of other generated ops Finally, the second argument of Pattern is a list of generated ops to replace the matched input IR, rather than a single node for Pat.

The benefit of doing this is significantly less boilerplate related to type casting, checking for nulls, and emitting error messages. But because you still occasionally need to inject random C++ code, and inspect the generated C++ to debug, it helps to be fluent in both styles. I don’t know how to check a constraint like “has a single use” in pure DRR tablegen.

Share this:

#c_ #canonicalization #compilers #complexNumbers #mathematics #mlir #polynomials #primer #programming

https://jeremykun.com/2023/09/20/mlir-canonicalizers-and-declarative-rewrite-patterns/

2023-09-18

In cryptography, we need a distinction between a cleartext and a plaintext. A cleartext is a message in its natural form. A plaintext is a cleartext that is represented in a specific way to prepare it for encryption in a specific scheme. The process of taking a cleartext and turning it into a plaintext is called encoding, and the reverse is called decoding.

In homomorphic encryption, the distinction matters. Cleartexts are generally all integers, though the bit width of allowed integers can be restricted (e.g., 4-bit integers). On the other hand, each homomorphic encryption (HE) scheme has its own special process for encoding and decoding, and since HEIR hopes to support all HE schemes, I set about cataloguing the different encoding schemes. This article is my notes on what they are.

If you’re not familiar with the terms Learning With Errors LWE and and its ring variant RLWE, then you may want to read up on those Wikipedia pages first. These problems are fundamental to most FHE schemes.

Bit field encoding for LWE

A bit field encoding simply places the bits of a small integer cleartext within a larger integer plaintext. An example might be a 3-bit integer cleartext placed in the top-most bits of a 32-bit integer plaintext. This is necessary because operations on FHE ciphertexts accumulate noise, which pollutes the lower-order bits of the corresponding plaintext (BGV is a special case that inverts this, see below).

Many papers in the literature will describe “placing in the top-most bits” as “applying a scaling factor,” which essentially means pick a power of 2 $\Delta$ and encode an integer $x$ as $\Delta x$. However, by using a scaling factor it’s not immediately clear if all of the top-most bits of the plaintext are safe to use.

To wit, the CGGI (aka TFHE) scheme has a slightly more specific encoding because it requires the topmost bit to be zero in order to use its fancy programmable bootstrapping feature. Don’t worry if you don’t know what it means, but just know that in this scheme the top-most bit is set aside.

This encoding is hence most generally described by specifying a starting bit and a bit width for the location of the cleartext in a plaintext integer. The code would look like

plaintext = message << (plaintext_bit_width - starting_bit - cleartext_bit_width)

There are additional steps that come into play when one wants to encode a decimal value in LWE, which can be done with fixed point representations.

As mentioned above, the main HE scheme that uses bit field LWE encodings is CGGI, but all the schemes use this encoding as part of their encoding because all schemes need to ensure there is space for noise growth during FHE operations.

Coefficient encoding for RLWE

One of the main benefits of RLWE-based FHE schemes is that you can pack lots of cleartexts into one plaintext. For this and all the other RLWE-based sections, the cleartext space is something like $(\mathbb{Z}/3\mathbb{Z})^{1024}$, vectors of small integers of some dimension. Many folks in the FHE world call $p$ the modulus of the cleartexts. And the plaintext space is something like $(\mathbb{Z}/2^{32}\mathbb{Z})[x] / (x^{1024} + 1)$, i.e., polynomials with large integer coefficients and a polynomial degree matching the cleartext space dimension. Many people call $q$ the coefficient modulus of the plaintext space.

In the coefficient encoding for RLWE, the bit-field encoding is applied to each input, and they are interpreted as coefficients of the polynomial.

This encoding scheme is also used in CGGI, in order to encrypt a lookup table as a polynomial for use in programmable bootstrapping. But it can also be used (though it is rarely used) in the BGV and BFV schemes, and rarely because both of those schemes use the polynomial multiplication to have semantic meaning. When you encode RLWE with the coefficient encoding, polynomial multiplication corresponds to a convolution of the underlying cleartexts, when most of the time those schemes prefer that multiplication corresponds to some kind of point-wise multiplication. The next encoding will handle that exactly.

Evaluation encoding for RLWE

The evaluation encoding borrows ideas from the Discrete Fourier Transform literature. See this post for a little bit more about why the DFT and polynomial multiplication are related.

The evaluation encoding encodes a vector $(v_1, \dots, v_N)$ by interpreting it as the output value of a polynomial $p(x)$ at some implicitly determined, but fixed points. These points are usually the roots of unity of $x^N + 1$ in the ring $\mathbb{Z}/q\mathbb{Z}$ (recall, the coefficients of the polynomial ring), and one computes this by picking $q$ in such a way that guarantees the multiplicative group $(\mathbb{Z}/q\mathbb{Z})^\times$ has a generator, which plays the analogous role of a $2N$-th root of unity that you would normally see in the complex numbers.

Once you have the root of unity, you can convert from the evaluation form to a coefficient form (which many schemes need for the encryption step) via an inverse number-theoretic transform (INTT). And then, of course, one must scale the coefficients using the bit field encoding to give room for noise. The coefficient form here is considered the “encoded” version of the cleartext vector.

Aside: one can perform the bit field encoding step before or after the INTT, since the bitfield encoding is equivalent to multiplying by a constant, and scaling a polynomial by a constant is equivalent to scaling its point evaluations by the same constant. Polynomial evaluation is a linear function of the coefficients.

The evaluation encoding is the most commonly used encoding used for both the BGV and BFV schemes. And then after encryption is done, one usually NTT’s back to the evaluation representation so that polynomial multiplication can be more quickly implemented as entry-wise multiplication.

Rounded canonical embeddings for RLWE

This embedding is for a family of FHE schemes related to the CKKS scheme, which focuses on approximate computation.

Here the cleartext space and plaintext spaces change slightly. The cleartext space is $\mathbb{C}^{N/2}$, and the plaintext space is again $(\mathbb{Z}/q\mathbb{Z})[x] / (x^N + 1)$ for some machine-word-sized power of two $q$. As you’ll note, the cleartext space is continuous but the plaintext space is discrete, so this necessitates some sort of approximation.

Aside: In the literature you will see the plaintext space described as just $(\mathbb{Z}[x] / (x^N + 1)$, and while this works in principle, in practice doing so requires multiprecision integer computations, and ends up being slower than the alternative, which is to use a residue number system before encoding, and treat the plaintext space as $(\mathbb{Z}/q\mathbb{Z})[x] / (x^N + 1)$. I’ll say more about RNS encoding in the next section.

The encoding is easier to understand by first describing the decoding step. Given a polynomial $f \in (\mathbb{Z}/q\mathbb{Z})[x] / (x^N + 1)$, there is a map called the canonical embedding $\varphi: (\mathbb{Z}/q\mathbb{Z})[x] / (x^N + 1) \to \mathbb{C}^N$ that evaluates $f$ at the odd powers of a primitive $2N$-th root of unity. I.e., letting $\omega = e^{2\pi i / 2N}$, we have

\[ \varphi(f) = (f(\omega), f(\omega^3), f(\omega^5), \dots, f(\omega^{2N-1})) \]

Aside: My algebraic number theory is limited (not much beyond a standard graduate course covering Galois theory), but this paper has some more background. My understanding is that we’re viewing the input polynomials as actually sitting inside the number field $\mathbb{Q}[x] / (x^N + 1)$ (or else $q$ is a prime and the original polynomial ring is a field), and the canonical embedding is a specialization of a more general theorem that says that for any subfield $K \subset \mathbb{C}$, the Galois group $K/\mathbb{Q}$ is exactly the set of injective homomorphisms $K \to \mathbb{C}$. I don’t recall exactly why these polynomial quotient rings count as subfields of $\mathbb{C}$, and I think it is not completely trivial (see, e.g., this stack exchange question).

As specialized to this setting, the canonical embedding is a scaled isometry for the 2-norm in both spaces. See this paper for a lot more detail on that. This is a critical aspect of the analysis for FHE, since operations in the ciphertext space add perturbations (noise) in the plaintext space, and it must be the case that those perturbations decode to similar perturbations so that one can use bounds on noise growth in the plaintext space to ensure the corresponding cleartexts stay within some desired precision.

Because polynomials commute with complex conjugation ($f(\overline{z}) = \overline{f(z)}$), and roots of unity satisfy $\overline{\omega^k} = \omega^{-k}$, this canonical embedding is duplicating information. We can throw out the second half of the roots of unity and retain the same structure (the scaling in the isometry changes as well). The result is that the canonical embedding is defined $\varphi: (\mathbb{Z}/q\mathbb{Z})[x] / (x^N + 1) \to \mathbb{C}^{N/2}$ via

\[ \varphi(f) = (f(\omega), f(\omega^3), \dots, f(\omega^{N-1})) \]

Since we’re again using the bit-field encoding to scale up the inputs for noise, the decoding is then defined by applying the canonical embedding, and then applying bit-field decoding (scaling down).

This decoding process embeds the discrete polynomial space inside $\mathbb{C}^{N/2}$ as a lattice, but input cleartexts need not lie on that lattice. And so we get to the encoding step, which involves rounding to a point on the lattice, then inverting the canonical embedding, then applying the bit-field encoding to scale up for noise.

Using commutativity, one can more specifically implement this by first inverting the canonical embedding (which again uses an FFT-like operation), the result of which is in $\mathbb{C}[x] / (x^N + 1)$, then apply the bit-field encoding to scale up, then round the coefficients to be in $\mathbb{Z}[x] / (x^N + 1)$. As mentioned above, if you want the coefficients to be machine-word-sized integers, you’ll have to design this all to ensure the outputs are sufficiently small, and then treat the output as $\mathbb{Z}/q\mathbb{Z}[x] / (x^N + 1)$. Or else use a RNS mechanism.

Residue Number System Pre-processing

In all of the above schemes, the cleartext spaces can be too small for practical use. In the CGGI scheme, for example, a typical cleartext space is only 3 or 4 bits. Some FHE schemes manage this by representing everything in terms of boolean circuits, and pack inputs to various boolean gates in those bits. That is what I’ve mainly focused on, but it has the downside of increasing the number of FHE operations, requiring deeper circuits and more noise management operations, which are slow. Other approaches try to use the numerical structure of the ciphertexts more deliberately, and Sunzi’s Theorem (colloquially known as the Chinese Remainder Theorem) comes to the rescue here.

There will be two “cleartext” spaces floating around here, one for the “original” message, which I’ll call the “original” cleartext space, and one for the Sunzi’s-theorem-decomposed message, which I’ll call the “RNS” cleartext space (RNS for residue number system).

The original cleartext space size $M$ must be a product of primes or co-prime integers $M = m_1 \cdot \dots \cdot m_r$, with each $m_i$ being small enough to be compatible with the desired FHE’s encoding. E.g., for a bit-field encoding, $M$ might be large, but each $m_i$ would have to be at most a 4-bit prime (which severely limits how much we can decompose).

Then, we represent a single original cleartext message $x \in \mathbb{Z}/M\mathbb{Z}$ via its residues mod each $m_i$. I.e., $x$ becomes $r$ different cleartexts $(x \mod m_1, x \mod m_2, \dots, x \mod m_r)$ in the RNS cleartext space. From there we can either encode all the cleartexts in a single plaintext—the various RLWE encodings support this so long as $r < N$ (or $N/2$ for the canonical embedding))—or else encode them as difference plaintexts. In the latter case, the executing program needs to ensure the plaintexts are jointly processed. E.g., any operation that happens to one must happen to all, to ensure that the residues stay in sync and can be reconstructed at the end.

And finally, after decoding we use the standard reconstruction algorithm from Sunzi’s theorem to rebuild the original cleartext from the decoded RNS cleartexts.

I’d like to write a bit more about RNS decompositions and Sunzi’s theorem in a future article, because it is critical to how many FHE schemes operate, and influences a lot of their designs. For example, I glazed over how inverting the canonical embedding works in detail, and it is related to Sunzi’s theorem in a deep way. So more on that in the future.

Share this:

#complexNumbers #cryptography #encoding #fhe #fullyHomomorphicEncryption #learningWithErrors #lwe #mathematics #NumberTheory #programming #residueNumberSystem #rlwe

https://jeremykun.com/2023/09/18/encoding-schemes-in-fhe/

Client Info

Server: https://mastodon.social
Version: 2025.04
Repository: https://github.com/cyevgeniy/lmst