revisions to polycount.cell1

This commit is contained in:
queue-miscreant 2025-02-19 23:02:09 -06:00
parent 879cbc909f
commit 9838e0d5cf

View File

@ -1,373 +1,759 @@
---
title: "Counting in 2D: Lines, Leaves, and Sand"
format:
html:
html-math-method: katex
date: "2021-02-23"
date-modified: "2025-2-14"
jupyter: python3
categories:
- algebra
- python
execute:
eval: false
---
```{python}
#| echo: false
Counting in 2D: Lines, Leaves, and Sand
=======================================
import sympy
from sympy.abc import x, y
from sympy.plotting import plot_implicit
import numpy as np
import matplotlib.pyplot as plt
I have written previously about positional systems and so-called "[polynomial counting]()". Basically, this generalizes familiar integer bases like two and ten into less familiar ones like the golden ratio. However, all systems I presented have something in common: they all use polynomials of a single variable. But does it even make sense to count in two variables?
from IPython.display import (
Video,
Latex,
display,
display_latex,
)
from carry2d.carry import Carry
from carry2d.anim import animate_carry_count
from carry2d.expansion import (
poly_from_array,
latex_polynumber,
add as carry_add,
)
```
Typical Restrictions
--------------------
Previously, I've written about positional systems and so-called "[polynomial counting](../1)".
Basically, this generalizes familiar integer bases like two and ten into less familiar irrational ones like the golden ratio.
However, all systems I presented have something in common: they all use polynomials of a single variable.
Does it make sense to count in two variables?
Before proceeding, I will summarize some of the restrictions on which polynomials can be used as carries in positional systems. Carry polynomials must be nonpositive when evaluated at 1 (otherwise the digital root of an expansion is unbounded) and either:
- All coefficients have the same sign but one, which has a much larger magnitude than the rest (and forces the digital root condition)
Preliminaries
-------------
Before proceeding, I will summarize some of the restrictions on which polynomials can be
used as carries in positional systems.
Carry polynomials must be nonpositive when evaluated at 1 (otherwise the digital root of
an expansion is unbounded) and either:
- All coefficients have the same sign but one, which has a much larger
magnitude than the rest (and forces the digital root condition)
- These are *explicit* irrational carries
- They are monic (have leading coefficient 1) and the remaining coefficients are all negative and monotonically increase toward 0 after the second coefficient (0's are allowed anywhere)
- They are monic (have leading coefficient 1) and the remaining coefficients are all negative
and monotonically increase toward 0 after the second coefficient (0's are allowed anywhere)
- These are *implicit* irrational carries
Implicit carries must be multiplied, in particular by cylotomic polynomials, to obtain additional rules for larger numbers.
Implicit carries must be multiplied, typically by cylotomic polynomials, to obtain additional
rules for larger numerals.
A Game of Chess
---------------
A Game of Che...ckers
---------------------
[Conway's checkers](https://en.wikipedia.org/wiki/Conway%27s_Soldiers) is a game played by attempting to advance game pieces to the furthest extent beyond a line. A piece can only be moved by jumping it horizontally or vertically over others, which removes them from play. Squinting hard enough, in one dimension, this is similar to how in phinary, a "1" in an expansion can jump over an adjacent "1" to its left.
[Conway's checkers](https://en.wikipedia.org/wiki/Conway%27s_Soldiers) is a game played by attempting to
advance game pieces to the furthest extent beyond a line.
A piece can only be moved by jumping it horizontally or vertically over others, which removes them from play.
Squinting hard enough, in one dimension, this is similar to how in phinary, a "1" in an
expansion can jump over an adjacent "1" to its left.
$$
\begin{array}{}
\bullet \\ \hline \bullet \\ \hline \vphantom{\bullet}
\bullet \\ \hline \bullet \\ \hline \vphantom{\bullet}
\end{array} \huge⤸ \normalsize
~\longrightarrow~
\begin{array}{}
\vphantom{\bullet} \\ \hline \vphantom{\bullet} \\ \hline \bullet
\vphantom{\bullet} \\ \hline \vphantom{\bullet} \\ \hline \bullet
\end{array}
~~ \iff ~~
\quad \iff \quad
0\textcolor{red}{11} = \textcolor{red}{1}00
$$
In fact, it is exactly the same. The best solution of the game relies on a geometric series in $\phi$, by placing a power of it in each square of a grid ([video by Numberphile](https://www.youtube.com/watch?v=Or0uWM9bT5w)).
In fact, it is exactly the same.
The best solution of the game relies on a geometric series in *φ*, as seen in
[this video by Numberphile](https://www.youtube.com/watch?v=Or0uWM9bT5w).
### Pairs of Checkers
My first intuition for 2D numbers was to replace the standard hop with the movement of a knight in chess. That way, not one, but two sequences of digits are necessary for the expansion of a number:
To actually take phinary and systems like it from 1D to 2D, we really just need a carry rule that respects the
second dimension.
To do this, we use a new variable *y* and invent a carry rule based on it.
The smallest way to extend our one-dimensional string of digits is do just introduce a second string.
Based on this, my intuition was to replace the standard hop with the movement of a knight in chess.
$$
\begin{gather*}
\begin{array}{c|c}
\bullet & \phantom{\bullet} \\
\hline \bullet \\ \hline \vphantom{\bullet}
\end{array}
~\longrightarrow~
\begin{array}{c|c}
\phantom{\bullet} & \phantom{\bullet} \\
\hline \\ \hline & \bullet
\end{array}
~~ \iff ~~
\left . \begin{array}{}
0\textcolor{red}{11}_x \\
000_y
\end{array} \right ] = \left . \begin{array}{}
000_x \\
\textcolor{red}{1}00_y
\end{array} \right ] \\ \\
y^2 = x + 1,~ x^2 = y + 1
\begin{array}{c|c}
\bullet & \phantom{\bullet} \\
\hline \bullet \\ \hline \vphantom{\bullet}
\end{array}
~\longrightarrow~
\begin{array}{c|c}
\phantom{\bullet} & \phantom{\bullet} \\
\hline \\ \hline & \bullet
\end{array}
~~ \iff ~~
\left. \begin{array}{}
0\textcolor{red}{11}_x \\
000_y
\end{array} \right]
= \left. \begin{array}{}
000_x \\
\textcolor{red}{1}00_y
\end{array} \right ] \\ \\
y^2 = x + 1,~ x^2 = y + 1
\end{gather*}
$$
This looks good on the surface, but the polynomials aren't sufficiently intertwined. Each variable is trivially a function of the other, and we can coax them together into a single polynomial:
This looks good on the surface, but it turns out that the polynomials aren't sufficiently intertwined.
Since each variable is a function of the other, they can easily be coaxed into a single polynomial:
$$
y = x^2 - 1 \Rightarrow (x^2 - 1)^2 = x + 1 \\
y = x^2 - 1 \longrightarrow y^2 = (x^2 - 1)^2 = x + 1 \\[6pt]
x^4 - 2x^2 + 1 = x + 1 \\
x^3 - 2x - 1 = 0 \\
(x + 1)(x^2 - x - 1) = 0
$$
The same argument applies symmetrically for *y*. Of course, one of the resultant polynomials is the minimal polynomial of $\phi$, reducing the prospective 2D system back to standard phinary.
The same argument applies symmetrically for *y*.
One of the resultant polynomials is the minimal polynomial of *φ*, so the prospective
2D system reduces back to standard phinary.
There are additional problems with using a pair of digit sequences. For example, the products of (powers of) both variables are ignored, such as xy. Also, both sequences have a ones' place, but agree that $x^0 = y^0 = 1$.
There are additional problems with this "pairs of strings" model.
For example, terms like *xy* never appear, and there is no way to represent them by just using two strings.
Also, both sequences have a ones' place, but should agree that $x^0 = y^0 = 1$.
Building the Plane
------------------
If it is the case that both powers of variables should share the ones' digit, then why not join the two at a right angle? Then, each variable has its own "axis" (unrelated to Cartesian coordinates), forming an infinite cross-shaped array of cells. We can arrange expansions in one variable in a line, so to make Euclid happy, those in two variables span a plane. Forms like *xy* align themselves with the cells that correspond to powers of *x* and *y*, occupying a cell off of the cross. Instead of a point-like separator, the radix point is now a pair of rays that share a vertex.
My inspiration for doing so was Norman Wildberger's variable-free approach to "[bipolynumbers](https://www.youtube.com/watch?v=_9FvrO1JTTI)", which is especially powerful, seen below
If it is the case that both powers of variables should share the one's digit, then why not
join the two strings there?
In other words, for each variable, we think of a line with a distinguished point for zero.
We then match the distinguished points together.
Thus, instead of a point separator between positive and negative powers,
the radix point is now a pair of lines that share a vertex.
From this pair of lines, according to Euclid, we can make a plane.
$$
\begin{array}{|c}
\hline
f & d & a \\
e & b \\
c
\hline
f & d & a \\
e & b \\
c
\end{array}
\begin{matrix} ~\equiv~
ax^2 + bxy + cy^2 + dx + ey + f, \\
\text{the general form of a conic}
ax^2 + bxy + cy^2 + dx + ey + f, \\
\text{the general form of a conic}
\end{matrix}
$$
By aligning the ones' places of two polynomials, it is very simple to compute their sum of, since it is simply pointwise addition. We should expect that expansion of the sum is the sum of the expansions (after applying carry), as is the case with 1D numbers.
Since the horizontal and vertical correspond with the *x* and *y* appearing in polynomials,
it may be easy to confuse this with the *x*- and *y*-axes of the typical Cartesian plane.
The key differences here are that these are discrete (two-dimensional) strings, rather than a continuous plane,
and along each point, there exists a cell in which an integer value lives.
Even in two variables, polynomials obey both scalar multiplication and multiplication by other polynomials. In a single variable, the latter is a somewhat involved 1D convolution; in two, it is naturally 2D convolution, an operation typically encountered in image processing and signal analysis. The intuition for this as it applies to expansions will be detailed momentarily.
The forward process of multiplication is difficult. Even so, division is harder. In polynomials of a single variable, we can do synthetic (or long) division in the direction the numbers do not extend (i.e., downward). On the face of a page, numbers occupy both dimensions, so they are not quite as "open" to manipulation by an algorithm. Fortunately, symbolic manipulation systems can perform both this and factorization in general reasonably quickly.
### An Arithmetical Aside
Expressed in this way, it is very simple to compute the sum of two polynomials.
Just like in the 1D system, we can align the one's places, add them together term-by-term, then apply carry rules.
With luck, this produces a canonical form.
It should also be possible to multiply two polynomials together.
In a single variable, multiplication is a somewhat involved 1D convolution.
In two, it is naturally [2D convolution](https://en.wikipedia.org/wiki/Kernel_(image_processing)),
an operation typically encountered in image processing and signal analysis.
I won't bother building up an intuition for this, since it won't be relevant to discussion.
Even if multiplication is difficult, division is always harder.
In polynomials of a single variable, we can do synthetic (or long) division.
Notationally, we work in the direction that the digits do not extend (i.e., downward).
With 2D numbers, we have digits in both dimensions.
Fortunately, computer algebra systems can divide and factor polynomials of two variables
reasonably quickly, so this point is moot if we allow ourselves to use one.
1, 2, 3, Takeoff
----------------
Where to start in a world of planar numbers? Well, we want one integer which has extent in both *x* and *y*. The simplest way to do this is by letting $x + y = n$: when the one's place has a magnitude of *n*, it flows into *x* and *y*. If *n* = 1, then the sum of coefficients is skewed toward the RHS, and the digital root is unbounded. We had a similar restriction in one dimension. Therefore, let *n* = 2. The expansions of the powers of 2 are:
Where should we start in a world of planar numbers?
Well, we want to assign an integer to a polynomial with extent in both *x* and *y*.
The simplest way to do this is by letting $n = x + y$: when the one's place has
a magnitude of *n*, it flows into *x* and *y*.
If $n = 1$, then the sum of coefficients is skewed toward the LHS, and the digital root is unbounded.
We had a similar restriction in one dimension.
Therefore, let $n = 2$.
The expansions of the powers of 2 are:
$$
\begin{matrix}
&&\text{Carry:}&\begin{array}{|c}\hline
-2 & 1 \\ 1
\end{array}\\
1 & 2 & ... & 4 & ... & 8 \\
\begin{array}{|c} \hline
1
\end{array} &
\begin{array}{|c} \hline
0 & 1 \\ 1
\end{array} & ... &
\begin{array}{|c} \hline
0 & 2 \\ 2
\end{array} & ... &
\begin{array}{|c} \hline
0 & 4 \\ 4
\end{array} \\ \\ &&&
\begin{array}{|c} \hline
0 & 0 & 1 \\ 0 & 0 & 1 \\ 1 & 1
\end{array} & ... &
\begin{array}{|c} \hline
0 & 0 & 2 \\ 0 & 0 & 2 \\ 2 & 2
\end{array} \\ \\ &&&&&
\begin{array}{|c} \hline
0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 1 \\
0 & 1 & 0 & 1 \\ 1 & 1 & 1
\end{array}
&&\text{Carry:}&\begin{array}{|c} \hline
-2 & 1 \\
1
\end{array}\\
1 & 2 & ... & 4 & ... & 8 \\
\begin{array}{|c} \hline
1
\end{array} &
\begin{array}{|c} \hline
0 & 1 \\
1
\end{array} & ... &
\begin{array}{|c} \hline
0 & 2 \\
2
\end{array} & ... &
\begin{array}{|c} \hline
0 & 4 \\
4
\end{array} \\ \\ &&&
\begin{array}{|c} \hline
0 & 0 & 1 \\
0 & 0 & 1 \\
1 & 1
\end{array} & ... &
\begin{array}{|c} \hline
0 & 0 & 2 \\
0 & 0 & 2 \\
2 & 2
\end{array} \\ \\ &&&&&
\begin{array}{|c} \hline
0 & 0 & 0 & 1 \\
0 & 0 & 1 & 1 \\
0 & 1 & 0 & 1 \\
1 & 1 & 1
\end{array}
\end{matrix}
$$
The final entry of each column is achieved by applying the carry enough times, reducing all digits to "0"s and "1"s. Notice that the typical binary expansion, reversed, is visible along first row and column. This is always the case, since there is no mechanism for the carry to spread either left or up; all expansions contain their binary expansion along those stripes. Additionally, the number of "1"s that appear in the expansion is equal to the number itself. These two properties make it a sort of hybrid between unary and binary. This representation of integers bridges their [Hamming weights](https://en.wikipedia.org/wiki/Hamming_weight) and magnitudes.
The final entry of each column is achieved by applying the carry enough times, reducing all digits
to "0"s and "1"s.
In doing so, the typical binary expansion is visible along first row and column, albeit reversed.
This is always the case, since when the carry is restricted to a single dimension (i.e., let $y = 0$),
we get the 1D binary carry.
More visually, there is no mechanism for the carry to disturb the column and row by spreading either left or up.
I'll call this system *dibinary*, since it is binary across two variables.
This notation is very heavy, but fortunately, we can visualize them easier. Each integer's expansion is more or less an image, so simply arranging each as frames in a video:
An additional property of this system is that the number of "1"s that appear in the expansion is equal
to the number itself.
These two properties make it a sort of hybrid between unary and binary, bridging their
binary expansion and its [Hamming weight](https://en.wikipedia.org/wiki/Hamming_weight).
![]()
This notation is very heavy, but fortunately lends itself well visualization.
Each integer's expansion is more or less an image, so we can arrange each as frames in a video:
Starting at the expansion of 12, the furthest extent is not in the first column or row. It instead grows faster along the diagonal, along which larger triangles appear when counting to higher and higher integers. Meanwhile, lower degrees (toward the upper left) appear to be slightly less predictable.
```{python}
#| code-fold: true
animate_carry_count(
carry = Carry([
[-2, 1],
[ 1, 0]
]),
frames=list(range(200))
).save("count_xy2.mp4")
Video("count_xy2.mp4")
```
Starting at the expansion of twelve, the furthest extent is not in the first column or row.
It instead grows faster along the diagonal, along which larger triangles appear when
counting to higher and higher integers.
Meanwhile, lower degrees (toward the upper left) appear to be slightly less predictable.
### Higher *n*
By incrementing *n*, the expansions of numbers shrink exponentially, and simply incrementing is not fast enough. Fortunately, we can scalar-multiply base expansions, then assiduously apply the carry. Ascending the powers of *n* in this manner for the carries where *n* = 3 and 4:
By incrementing *n*, the expansions of numbers shrink exponentially, and simply incrementing is not fast enough.
Fortunately, this is a simple enough system that we can scalar-multiply base expansions, then assiduously apply the carry.
Ascending the powers of *n* in this manner for the carries where *n* = 3 and 4:
:::{.column width="40%"}
![]()
:::
```{python}
#| code-fold: true
#| layout-ncol: 2
:::{.column width="40%"}
![]()
:::
animate_carry_count(
carry = Carry([
[-3, 1],
[ 1, 0]
]),
operation="multiply",
op_val=3,
frames=list(range(200))
).save("count_xy3.mp4")
Yellow corresponds to the highest allowed numeral, *n* - 1. Along with the boundary being much more rounded, neither appears to have an emergent pattern. Certainly patterns which appear in binary do not necessarily appear in ternary. But in 1D systems, we can group *m* digits in base *b* to convert the expansion to base *b^m*. If such a procedure still exists in 2D, it destroys the pattern that *n* = 2 has, as the quaternary system is still very chaotic.
animate_carry_count(
carry = Carry([
[-4, 1],
[ 1, 0]
]),
operation="multiply",
op_val=4,
frames=list(range(200)),
).save("count_xy4.mp4")
display(
Video("count_xy3.mp4"),
Video("count_xy4.mp4")
)
```
Yellow corresponds to the highest allowed numeral, $n - 1$.
It seems like the lower-right grows much rounder as *n* increases.
However, neither appears to have an emergent pattern as dibinary did.
You might be familiar with the way hexadecimal is a more compact version of binary.
For example,
$$\textcolor{red}{1}\textcolor{blue}{2}_{16} = \textcolor{red}{0001}\textcolor{blue}{0010}_{2}$$
because both one hexadecimal digit and four binary digits can range over zero to fifteen.
Consequently, by grouping group *m* digits of an expansion in base *b*, one can easily convert to base $b^m$.
If such a procedure still exists in 2D, applying it somehow destroys the pattern in the $n = 2$ case,
as the quaternary system is very chaotic.
Carry Curves
------------
Since the carry is a polynomial in both *x* and *y*, it lends itself to interpretation as an algebraic curve. Taking *x* or *y* to higher powers will dilate expansions along that axis.
Since the carry is a polynomial in both *x* and *y*, it lends itself to interpretation as an algebraic curve.
Taking *x* or *y* to higher powers will dilate expansions along that axis.
Rather than the shape of the curve, the points it passes through are important. In particular, we can interpret the intersections with the *x* and *y* axes as the "base" with respect to a single variable. Likewise, the intersection with the line *x* = *y* is the base with respect to both variables.
The carry is not the only curve which exists in a system, as each integer is also a polynomial unto itself.
Adding a constant term will not change anything unless it causes carries to occur.
Starting at the carry digit, some of the curves made by the carry $x + y = 2$ are shown below.
The carry is not the only curve which exists in a system, as each integer is also a polynomial unto itself. Adding a constant term will not change anything unless it causes carries to occur. Starting at the carry digit, some of the curves made by the carry $x + y = 2$ are shown below.
```{python}
#| code-fold: true
#| layout: [[3, 3], [2, 2, 2]]
![]()
expansion = np.zeros((50, 50), dtype=np.int64)
xy2 = Carry([
[-2, 1],
[ 1, 0],
])
In each of these figures, the line for the carry is present, since we can always factor it out of an expansion. Other shapes like hyperbolae show up and disappear, only to be replaced with higher-order curves. These higher order curves are rather difficult to graph, so they show up as thicker lines.
for i in range(2):
carry_add(expansion, xy2, 2)
plot_implicit(
poly_from_array(expansion) - (2 * i + 2),
title=f"{2*i + 2}"
)
plt.show()
It appears as though the reflection of the carry across the origin (the curve $x + y = -2$) "wants" to appear. In fact, looking at the curves for 24 and 64, the entire diamond shape centered at the origin appears to shrink. Perhaps it converges in the limit, but it is difficult to say.
carry_add(expansion, xy2, 8)
for i in range(3):
plot_implicit(
poly_from_array(expansion) - (26*i + 12),
title=f"{26*i + 12}",
)
plt.show()
carry_add(expansion, xy2, 26)
```
In each of these figures, shapes like hyperbolae show up and disappear, only to be replaced with
higher-order curves.
These higher order curves can be difficult to graph, so they show up as thicker lines.
At sixty-four, it appears as though the reflection of the carry across the origin
(the curve $x + y = -2$) "wants" to appear.
The continued presence of the original carry curve in larger integers suggests that
the points it passes through are important.
This curve happens to intersect with the *x* and *y* axes, which we can interpret as the "base"
with respect to a single variable.
For dibinary, these are the aforementioned "typical binary expansions".
We can also evaluate the polynomial at $(x, y) = (1, 1)$, a point on the carry curve,
to compute its digital root, which is also equal to the integer it represents.
This underlies a key difference from the 1D case.
In higher dimensions, there is no proper "base" we can plug in to "test" that an expansion
is valid like we can with the golden ratio in phinary.
1D systems are special in this regard: the carry can be equated with a base (or bases), which
is a discrete zero-dimensional point on a number line.
Casting the Line
----------------
Since the carry is always an expression in *x* and *y* equal to 0, if the curve a carry describes contains the point (1, 1), then the sum of its coefficients of the polynomial is 0. Expansions in such a carry have digital root the same as their magnitude.
We may want to extend the "unary" property to other carries.
To do this, we construct a curve which passes through $(1, 1)$.
Lines of the form $(n - 1)x + y = n$ trivially satisfy this relationship. While the digits along the y column will be expansions of base *n*, those along *x* will not correspond to any base, and expand faster. For example, for *n* = 3
Lines of the form $(n - 1)x + y = n$ trivially satisfy this relationship.
Further, while the digits along the *y* column will be expansions of base *n*, those along *x* will
be in base $n / (n - 1)$.
For example, for $n = 3$
$$
\begin{matrix}
&&&\text{Carry:}&
\begin{array}{|c}
\hline -3 & 2 \\ 1
\end{array}\\
1 & ... & 3 & ... & 6 & ... & 9 \\
\begin{array}{|c}
\hline 1
\end{array} & ... &
\begin{array}{|c}
\hline 0 & 2 \\ 1
\end{array} & ... &
\begin{array}{|c}
\hline 0 & 4 \\ 2
\end{array} & ... &
\begin{array}{|c}
\hline 0 & 6 \\ 3
\end{array} \\ \\ &&&&
\begin{array}{|c}
\hline 0 & 1 & 2 \\ 2 & 1
\end{array} & ... &
\begin{array}{|c}
\hline 0 & 3 & 2 \\ 3 & 1
\end{array} \\ \\ &&&&&&
\begin{array}{|c}
\hline 0 & 0 & 1 & 2 \\ 0 & 1 & 0 & 2 \\ 1 & 1 & 1
\end{array}
&&&\text{Carry:}&
\begin{array}{|c} \hline
-3 & 2 \\
1
\end{array}\\
1 & ... & 3 & ... & 6 & ... & 9 \\
\begin{array}{|c} \hline
1
\end{array} & ... &
\begin{array}{|c} \hline
0 & 2 \\
1
\end{array} & ... &
\begin{array}{|c} \hline
0 & 4 \\
2
\end{array} & ... &
\begin{array}{|c} \hline
0 & 6 \\
3
\end{array} \\ \\ &&&&
\begin{array}{|c} \hline
0 & 1 & 2 \\
2 & 1
\end{array} & ... &
\begin{array}{|c} \hline
0 & 3 & 2 \\
3 & 1
\end{array} \\ \\ &&&&&&
\begin{array}{|c} \hline
0 & 0 & 1 & 2 \\
0 & 1 & 0 & 2 \\
1 & 1 & 1
\end{array}
\end{matrix}
$$
Naturally, this works symmetrically considering *y* to have the coefficient instead of *x*, transposing both the carry and the expansion. Cutting to the chase, visually representing this expansion (and the one corresponding to *n* = 4):
The expansion in the *y* column looks trivially correct, since $y^2 = 9$ when $x = 0$.
We can check the base of the expansion of nine ("2100") by factoring a polynomial:
![]()
```{python}
#| code-fold: true
The pattern produced is mostly the same as the case when *n* = 2. The triangular pattern no longer grows all at once, but with the bottom leading the top. Meanwhile, the disordered part in the upper left of the expansion appears to grow larger as *n* increases, not to mention that it contains many artifacts from smaller colors.
nine_x2y3 = 2*x**3 + x**2 - 9
The slope of the line appears also as the slope of the pattern. As the coefficient of *x* approaches infinity, the pattern gets thinner and takes longer to show up. This is because the unary system (*x*) grows *arithmetically*, while other positional systems grow *geometrically*, and the former approaches infinity much slower than the latter.
display_latex(
sympy.Eq(nine_x2y3, nine_x2y3.factor())
)
```
The linear polynomial has 3/2 as a root, while the quadratic one has only complex roots.
Naturally, these expansions work symmetrically by considering *y* to have the $n - 1$ coefficient instead of *x*.
This transposes the carry (and hence the expansion).
### Slanted Counting
For good measure, let's see what it looks like when we count in the $n = 3$ and $n = 4$ cases.
```{python}
#| code-fold: true
#| layout-ncol: 2
animate_carry_count(
carry = Carry([
[-3, 2],
[ 1, 0]
]),
operation="add",
op_val=3,
frames=list(range(200))
).save("count_x2y3.mp4")
animate_carry_count(
carry = Carry([
[-4, 3],
[ 1, 0]
]),
operation="add",
op_val=4,
frames=list(range(200)),
).save("count_x3y4.mp4")
display(
Video("count_x2y3.mp4"),
Video("count_x3y4.mp4")
)
```
The pattern produced is similar to the one from dibinary.
However, the triangular pattern no longer grows all at once, and instead, the bottom leads the top.
Meanwhile, the disordered part in the upper left of the expansion appears to grow larger as *n* increases,
not to mention that it contains many artifacts from smaller colors.
The slope of the pattern is $1 / (n - 1)$, since this is the ratio of growth between *x* and *y*.
As *n* grows, the diagonal pattern gets thinner and takes longer to show up.
This is because the size of the alphabet causing expansions to shrink *geometrically*, while the digital
root is always increasing *arithmetically*.
The binary alphabet is the smallest possible, so the minimal shrinkage is in dibinary.
### Incremented Curves
Below are the incremented curves for *n* = 3. Note that the only quadratic curve shown above is the expansion for 6, as this system grows much more quickly. I find the transition between 12 and 15 to be interesting, since the spike seems to grow to encompass the point at infinity.
Below are the incremented curves for $n = 3$.
These curves show an interesting phenomenon: an extra line is present in in the expansion of six.
This line turns into a spike at twelve, then at fifteen, it seems to grow to encompass the point at infinity.
![]()
```{python}
#| code-fold: true
#| layout-ncol: 2
x2y3 = Carry([
[-3, 2],
[ 1, 0]
])
expansion = np.zeros((50, 50), dtype=np.int64)
last_i = 0
for i in [3, 6, 12, 15]:
carry_add(expansion, x2y3, i - last_i)
plot_implicit(
poly_from_array(expansion) - i,
(x, -10, 10),
(y, -10, 10),
title=f"{i}",
)
last_i = i
```
Leaves in the Mirror
--------------------
Algebraic curves are not my forte. However, I believe it would be a good idea to briefly examine some classical examples.
Algebraic curves are not my forte.
However, I believe it would be a good idea to briefly examine some classical examples.
All Fermat curves $x^n + y^n = 1$ are not suitable as carries, since their digital root is unbounded. Scaling up the curve, as by $x^n + y^n = 2$ will just degenerate back to the previous examples with lines, but spaced out by zeros between entries.
All Fermat curves $x^n + y^n = 1$ are not suitable as carries, since their digital root is unbounded.
Scaling up the curve, as by $x^n + y^n = 2$ will just degenerate back to the previous examples with lines,
but spaced out by zeros between entries.
The [folium of Descartes](https://en.wikipedia.org/wiki/Folium_of_Descartes) is another classical curve that played a role in the development of calculus, described by the equation:
The [folium of Descartes](https://en.wikipedia.org/wiki/Folium_of_Descartes) is another
classical curve that played a role in the development of calculus, described by the equation:
$$
\begin{gather*}
x^3 + y^3 - 3axy = 0
\\ \\
\begin{array}{|c} \hline
0 & 0 & 0 & 1 \\
0 & -3a \\ 0 & \\ 1
\end{array}
x^3 + y^3 - 3axy = 0
\\ \\
\begin{array}{|c} \hline
0 & 0 & 0 & 1 \\
0 & -3a \\
0 & \\
1
\end{array}
\end{gather*}
$$
The position of the 3*a* term may be confusing. As the negative coefficient, we must place it atop the digit we are carrying, meaning that expansions will propagate toward negative powers of *x* and *y* as well as positive. While the shape of the curve is different from an ordinary line, the coefficients are arranged too similarly; for $a = 2/3$ (so the curve passes through (1, 1)) incrementing appears as:
The position of the 3*a* term may be confusing.
As the negative coefficient, we must place it atop the digit we are carrying, meaning that
expansions will propagate toward negative powers of *x* and *y* as well as positive.
While the shape of the curve is different from an ordinary line, the coefficients are arranged
too similarly; for $a = 2/3$ (so the curve passes through the point (1, 1)) incrementing appears as:
:::{.column width="40%"}
$$
\begin{gather*}
2: & \begin{array}{c|cc}
& 0 & 0 & 1 \\ \hline
0 & 0 \\ 0 & \\ 1
\end{array} \\ \\
4: & \begin{array}{cc|cc}
& & 0 & 0 & 0 & 0 & 1 \\
& & 0 & 0 & 0 & 0 & 0 \\ \hline
0 & 0 & 0 & 0 & 0 & 1 & 0\\
0 & 0 & 0 & 0 & 0 & 0 & 0 \\
0 & 0 & 0 & 0 & 0 & 0 & 0 \\
0 & 0 & 1 & 0 & 0 & 0 & 0 \\
1 & 0 & 0 & 0 & 0 & 0 & 0
\end{array}
\end{gather*}
$$
:::
```{python}
#| echo: false
#| layout-ncol: 2
:::{.column width="40%"}
![]()
:::
folium = Carry([
[ 0, 0, 0, 1],
[ 0,-2, 0, 0],
[ 0, 0, 0, 0],
[ 1, 0, 0, 0],
])
dims = (50, 50)
center = (dims[0] // 2, dims[1] // 2)
two_folium = carry_add(np.zeros(dims, dtype=np.int64), folium, 2, center=center)
four_folium = carry_add(np.zeros(dims, dtype=np.int64), folium, 4, center=center)
Clearly, this also tends back toward the $x + y = 2$ example, but with the yellow digits spaced out more.
display_latex(
Latex(
r"\begin{gather*}"
+ "2: &"
+ latex_polynumber(two_folium, center=center, show_zero=True) + r"\\"
+ r"\\ \\ 4: &"
+ latex_polynumber(four_folium, center=center, show_zero=True) + r"\\"
+ r"\end{gather*}"
)
)
animate_carry_count(
carry=folium,
operation="add",
op_val=4,
frames=list(range(200)),
).save("count_folium.mp4")
display(Video("count_folium.mp4"))
```
Clearly, this also tends back toward dibinary, but with the yellow digits spaced out more.
### Discounting Curves
The interpretation of the carry as a curve is secondary, so I'd like to disqualify the archetypal incarnations of some quadratic objects.
The interpretation of the carry as a curve is secondary, so I'd like to disqualify
some archetypal quadratic objects.
- Circle ($x^2 + y^2 - 1 = 0$) and Hyperbola ($\pm x^2 \mp y^2 - 1 = 0$)
- Unit circle ($x^2 + y^2 - 1 = 0$) and hyperbola ($\pm x^2 \mp y^2 - 1 = 0$)
- The sum of coefficients is positive, which means that expansions are unbounded.
- Alternatively, coefficients are of an improper form, since one positive coefficient does not outweigh all remaining negative coefficients.
- Alternatively, coefficients are of an improper form, since one positive coefficient
does not outweigh all remaining negative coefficients.
- The curve as a function in *x* and *y* can be written in simpler terms, i.e., $F(x, y) = G(x^2, y^2)$
- Hyperbola ($xy \pm 1 = 0$)
- The curve is a function of only *xy*, i.e., $F(x, y) = P(xy)$, so the curve degenerates to one-dimensional unary.
- The curve is a function of only *xy*, i.e., $F(x, y) = P(xy)$,
so the curve degenerates to one-dimensional unary.
- Parabolae ($y = \pm x^2,~ x = \pm y^2$)
- One coefficient does not outweigh the other.
- The fact that either *y* is a polynomial in *x* or vice versa again degenerates the system to a singular dimension.
- The system degenerates to a single dimeension due to the fact that either *y* is a polynomial in *x* or vice versa
While it may be the case that other polynomials in *x* and *y* can produce these curves, this interpretation is still secondary. What matters is that one cell affects relatively close cells in a certain way.
Other polynomials in *x* and *y* may produce rotations or dilations of these curves,
but the key fact is that this interpretation is secondary.
What truly matters is that one cell affects relatively close cells in a certain way.
Laplace's Sandstorm
-------------------
As stated previously, polynomials naturally multiply by convolution. In this context, a form which frequently arises is an approximation of the 2D Laplacian. While commonly presented as a matrix, it has very little to do with the machinery of linear algebra which normally empowers matrices.
As stated previously, polynomials naturally multiply by convolution.
In this context, a form which frequently arises is an approximation of the 2D Laplacian.
While commonly presented as a matrix, it has very little to do with the machinery of
linear algebra which normally empowers matrices.
:::{.column width="40%"}
::: {layout-ncol="2"}
$$
\begin{gather*}
\begin{bmatrix}
0 & 1 & 0 \\
1 & -4 & 1 \\
0 & 1 & 0
\end{bmatrix} ~\implies~ \begin{array}{c|cc}
& 1 & \\ \hline
1 & -4 & 1 \\
& 1
\end{array} \\ \\
x + x^{-1} + y + y^{-1} - 4 = 0\\
x + xy^2 + y + yx^2 - 4xy = 0
\begin{bmatrix}
0 & 1 & 0 \\
1 & -4 & 1 \\
0 & 1 & 0
\end{bmatrix} ~\implies~
\begin{array}{c|cc}
& 1 & \\ \hline
1 & -4 & 1 \\
& 1
\end{array} \\ \\
x + x^{-1} + y + y^{-1} - 4 = 0 \\
x + xy^2 + y + yx^2 - 4xy = 0
\end{gather*}
$$
```{python}
#| echo: false
plot_implicit(x + y + x*y**2 + y*x**2 - 4*x*y, (x, -10, 10), (y, -10, 10))
```
:::
:::{.column width="40%"}
![]()
:::
From the above, it is clear that this is a cubic curve. Aside from the trivial solution $x = y = 0$ (which obviously also satisfies *x* = *y*), the curve made by the carry does not intersect either axis. There is an additional isolated point at (1, 1), implying some is in unary-ness this system. If we make it a carry, then the first few powers of 4 are
From the above, it is clear that this is a cubic curve.
Aside from the trivial solution $x = y = 0$ (which obviously also satisfies *x* = *y*),
the curve made by the carry does not intersect either axis.
There is an additional isolated point at (1, 1), implying some is in unary-ness this system.
If we make it a carry, then the first few powers of 4 are
$$
\begin{matrix}
1 & ... & 4 & ... & 16 \\
\begin{array}{|c}
\hline 1
\end{array} & ... &
\begin{array}{c|cc}
0 & 1 & 0 \\ \hline 1 & 0 & 1 \\ 0 & 1 &
\end{array} & ... &
\begin{array}{c|cc}
0 & 4 & 0 \\ \hline 4 & 0 & 4 \\ 0 & 4 &
\end{array} \\ \\ &&&&
\begin{array}{cc|cc} & & 1 & & \\ & 2 & 0 & 2 & \\ \hline 1 & 0 & 4 & 0 & 1 \\ & 2 & 0 & 2 \\ & & 1 &
\end{array} \\ \\ &&&&
\begin{array}{cc|cc} & & 1 & & \\ & 2 & 1 & 2 & \\ \hline 1 & 1 & 0 & 1 & 1 \\ & 2 & 1 & 2 \\ & & 1 &
\end{array}
1 & ... & 4 & ... & 16 \\
\begin{array}{|c} \hline
1
\end{array} & ... &
\begin{array}{c|cc}
0 & 1 & 0 \\ \hline
1 & 0 & 1 \\
0 & 1 &
\end{array} & ... &
\begin{array}{c|cc}
0 & 4 & 0 \\ \hline
4 & 0 & 4 \\
0 & 4 &
\end{array} \\ \\ &&&&
\begin{array}{cc|cc}
& & 1 \\
& 2 & 0 & 2 \\ \hline
1 & 0 & 4 & 0 & 1 \\
& 2 & 0 & 2 \\
& & 1
\end{array} \\ \\ &&&&
\begin{array}{cc|cc}
& & 1 \\
& 2 & 1 & 2 \\ \hline
1 & 1 & 0 & 1 & 1 \\
& 2 & 1 & 2 \\
& & 1
\end{array}
\end{matrix}
$$
As the negative (and largest) term, we carry at the "4" entry, as shown above. Terms progress along lower and higher powers of *x* and *y*. However, rather than just rightward propagation interfering with downward propagation (and vice versa), all directions can interfere with each other.
As the negative (and largest) term, we carry at the "4" entry, as shown above.
Terms progress along lower and higher powers of *x* and *y*.
However, rather than just rightward propagation interfering with downward propagation (and vice versa),
all directions can interfere with each other.
This carry can be interpreted more naturally as a [sandpile](https://en.wikipedia.org/wiki/Abelian_sandpile_model). As cellular automata, they are rather popular as a coding challenge ([video by The Coding Train](https://www.youtube.com/watch?v=diGjw5tghYU)), and when the grid is finite, certain subsets of all possible elements form finite groups under addition ([video by Numberphile](https://www.youtube.com/watch?v=1MtEUErz7Gg)). These videos discuss toppling the initial value, but do not show that they are quite analogous to polynomial expansions.
This system can be interpreted more naturally as a
[sandpile](https://en.wikipedia.org/wiki/Abelian_sandpile_model).
As a cellular automaton, it is rather popular as a coding challenge
([video by The Coding Train](https://www.youtube.com/watch?v=diGjw5tghYU)),
and when the grid is finite, certain subsets of all possible elements form finite groups under addition
([video by Numberphile](https://www.youtube.com/watch?v=1MtEUErz7Gg)).
These videos discuss toppling the initial value, but do not point out the analogy to polynomial expansions.
![]()
```{python}
#| code-fold: true
#| layout-ncol: 2
The shapes produced by this pattern are well-known, with larger numbers appearing as fractals. Generally, the expansions are enclosed within a circle, with shrinking triangular looking sections. This is eerily similar to the same shrinking triangles which appear in other carries which encompass (1, 1).
animate_carry_count(
carry = Carry([
[ 0, 1, 0],
[ 1,-4, 1],
[ 0, 1, 0],
]),
operation="add",
op_val=4,
frames=list(range(200)),
).save("count_laplace.mp4")
Video("count_laplace.mp4")
```
The shapes produced by this pattern are well-known, with larger numbers appearing as fractals.
Generally, the expansions are enclosed within a circle, with shrinking triangular looking sections.
This is eerily similar to the same shrinking triangles which appear in other unary-like carries.
Closing
-------
All of the 2D carries presented here are explicit carries. Unlike phinary, they have no additional rules hidden in mutliples of the carry. Implicit carries in two variables can be found by selecting a cyclotomic polynomial (in *x*, *y*, and $x + y$) and finding a second polynomial so that the product of the two resembles a explicit carry. Some of my investigations into this topic are available [here]().
All of the 2D carries presented here are explicit carries.
Unlike phinary, they have no additional rules hidden in mutliples of the carry.
It seems to be the case that implicit carries in two variables can be found by
selecting a cyclotomic polynomial (in *x*, *y*, and $x + y$)
and computing an explicit carry by multiplying it with another polynomial.
Some of my investigations into this topic are available [here](../cell2).
Just as learning to count on polynomials opens up a world of possibilities, learning to count on *bi*polynomials is a perplexing topic that I wish to explore further in the future.
I should also mention that my notational inspiration for arranging digits things in
a 2D array is Norman Wildberger's variable-free approach to
"[bipolynumbers](https://www.youtube.com/watch?v=_9FvrO1JTTI)", a term he uses
to refer to polynomials of two variables.
Just as learning to count on polynomials in a single variable opens up a world of possibilities,
learning to count on those in two variables is a perplexing topic that is worth exploring.