Compare commits
10 Commits
4bfcb7b708
...
637b08ee0c
| Author | SHA1 | Date | |
|---|---|---|---|
| 637b08ee0c | |||
| 69018bdbee | |||
| c74d50c062 | |||
| fd53aaa59e | |||
| a11946d050 | |||
| b95746882c | |||
| 07954babf0 | |||
| 0c37410a7c | |||
| dcba3a400c | |||
| 19cb4a18b8 |
371
assorted/platonic_volume/index.qmd
Normal file
371
assorted/platonic_volume/index.qmd
Normal file
@ -0,0 +1,371 @@
|
||||
---
|
||||
format:
|
||||
html:
|
||||
html-math-method: katex
|
||||
---
|
||||
|
||||
|
||||
On the Volume of the Platonic Solids
|
||||
====================================
|
||||
|
||||
|
||||
The Platonic solids have been known for millennia. They bear the name of Plato, who spoke of them in his dialogue *Timaeus*. He describes their "construction" (sans the dodecahedron) from the most basic "isosceles and scalene" triangles, or in modern parlance, the "45-45-90 and 30-60-90" triangles. However, the construction was not mathematical, and to my knowledge, each solid was first rigorously described from principles in Book XIII of Euclid's Elements.
|
||||
|
||||
In my teenage years, I recall viewing articles on the solids with their volumes so proudly displayed along with their surface area. While the latter quantity may be troublesome in the case of the dodecahedron (as the geometry of regular pentagons is not widely taught), it is easy for any student of trigonometry to calculate the surface area of the solids made of equilateral triangles, and easy for any child who knows of squares to do so for the cube. On the other hand, the volume is somewhat mystical.
|
||||
|
||||
There is only one free variable in a Platonic solid, its edge length, which means that their volumes are parametrized by this value alone. To be a true unitless value, a volume must be in a ratio with another volume. The cube has the simplest expression for its volume; it is simply the side length cubed. Therefore in the following post, I will derive the ratio of each solid's volume to the volume of the cube formed by any side.
|
||||
|
||||
This post will calculate the volume without using any trigonometric functions (sine, cosine tangent), opting instead for a more compass-and-straightedge approach. For this reason, *square* of the volume will be calculated initially to better cooperate with the Pythagorean theorem. Additionally, except for the octahedron, the edge length of every solid will be 2 to simplify the bisection of edges. This happens to coincide with Plato's description; two 30-60-90 triangles were used to make an equilateral triangle, meaning that its edge length was twice the "unit" length: the shortest side of the 30-60-90 triangle.
|
||||
|
||||
|
||||
A Recap of Geometry
|
||||
-------------------
|
||||
|
||||
For those with only a vague recollection (or perhaps none at all) of geometry, this section is intended as a refresher.
|
||||
|
||||
|
||||
### Planar Geometry
|
||||
|
||||
There are [many centers of a triangle](https://faculty.evansville.edu/ck6/encyclopedia/etc.html), but there are two of primary interest:
|
||||
|
||||
- The *circumcenter* is the center of the circle containing every vertex, meaning that it is equidistant from every vertex.
|
||||
- It is constructed by finding the intersection of the edges' perpendicular bisectors.
|
||||
- The distance from a vertex to the circumcenter is called the *circumradius* (*c*).
|
||||
- The *incenter* is equidistant from every edge, meaning that the length of the perpendicular segment connecting an edge and the incenter is the same for all edges.
|
||||
- It is constructed by finding the intersection of the lines which bisect each angle.
|
||||
- The perpendicular distance from an edge to the incenter is called the *inradius* (*a*).
|
||||
|
||||
::: {}
|
||||

|
||||
|
||||
Constructing the circumcenter and incenter. Angle bisectors in blue, perpendicular bisectors in red, in- and circumradii in green.
|
||||
:::
|
||||
|
||||
The inradius is special because it is also an altitude for a triangle formed by the inradius and an edge of the larger triangle. This means that the area of the larger triangle is the sum of these smaller triangles.
|
||||
|
||||

|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
A &= \left ({e_1 a \over 2} + {e_2 a \over 2} + {e_3 a \over 2} \right) =
|
||||
\left ({a \over 2} \right ) (e_1 + e_2 + e_3) \\
|
||||
&= {Pa \over 2}
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
This gives an expression for the area. For an equilateral triangle, these two centers coincide. This is because the perpendicular bisectors of the edges *are* the angle bisectors. In fact, the bisection of an angle involves constructing a rhombus, which is made up of two isosceles triangles (of which the equilateral triangle is a special case). In this case, the inradius is also called the *apothem*, and the difference between it and the circumradius is immediately apparent and called the *sagitta* (*s*).
|
||||
|
||||

|
||||
|
||||
This idea of incenters and circumcenters can be extended to other 2D figures such as the square and regular pentagon. For a square, the center is simply the intersection of the diagonals (i.e., the diagonals' common midpoint). The pentagon is trickier, and will be discussed later. Regardless, the expression for the area ${Pa \over 2}$ still works, since the polygon can be triangulated through the center in a similar way.
|
||||
|
||||

|
||||
|
||||
|
||||
### Cubes, Prisms, and Pyramids
|
||||
|
||||
Now we speak of 3D geometry. The volume of a prism is equal to the height times the area of the base, where the "height" is orthogonal to the plane of the base. Pyramids with the same height and base have one-third this area.
|
||||
|
||||
$$
|
||||
V_\text{prism} = Bh,~~ V_\text{pyramid} = {Bh \over 3}
|
||||
$$
|
||||
|
||||
This volume formula can be made more intuitive by considering the cube. The pyramid formed by one of the faces and an edge perpendicular to it will contain one square and two half-squares, or two squares in total. Therefore three pyramids are needed to recreate all six faces of the cube.
|
||||
|
||||
For a slightly more detailed explanation, consider a point inside the face on top of the cube. Its (perpendicular) distance from one edge is *x* and its distance to an edge adjacent to that is *y*. Connecting all other bases to this point produces five pyramids, whose bases all have the same area. Designate these pyramids as "bottom", "left", "right", "front", and "back", where left and right correspond to *x* and front and back correspond to *y*.
|
||||
|
||||

|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
V_\text{cube} &= Bh = V_\text{bottom} + V_\text{left} + V_\text{right}
|
||||
+ V_\text{front} + V_\text{back} \\
|
||||
&= rBh + rBx + rB(h-x) + rBy + rB(h-y) \\
|
||||
&= rBh + rBh + rBh \implies 1 = 3r \\
|
||||
r &= {1 \over 3}
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
This can be generalized to a pyramid based on any prism, where the top point lies in the plane of one of the bases. However, this is beyond the scope of this post.
|
||||
|
||||
|
||||
Simple Solids: the Octahedron and the Tetrahedron
|
||||
-------------------------------------------------
|
||||
|
||||
While "simple" is a bit of a misnomer, their volumes are easiest to appreciate, since they do not need regular pentagons.
|
||||
|
||||
|
||||
### Octahedron
|
||||
|
||||
The octahedron can be thought of as two square pyramids joined end-on-end, with uniform edge length throughout. Since the base is a square, its center is equidistant from the vertices of the base. An alternative, congruent square can be noticed by the symmetry of the octahedron, meaning the center is also equidistant from the top of the square pyramid, and that the segment connecting the two is an altitude of the pyramid.
|
||||
|
||||
::: {}
|
||||

|
||||
|
||||
Primary square in blue, secondary square in red. Diagonals of both squares shown.
|
||||
:::
|
||||
|
||||
The length of this altitude is simply half of the diagonal of the square. Therefore, the volume of an octahedron (calculated using edge length 1) is:
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
B^2 &= (1^2)^2 \\
|
||||
(2h)^2 &= 4h^2 = 1^2 + 1^2 = 2 \\
|
||||
V_\text{sq.pyr.}^2 &= {B^2 h^2 \over 3^2} =
|
||||
{1 \cdot {2 / 4} \over 3^2} \\
|
||||
(1^3 \cdot V_\text{oct})^2 &= (2V_\text{sq.pyr})^2 = 4V_\text{sq.pyr}^2 =
|
||||
4 \cdot {2 / 4 \over 3^2} = {2 \over 3^2} \\ \\
|
||||
V_\text{oct} &= {\sqrt{2} \over 3}
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
|
||||
### Tetrahedron
|
||||
|
||||
The tetrahedron is itself a pyramid. First, the (square of the) area of the base of an equilateral triangle must be known. As a reminder, this and all future solids will have edge length 2.
|
||||
|
||||
:::: {layout-ncol="2"}
|
||||
::: {.column width="49%"}
|
||||

|
||||
:::
|
||||
|
||||
::: {.column width="49%"}
|
||||
$$
|
||||
\begin{align*}
|
||||
d_\text{altitude}^2 &=
|
||||
\textcolor{orange}{2}^2 -\ \textcolor{green}{1}^2 = 3 \\
|
||||
B^2 &= \left ( {2 \cdot d_\text{altitude} \over 2} \right )^2 = 3
|
||||
\end{align*}
|
||||
$$
|
||||
:::
|
||||
::::
|
||||
|
||||
Next, bisect the tetrahedron through one edge and the altitudes of two faces. The altitudes form the legs of an isosceles triangle, so bisecting the angle where they meet (perpendicularly) bisects the remaining edge.
|
||||
|
||||
:::: {layout-ncol="2"}
|
||||
::: {.column width="49%"}
|
||||

|
||||
:::
|
||||
|
||||
::: {.column width="49%"}
|
||||
$$
|
||||
\begin{align*}
|
||||
\textcolor{blue}{d_\text{length}}^2 &=
|
||||
d_\text{altitude}^2 -\ \textcolor{green}{1}^2 = 2 \\
|
||||
(2A_\text{center})^2 &= (2d_\text{length})^2 = (\textcolor{red}{h} d_\text{altitude})^2 \\
|
||||
&= 4 \cdot 2 = 3h^2 \\
|
||||
h^2 &= 8 / 3
|
||||
\end{align*}
|
||||
$$
|
||||
:::
|
||||
::::
|
||||
|
||||
Since *h* is known, we can calculate the volume. Note that the volume is multiplied by the cube of the side length to produce the correct ratio to a unit cube.
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
({2^3 \cdot V_\text{tet}})^2 &= {B^2 h^2 \over 3^2} = {3 \cdot (8/3) \over 3^2} \\
|
||||
V_\text{tet}^2 &= {8 \over 2^6 \cdot 3^2} = {1 \over 2^3 \cdot 3^2} = {1 \over 2 \cdot 6^2} \\
|
||||
V_\text{tet} &= \sqrt{1 \over 6^2 \cdot 2} = {1 \over 6\sqrt 2}
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
|
||||
### Returning to 2D: Regular Pentagons
|
||||
|
||||
Both of the icosahedron and dodecahedron contain regular pentagons. Thus, it is necessary to examine them in detail.
|
||||
|
||||
The regular pentagon has five diagonals, which form a pentagram. Since all angles in a regular pentagon are equal, the trapezoid formed by three consecutive edges and one diagonal is isosceles. This means the diagonal is parallel to one of the edges, which applies to all diagonals by symmetry. Since the diagonal and side are parallel, this means that any two adjacent edges form a parallelogram with part of the diagonal. More specifically, it is a rhombus and those "parts of diagonals" have length equal to the side.
|
||||
|
||||
::: {}
|
||||

|
||||
|
||||
Left: Pentagram in regular pentagon; Middle: Isosceles trapezoid, with parallel lines marked in blue; Right: Rhombus in regular pentagon
|
||||
:::
|
||||
|
||||
Bisect the pentagon vertically and let the length of half of the diagonal of a pentagon be *d*, half the length of the other diagonal of a rhombus be *h*, and the remaining height of the pentagon be *g*.
|
||||
|
||||

|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
\textcolor{orange}{d}^2 + \textcolor{red}{h}^2 &= \textcolor{blue}{2}^2 \\
|
||||
2\textcolor{darkblue}{A_\text{blue}} &= 2\textcolor{green}{g} = h(\textcolor{magenta}{d -\ (2 -\ d)}) = h (2d -\ 2) \\
|
||||
\implies g &= {h(2d -\ 2) \over 2} = h(d -\ 1)
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
Notice that the center of a pentagram contains a regular pentagon. This means that the ratio of its height to the side is equal to the ratio of the larger pentagon's height to its side. This is enough information to deduce *d*:
|
||||
|
||||
\begin{align*}
|
||||
{\textcolor{red}{h} \over 2(\textcolor{brown}{2 -\ d})} &=
|
||||
{2\textcolor{red}{h} + \textcolor{green}{g} \over \textcolor{blue}{2}} =
|
||||
|
||||
{2h + h(d-1) \over 2} = {h(1 + d) \over 2 } \\
|
||||
2h &= 2h(1 + d)(2 -\ d) \\
|
||||
1 &= (1 + d)(2 -\ d) = 2 -\ d + 2d -\ d^2 \\
|
||||
0 &= d^2 -\ d -\ 1
|
||||
\end{align*}
|
||||
|
||||
This is the minimal polynomial of the golden ratio $\phi$; it is half the length of the diagonal, so the ratio of a diagonal to a side is also $\phi$. To make calculations easier, some conversions will be made to base $\phi$, or phinary. If you are not familiar already with phinary, I have already written at length about it [here](). Finally, the apothem *a* and height *l* can be calculated by similar triangles.
|
||||
|
||||
::: {}
|
||||

|
||||
:::
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
\textcolor{blue}{c \over a} &= \textcolor{brown}{2 \over \phi},~
|
||||
a^2 + 1^2 = c^2 \implies 1 = c^2 -\ a^2 = (c + a)(c -\ a) \\
|
||||
l &= c + a = {2a \over \phi} + a = a{2 + \phi \over \phi} = a{12_\phi \over 10_\phi} =
|
||||
a{2\bar{1}0_\phi \over 10_\phi} = a(2\bar{1}_\phi) \\ \\
|
||||
s &= c -\ a = {2a \over \phi} -\ a = a{2 -\ \phi \over \phi} = a{\bar{1}2_\phi \over 10_\phi} =
|
||||
a{2\bar{3}0_\phi \over 10_\phi} = a(2\bar{3}_\phi) \\ \\ \\
|
||||
1 &= ls = a^2(2\bar{1}_\phi)(2\bar{3}_\phi) = a^2(4\bar{8}3_\phi) =
|
||||
a^2(\bar{4}7_\phi) = a^2(3.\bar{4}_\phi) \\
|
||||
a^2 &= {1 \over 3.\bar{4}_\phi} \cdot {43_\phi \over 43_\phi} =
|
||||
{43_\phi \over [12]\bar{7}.[\bar{12}]_\phi} = {3 + 4\phi \over 5} \\ \\
|
||||
\implies l^2 &= a^2(2\bar{1}_\phi)^2 = {3 + 4\phi \over 5} \cdot (4\bar{4}1_\phi)
|
||||
= {3 + 4\phi \over 5} \cdot 5 = 3 + 4\phi
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
The last few steps in solving for $a^2$ are somewhat tricky. The conjugate of $\phi$ is $-{1 \over \phi}$. Since the digit in the $\phi^{-1}$ place value is negative, its conjugate has a positive value in the $\phi$ place value; i.e., $3.\bar{4}_\phi^* = 43_\phi$. Multiplying a quadratic root by its conjugate produces an integer value, which means that the scary quantity $[12]\bar{7}.[\bar{12}]_\phi$ resolves cleanly to 5.
|
||||
|
||||
The division can also be done explicitly in phinary:
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
{1 \over 3.\bar{4}_\phi} &= {1 \over 0.\bar{1}3_\phi} =
|
||||
{500_\phi \over 5 (\bar{1}3_\phi)} =
|
||||
{233_\phi + (0 = \textcolor{red}{4\bar{4}}\bar{4}0_\phi =
|
||||
\textcolor{red}{26\bar{2}}\bar{4}0_\phi) \over 5 (\bar{1}3_\phi)} \\
|
||||
&= {\bar{2}60\bar{1}3_\phi \over 5 (\bar{1}3_\phi)} =
|
||||
{2001_\phi \over 5} =
|
||||
{221_\phi \over 5} =
|
||||
{43_\phi \over 5} =
|
||||
{3 + 4\phi \over 5}
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
Finally, with the length and apothem of a regular pentagon in tow, the geometry of the final two solids may be explored.
|
||||
|
||||
|
||||
The Remaining Solids
|
||||
--------------------
|
||||
|
||||
The icosahedron and dodecahedron are easiest to dissect as many pyramids joined to a single center. This is reminiscent of the area formula which uses the triangulation of a polygon through the incenter.
|
||||
|
||||
The altitude (*h*) of any one pyramid is the radius of the *insphere* of the solid, which is tangent to the plane of every face. Similarly, the *circumsphere* (circumradius, *r*) contains all vertices, and the *midsphere* (midradius, $\rho$) is tangent to every edge. These will become important shortly.
|
||||
|
||||
|
||||
### The Icosahedron: an Antiprism in Profile
|
||||
|
||||
The icosahedron may also be thought of as two pentagonal pyramids connected to either base of a pentagonal *antiprism*. An antiprism is a figure similar to a prism, but with the one of the bases twisted relative to the other and with (equilateral) triangles joining them.
|
||||
|
||||
:::: {layout-ncol="2"}
|
||||
::: {.column width="49%"}
|
||||

|
||||
|
||||
Icosahedron with pentagonal antiprism in blue
|
||||
:::
|
||||
|
||||
::: {.column width="49%"}
|
||||

|
||||
|
||||
Construction showing $2a = c$
|
||||
:::
|
||||
::::
|
||||
|
||||
A segment connecting the centers of two antipodal faces is a diameter of the insphere. The altitude of one of these faces will be cut into circumradius and inradius. By similar triangles, the circumradius of an equilateral triangle is exactly twice the length of the inradius. This means the inradius is 1/3 of the altitude, or 1/9 of the square of the altitude. With edge length 2, the square of the altitude is 3, so the square of the inradius is ${3 \over 9} = {1 \over 3}$ .
|
||||
|
||||
The pentagonal antiprism may be bisected bisected along the plane containing the altitudes of two triangles opposite one another. This forms a parallelogram with side lengths of the altitude of an equilateral triangle and height of a pentagon.
|
||||
|
||||

|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
(\textcolor{green}{2h})^2 &= \textcolor{red}{l}^2 -\ {1 \over 3} = 3 + 4\phi -\ {1 \over 3} =
|
||||
{3(3 + 4\phi) -\ 1 \over 3} \\
|
||||
h^2 &= {8 + 12\phi \over 3 \cdot 4} = {2 + 3\phi \over 3} = {32_\phi \over 3} \\
|
||||
32_\phi &= 210_\phi = 1100_\phi = 10000_\phi = \phi^4 \\ \\
|
||||
(2^3 \cdot V_\text{ico})^2 &= \left ( 20 \cdot {Bh \over 3} \right )^2 =
|
||||
{20^2 B^2 h^2 \over 3^2} = {5^2 \cdot 4^2 \cdot 3 \cdot {\phi^4 \over 3} \over 3^2} =
|
||||
{5^2 \cdot 2^4 \cdot \phi^4 \over 3^2} \\
|
||||
V^2 &= {5^2 \cdot 2^4 \cdot \phi^4 \over 2^6 \cdot 3^2} =
|
||||
{5^2 \cdot \phi^4 \over 2^2 \cdot 3^2} \\
|
||||
V &= {5 \phi^2 \over 6}
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
|
||||
### The Dodecahedron
|
||||
|
||||
The dodecahedron is a bit trickier. It belongs to a class of polyhedra known as *truncated trapezohedra*. However, the bisection trick from the icosahedron still works. Begin by bisecting the solid through antipodal altitudes. This produces an oblong hexagon made up of four pentagon heights and two edges.
|
||||
|
||||

|
||||
|
||||
The segment connecting the antipodal midpoints bisects the hexagon into two (isosceles) trapezoids, and is a diameter of the midsphere. Additionally, it is parallel to the two edges. A second midradius is perpendicular to this one, bisecting the trapezoid.
|
||||
|
||||

|
||||
|
||||
The inradius is the altitude of a triangle formed by the length of a pentagon (its base), a midradius, and a circumradius. However, the altitude with respect to the midradius is another midradius. This means that the height can be found by equating areas and completing the square.
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
a^2 + \textcolor{green}{h}^2 &= \textcolor{blue}{\rho}^2,~~
|
||||
\textcolor{orange}{l}\textcolor{green}{h} =
|
||||
\textcolor{blue}{\rho \rho} \implies \textcolor{orange}{l}^2\textcolor{green}{h}^2 =
|
||||
(\textcolor{orange}{(2\bar{1}_\phi) a})^2 h^2 = 5a^2h^2 = \textcolor{blue}{\rho}^4 \\
|
||||
5a^2h^2 &= (a^2 + h^2)^2 = a^4 + 2a^2h^2 + h^4 \\ \\
|
||||
0 &= a^4 -\ 3a^2h^2 + h^4 = (h^2 -\ x)^2 + y = h^4 -\ 2xh^2 + x^2 + y \\
|
||||
-2x &= -3a^2 \implies x = {3a^2 \over 2},~~
|
||||
x^2 + y = {9a^4 \over 4} + y = a^4 \\
|
||||
y &= {4a^4 \over 4} -\ {9a^4 \over 4} = -{5a^4 \over 4} =
|
||||
-{(2\bar{1}_\phi)^2 a^4 \over 4} = -\left ( {(2\bar{1}_\phi) a^2 \over 2} \right )^2
|
||||
\\ \\
|
||||
(h^2 -\ {3a^2 \over 2})^2 &= -y = \left ( {(2\bar{1}_\phi) a^2 \over 2} \right )^2 \\
|
||||
h^2 -\ {3a^2 \over 2} &= {(2\bar{1}_\phi) a^2 \over 2} \\
|
||||
h^2 &= {(2\bar{1}_\phi) a^2 \over 2} + {3a^2 \over 2} = {(22_\phi) a^2 \over 2} =
|
||||
(11_\phi) a^2 \\
|
||||
&= {(11_\phi)(43_\phi) \over 5} = {473_\phi \over 5} =
|
||||
{[11]7_\phi \over 5} = {7 + 11\phi \over 5}
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
With the square of the height known, all that is left to do is find the volume.
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
B^2 &= \left( {Pa \over 2} \right)^2 = (5a)^2 = 25a^2 = 5(43_\phi) \\
|
||||
5h^2 &= [11]7_\phi = 740_\phi = 4300_\phi = (43_\phi)(100_\phi) \\
|
||||
(2^3 \cdot V_\text{dodec})^2 &= \left (12 \cdot {Bh \over 3} \right )^2 =
|
||||
4^2 B^2 h^2 = 2^4 \cdot 5(43_\phi) \cdot {(43_\phi)(100_\phi) \over 5} \\
|
||||
V^2 &= {2^4 \cdot (43_\phi)^2 \cdot (100_\phi) \over 2^6} =
|
||||
{(43_\phi)^2(10_\phi)^2 \over 2^2} \\
|
||||
V &= {(43_\phi)(10_\phi) \over 2} = {(430_\phi) \over 2} = {(74_\phi) \over 2} =
|
||||
{4 + 7\phi \over 2}
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
|
||||
Closing
|
||||
-------
|
||||
|
||||
Since each of these volumes has been calculated algebraically, there have been no approximate decimal forms. Ordered by size, the volumes of each of the solids are:
|
||||
|
||||
| Solid | Volume | Approximation | Length of Side with Unit Volume |
|
||||
|--------------|-----------------------|-----------------|---------------------------------|
|
||||
| Tetrahedron | ${1 \over 6\sqrt 2}$ | 0.1178511302... | 2.039648903... |
|
||||
| Octahedron | ${\sqrt{2} \over 3}$ | 0.4714045208... | 1.284898293... |
|
||||
| Cube | $1^3$ | 1 | 1 |
|
||||
| Icosahedron | ${5\phi^2 \over 6}$ | 2.181694991... | 0.7710253465... |
|
||||
| Dodecahedron | ${4 + 7\phi \over 2}$ | 7.663118961... |0.5072220724... |
|
||||
|
||||
The dodecahedron being so much larger than the icosahedron surprised me, to be honest. When one glances at a set of dice (as one does), it seems like the d20 is larger than the d12, albeit with smaller edges. However, at least in my set, the edges of the d20 are in fact about 1.5 times as long as those of the d12, implying their volumes are roughly equal.
|
||||
|
||||
***
|
||||
|
||||
I tried to use as much coordinate-free geometry as I could in producing these diagrams. GeoGebra lacks a tool for producing Platonic solids other than cubes and tetrahedra, so I ended up using approximations for the octahedron and icosahedron diagrams. On the other hand, the hexagon I described in the dodecahedron is of such importance to its construction that I ended up constructing it from scratch. I am rather proud of this because I did so without looking up someone else's. After having written this post, I feel much more competent with compass-and-straightedge constructions.
|
||||
|
||||
All diagrams made with GeoGebra.
|
||||
55
drafts/alg_homo/index.qmd
Normal file
55
drafts/alg_homo/index.qmd
Normal file
@ -0,0 +1,55 @@
|
||||
---
|
||||
format:
|
||||
html:
|
||||
html-math-method: katex
|
||||
---
|
||||
|
||||
|
||||
Viewing Homology Geometrically
|
||||
==============================
|
||||
|
||||
Delta sets for the Klein bottle, torus, and projective plane.
|
||||
|
||||
Define the delta maps in the natural way.
|
||||
|
||||
$$
|
||||
0 \overset{\partial_3}{\longrightarrow}
|
||||
C_2 \overset{\partial_2}{\longrightarrow}
|
||||
C_1 \overset{\partial_1}{\longrightarrow}
|
||||
C_0
|
||||
$$
|
||||
|
||||
Note that multiplying d1 by d2
|
||||
|
||||
$$
|
||||
0 \overset{\delta_3}{\longleftarrow}
|
||||
C_2 \overset{\delta_2}{\longleftarrow}
|
||||
C_1 \overset{\delta_1}{\longleftarrow}
|
||||
C_0
|
||||
$$
|
||||
|
||||
Fortunately, defining these maps is easy; we can just transpose the matrices by letting $\delta_\bullet = \partial_\bullet {}^T$.
|
||||
|
||||
For the projective plane, we end up modding out the base space. But what does this actually look like?
|
||||
|
||||
$$
|
||||
\begin{pmatrix} 1 & 0 \\ 0 & 1 \end{pmatrix} \left/
|
||||
\begin{pmatrix} 1 & 1 \\ -1 & 1 \end{pmatrix}
|
||||
\right.
|
||||
$$
|
||||
|
||||
On the left is the integer lattice and on the right is the sublattice generated by (1, -1) and (1, 1). These can be overlaid to show that there are obviously twice as many points in the former as there are the latter. When we perform the action of "modding out", all of the points in the sublattice go to 0. So in the quotient space, we only have two types of points: ones which were on the sublattice (0) and points which were not (1). But this describes the exact same thing as $Z_2$, which happens to be the homology group under discussion.
|
||||
|
||||
The Klein bottle is a little more complicated. In this case, we end up modding a rank 3 space out by a rank 2 space.
|
||||
|
||||
$$
|
||||
\begin{pmatrix} 1 & 0 & 0\\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{pmatrix} \left/
|
||||
\begin{pmatrix} 1 & -1 \\ 1 & 1 \\ -1 & 1 \end{pmatrix}
|
||||
\right.
|
||||
$$
|
||||
|
||||
The free component is the line normal to the plane described by the column space. But this is precisely the kernel of the map.
|
||||
|
||||
The torsion subgroup turns out to be identical to the one we already discussed for the Klein bottle, only we're in a plane that's slightly skewed with respect to the integer lattice.
|
||||
|
||||
![]()
|
||||
195
drafts/pseudo_taylor/index.qmd
Normal file
195
drafts/pseudo_taylor/index.qmd
Normal file
@ -0,0 +1,195 @@
|
||||
---
|
||||
format:
|
||||
html:
|
||||
html-math-method: katex
|
||||
---
|
||||
|
||||
|
||||
Notes on Pseudo-Taylor Series
|
||||
=============================
|
||||
|
||||
Whilst examining type algebra, I found that the geometric series, a cornerstone of calculus, could be derived by analyzing its definition as a rewrite rule. That is, $(1 - X)^{-1}$ is expressed similarly to an exponential object; when it is written as $-1 \rightarrow (1 - X)$, it can be interpreted thus:
|
||||
|
||||
$$
|
||||
(1 - X)^{-1} = -1 ~\rightarrow~ 1 - X \\ \\
|
||||
\begin{matrix}
|
||||
-1
|
||||
& \rightarrow&
|
||||
1 - X
|
||||
\\&&
|
||||
1 + (-1) \times X
|
||||
& \rightarrow &
|
||||
1 +(1 - X)X
|
||||
\\ & & & &
|
||||
1 + X + (- 1) \times X^2
|
||||
& \rightarrow &
|
||||
1 + X + (1-X)X^2
|
||||
\\ &&&&&& ...
|
||||
\end{matrix}
|
||||
$$
|
||||
|
||||
Which, in the limit, becomes the geometric series
|
||||
|
||||
$$
|
||||
1 + X + X^2 + X^3 + X^4 + ... = \sum_{n=0}^\infty X^n
|
||||
$$
|
||||
|
||||
I would like dedicate a bit more of my time to examining this apparent correspondence between Taylor series and rewrite rules, which as far as I can tell, appears to be new territory.
|
||||
|
||||
|
||||
Why is -1 Special?
|
||||
------------------
|
||||
|
||||
One of my first questions upon seeing this was why -1 is special. In type theory, integers can represent the number of values which occupy a type. Naturally (if you'll pardon the pun), this only makes sense for the positives and zero. But what is it that distinguishes -1 from any other negative number?
|
||||
|
||||
Naively, we can examine what happens when examining $-2 \rightarrow 1 - 2X$:
|
||||
|
||||
$$
|
||||
\textcolor{blue}{-2} \rightarrow 1 - 2X \\
|
||||
\begin{matrix}
|
||||
\textcolor{blue}{-2}
|
||||
& \stackrel{?}{\rightarrow} &
|
||||
1 - 2X
|
||||
\\&&
|
||||
1 + (\textcolor{blue}{-2}) \times X
|
||||
& \stackrel{?}{\rightarrow} &
|
||||
1 +(1 - 2X)X
|
||||
\\ & & & &
|
||||
1 + X + (\textcolor{blue}{-2}) \times X^2
|
||||
& \stackrel{?}{\rightarrow} &
|
||||
1 + X + (1-2X)X^2
|
||||
\\ &&&&&& ...
|
||||
\end{matrix}
|
||||
$$
|
||||
|
||||
But we know that this arrowed expression should evaluate to $(1-2X)^{-2}$
|
||||
|
||||
$$
|
||||
\textcolor{blue}{-2} \rightarrow 1 - 2X =
|
||||
(1 - 2X)^{-2} =
|
||||
(1 - 4X + 4X^2)^{-1} =
|
||||
\textcolor{red}{-1} \rightarrow 1 - 4X + 4X^2 \\ \ \\
|
||||
\begin{matrix}
|
||||
\textcolor{red}{-1}
|
||||
& \rightarrow &
|
||||
1 - 4X + 4X^2
|
||||
\\&&
|
||||
1 + (\textcolor{red}{-1}) \times 4X + 4X^2
|
||||
& \rightarrow &
|
||||
1 +(1 - 4X + 4X^2) \times 4X + 4X^2
|
||||
\\ & & & &
|
||||
1 + 4X - 12X^2 + 16X^3
|
||||
\\ & & & &
|
||||
1 + 4X + (\textcolor{red}{-1}) \times 12X^2 + 16X^3
|
||||
& \rightarrow & ...
|
||||
\end{matrix}
|
||||
$$
|
||||
|
||||
Continuing the rewrite rule gives coefficients of 12, 32, 80, 192... for $X^2$ onward. And indeed, the [Taylor series for the reciprocal](https://www.wolframalpha.com/input?i=taylor+series+%281+-+4X+%2B+4X%5E2%29%5E-1) of $1 - 4X + 4X^2$ is
|
||||
|
||||
This gives us a bit more information: -1 is special because it factors out of all other negatives. From there, we can evaluate the remaining positive exponent by the binomial theorem.
|
||||
|
||||
|
||||
Ordering
|
||||
--------
|
||||
|
||||
What happens when there are multiple negatives in the denominator? For example, the expression $(1 - X -X^2)^{-1}$ has the Fibonacci numbers as its Taylor series coefficients. If we try replacing all the -1s at once, we don't even come close past the first numbers:
|
||||
|
||||
$$
|
||||
{1 \over 1 - X -X^2} = \textcolor{red}{-1} \rightarrow 1 - X - X^2 \\ \ \\
|
||||
\begin{matrix}
|
||||
\textcolor{red}{-1}
|
||||
& \rightarrow &
|
||||
1 - X - X^2
|
||||
\\&&
|
||||
1 + (\textcolor{red}{-1}) \times X + (\textcolor{red}{-1}) \times X^2
|
||||
& \rightarrow &
|
||||
1 + (1 - X - X^2 )X + (1 - X - X^2 )X^2
|
||||
\\ & & & &
|
||||
1 + X - X^2 - X^3 + X^2 - X^3 - X^4
|
||||
\\ & & & &
|
||||
1 + X - 2X^3 - X^4
|
||||
\\ & & & &
|
||||
1 + X + (\textcolor{red}{-1}) \times 2X^3 + (\textcolor{red}{-1}) \times X^4
|
||||
\\ & & & & ...
|
||||
\end{matrix}
|
||||
$$
|
||||
|
||||
The [katex]X^2[/katex] terms cancel after the first iterated replacement. This is clearly not right; the Fibonacci number following two 1's is 2.
|
||||
|
||||
In fact, all is well if we replace less aggressively.
|
||||
|
||||
$$
|
||||
\begin{matrix}
|
||||
\textcolor{red}{-1}
|
||||
& \rightarrow &
|
||||
1 - X - X^2
|
||||
\\&&
|
||||
1 + (\textcolor{red}{-1}) \times X -X^2
|
||||
& \rightarrow &
|
||||
1 + (1 - X - X^2 ) \times X - X^2
|
||||
\\ & & & &
|
||||
1 + X - X^2 - X^3 -X^2
|
||||
\\ & & & &
|
||||
1 + X - 2X^3 - X^3
|
||||
\\ & &
|
||||
1 + X+ (1- X - X^2)\times2X^2 - X^3
|
||||
& \leftarrow &
|
||||
1 + X + (\textcolor{red}{-1}) \times 2X^2 - X^3
|
||||
\\ & &
|
||||
1 + X + 2X^2 - 2X^3 - 2X^4 - X^3
|
||||
\\ & &
|
||||
1 + X + 2X^2 - 3X^3 - 2X^4
|
||||
\\ & &
|
||||
1 + X + 2X^2 + (\textcolor{red}{-1})\times 3X^3 - 2X^4
|
||||
& \rightarrow & ...
|
||||
\end{matrix} \\ \ \\
|
||||
{1 \over 1 - X - X^2} = 1 + X + 2X^2 + 3X^3 + 5X^4 + 8X^5 + ...
|
||||
$$
|
||||
|
||||
This gives us another way of obtaining the generating function for the Fibonacci numbers.
|
||||
|
||||
$$
|
||||
{1 \over 1 - X -X^2} = \textcolor{red}{-1} \rightarrow 1 - X - X^2 \\ \ \\
|
||||
\begin{matrix}
|
||||
\textcolor{red}{-1}
|
||||
& \rightarrow &
|
||||
F_0 + F_1 X + F_2 X^2 + ... - F_{n-1} X^{n-1} - F_{n-2} X^n
|
||||
\\ & &
|
||||
... + (-1) \times F_{n-1} X^{n-1} - F_{n-2} X^n
|
||||
\\ & & \downarrow
|
||||
\\ & &
|
||||
... + (1 - X - X^2) \times F_{n-1} X^{n-1} - F_{n-2} X^n
|
||||
\\ & &
|
||||
... + F_{n-1} X^{n-1} - F_{n-1} X^n - F_{n-1} X^{n+1} - F_{n-2} X^n
|
||||
\\ & &
|
||||
... + F_{n-1} X^{n-1} - (F_{n-1} + F_{n-2}) X^n - F_{n-1} X^{n+1}
|
||||
\\ & &
|
||||
... + F_{n-1} X^{n-1} - F_n X^n - F_{n-1} X^{n+1}
|
||||
\end{matrix}
|
||||
$$
|
||||
|
||||
|
||||
Complex Roots
|
||||
-------------
|
||||
|
||||
Negatives?
|
||||
|
||||
Must have a pole?
|
||||
|
||||
|
||||
Continued Fractions
|
||||
-------------------
|
||||
|
||||
I am the most interested in generalizing rewrite rule-style types. The recursive equation for a forest is actually similar to the continued fraction for the Catalan numbers:
|
||||
|
||||
$$
|
||||
F(X) = {1 \over 1 - X \times F(X)} = {1 \over 1 - X \times {1 \over 1 - X \times F(X)}} =...= \cfrac{1}{1 - \cfrac{X}{1- \cfrac{X}{1- \cfrac{X}{_\ddots} }}}
|
||||
\\
|
||||
= 1 + X\times F(X) + X^2 \times F(X)^2 +X^3 \times F(X)^3 + ... \\
|
||||
= 1 + X + 2X^2 +5X^3 + 14X^4 + 42X^5+...
|
||||
$$
|
||||
|
||||
Successively replacing F(X) with itself will produce the desired series.
|
||||
|
||||
Access to -1 and exponentials implies access to fractions and all surds.
|
||||
479
number_number/1/index.qmd
Normal file
479
number_number/1/index.qmd
Normal file
@ -0,0 +1,479 @@
|
||||
---
|
||||
format:
|
||||
html:
|
||||
html-math-method: katex
|
||||
---
|
||||
|
||||
|
||||
Numbering Numbers: From 0 to ∞
|
||||
==============================
|
||||
|
||||
The infinite is replete with paradoxes. Some of the best come from comparing sizes of infinite collections. For example, every natural number can be mapped to a (nonnegative) even number and vice versa.
|
||||
|
||||
$$
|
||||
\N \rightarrow 2\N \\
|
||||
n \mapsto 2n \\ ~ \\
|
||||
0 \mapsto 0,~ 1 \mapsto2,~ 2 \mapsto 4,~ 3 \mapsto 6,~ 4 \mapsto 8, ...
|
||||
$$
|
||||
|
||||
(For the purposes of this post, $0 \in \N, ~ 0 \notin \N^+$)
|
||||
|
||||
All even numbers are "hit" by this map (by the definition of an even number), and no two natural numbers map to the same even number (again, more or less by definition, since $2m = 2n$ implies that $m = n$ over $\N$). Therefore, the map is [one-to-one](https://en.wikipedia.org/wiki/Injective_function) and [onto](https://en.wikipedia.org/wiki/Surjective_function), and the map is a [bijection](https://en.wikipedia.org/wiki/Bijection). A consequence is that the map has an inverse, namely by reversing all of the arrows in the above block (i.e., the action of halving an even number).
|
||||
|
||||
Bijections with the natural numbers are easier to understand as a way to place things into a linear sequence. In other words, they enumerate some "sort" of item; in this case, even numbers.
|
||||
|
||||
In the finite world, a bijection between two things implies that they have the same size. It makes sense to extend the same logic to the infinite world, but there's a catch. The nonnegative even numbers are clearly a strict subset of the natural numbers, but by this argument they have the same size.
|
||||
|
||||
$$
|
||||
\begin{matrix}
|
||||
2\N & \longleftrightarrow & \N & \hookleftarrow & 2\N \\
|
||||
0 & \mapsto & \textcolor{red}0 & \dashleftarrow & \textcolor{red}0 \\
|
||||
2 & \mapsto & 1 & & \\
|
||||
4 & \mapsto & \textcolor{red}2 & \dashleftarrow & \textcolor{red}2 \\
|
||||
6 & \mapsto & 3 & & \\
|
||||
8 & \mapsto & \textcolor{red}4 & \dashleftarrow & \textcolor{red}4 \\
|
||||
10 & \mapsto & 5 & & \\
|
||||
12 & \mapsto & \textcolor{red}6 & \dashleftarrow & \textcolor{red}6 \\
|
||||
14 & \mapsto & 7 & & \\
|
||||
16 & \mapsto & \textcolor{red}8 & \dashleftarrow & \textcolor{red}8 \\
|
||||
\vdots & & \vdots & & \vdots
|
||||
\end{matrix}
|
||||
$$
|
||||
|
||||
|
||||
Are we Positive?
|
||||
----------------
|
||||
|
||||
The confusion continues if we look at the integers and the naturals. Integers are the natural numbers and their negatives, so it would be intuitive to assume that there are twice as many of them as there are naturals (more or less one to account for zero). But that logic fails for the naturals and the even numbers, and indeed, it fails for the integers and the naturals as well.
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
\mathbb{N} &\rightarrow \mathbb{Z} \\
|
||||
n &\mapsto \left\{ \begin{matrix}
|
||||
n/2 & n \text{ even} \\
|
||||
-(n+1)/2 & n \text{ odd}
|
||||
\end{matrix} \right.
|
||||
\end{align*}
|
||||
\\ ~ \\
|
||||
0 \mapsto 0,\quad 2 \mapsto 1, \quad 4 \mapsto 2, \quad 6 \mapsto 3, \quad 8 \mapsto 4,~... \\
|
||||
1 \mapsto -1, \quad 3 \mapsto -2, \quad 5 \mapsto -3, \quad 7 \mapsto -4, \quad 9 \mapsto -5, ...
|
||||
$$
|
||||
|
||||
Or, in Haskell (if you cover your eyes and pretend that the `undefined` below will never happen):
|
||||
|
||||
```{.haskell}
|
||||
type Nat = Int
|
||||
|
||||
listIntegers :: Nat -> Int
|
||||
listIntegers n
|
||||
| n < 0 = undefined
|
||||
| even n = n `div` 2
|
||||
| otherwise = -(n + 1) `div` 2
|
||||
```
|
||||
|
||||
In other words, this map sends even numbers to the naturals (the inverse of the doubling map) and the odds to the negatives. The same arguments about the bijective nature of this map apply as before, and so the paradox persists, since naturals are also a strict subset of integers.
|
||||
|
||||
|
||||
### Rational Numbers
|
||||
|
||||
Rationals are a bit worse. To make things a little easier, let's focus on the positive rationals (i.e., fractions excluding 0). Unlike the integers, there is no obvious "next rational" after (or even before) 1. If there were, we could follow it with its reciprocal, like how an integer is followed by is negative in the map above.
|
||||
|
||||
On the other hand, the integers provide a sliver of hope that listing all rational numbers is possible. Integers can be defined as pairs of natural numbers, along with a way of considering two pairs equal.
|
||||
|
||||
$$
|
||||
-1 = (0,1) \sim_\Z (1,2) \sim_\Z (2,3) \sim_\Z (3,4) \sim_\Z ... \\
|
||||
~ \\
|
||||
(a,b) \sim_\mathbb{Z} (c,d) \iff a+d = b+c \quad a,b,c,d \in \mathbb{N} \\
|
||||
~ \\
|
||||
\mathbb{Z} := ( \mathbb{N} \times \mathbb{N} ) / \sim_\mathbb{Z}
|
||||
$$
|
||||
|
||||
```{.haskell}
|
||||
intEqual :: (Nat, Nat) -> (Nat, Nat) -> Bool
|
||||
intEqual (a, b) (c, d) = a + d == b + c
|
||||
```
|
||||
|
||||
This relation is the same as saying $a - b = c - d$ (i.e., that -1 = 0 - 1, etc.), but has the benefit of not needing to define subtraction first. This is all the better, since, as grade-schoolers are taught, subtracting a larger natural number from a smaller one is impossible.
|
||||
|
||||
The same equivalence definition exists for positive rationals. It is perhaps more familiar, because of the emphasis placed on simplifying fractions when learning them. We can [cross-multiply](https://en.wikipedia.org/wiki/Cross-multiplication) fractions to get a similar equality condition to the one for integers.
|
||||
|
||||
$$
|
||||
{1 \over 2} = (1,2) \sim_\mathbb{Q} \overset{2/4}{(2,4)} \sim_\mathbb{Q} \overset{3/6}{(3,6)} \sim_\mathbb{Q} \overset{4/8}{(4,8)} \sim_\mathbb{Q} ... \\
|
||||
~ \\
|
||||
(a,b) \sim_\mathbb{Q} (c,d) \iff ad = bc \quad a,b,c,d \in \mathbb{N}^+ \\
|
||||
~ \\
|
||||
\mathbb{Q^+} := ( \mathbb{N^+} \times \mathbb{N^+} ) / \sim_\mathbb{Q}
|
||||
$$
|
||||
|
||||
```{.haskell}
|
||||
ratEqual :: (Nat, Nat) -> (Nat, Nat) -> Bool
|
||||
ratEqual (a, b) (c, d) = a * d == b * c
|
||||
```
|
||||
|
||||
We specify that neither element of the pair can be zero, so this excludes divisions by zero (and the especially tricky case of 0/0, which would be equal to all fractions). Effectively, this just replaces where addition appears in the integer equivalence with multiplication.
|
||||
|
||||
|
||||
### Eliminating Repeats
|
||||
|
||||
Naively, to tackle both of these cases, we might consider enumerating pairs of natural numbers. We order them by sums and break ties by sorting on the first index.
|
||||
|
||||
| Index | Pair (*a*, *b*) | Sum (*a* + *b*) | Integer (*a* - *b*) | Rational (*a*+1 / *b*+1) |
|
||||
|-------|-----------------|-----------------|---------------------|--------------------------|
|
||||
| 0 | (0, 0) | 0 | 0 | 1/1 |
|
||||
| 1 | (0, 1) | 1 | -1 | 1/2 |
|
||||
| 2 | (1, 0) | 1 | 1 | 2/1 |
|
||||
| 3 | (0, 2) | 2 | -2 | 1/3 |
|
||||
| 4 | (1, 1) | 2 | 0 | 2/2 = 1/1 |
|
||||
| 5 | (2, 0) | 2 | 2 | 3/1 |
|
||||
| 6 | (0, 3) | 3 | -3 | 1/4 |
|
||||
| 7 | (1, 2) | 3 | -1 | 2/3 |
|
||||
| 8 | (2, 1) | 3 | 1 | 3/2 |
|
||||
| 9 | (3, 0) | 3 | 3 | 4/1 |
|
||||
| ... | ... | ... | ... | ... |
|
||||
|
||||
```{.haskell}
|
||||
-- All pairs of natural numbers that sum to n
|
||||
listPairs :: Nat -> [(Nat, Nat)]
|
||||
listPairs n = [ (k, n - k) | k <- [0..n] ]
|
||||
|
||||
-- Use a natural number to index the enumeration of all pairs
|
||||
allPairs :: [(Nat, Nat)]
|
||||
allPairs = concat $ map listPairs [0..]
|
||||
|
||||
allPairsMap :: Nat -> (Nat, Nat)
|
||||
allPairsMap n = allPairs !! n
|
||||
```
|
||||
|
||||
This certainly works to show that naturals and pairs of naturals can be put into bijection, but it when interpreting the results as integers or rationals, we double-count several of them. This is easy to see in the case of the integers, but it will also happen in the rationals. For example, the pair (3, 5) would correspond to 4/6 = 2/3, which has already been counted.
|
||||
|
||||
Incidentally, Haskell comes with a function called `nubBy`. This function eliminates duplicates according to another function of our choosing. We can also just implement it ourselves and use it to create a naive enumeration of integers and rationals, based on the equalities defined earlier:
|
||||
|
||||
```{.haskell}
|
||||
nubBy :: (a -> a -> Bool) -> [a] -> [a]
|
||||
nubBy f xs = nubBy' [] xs where
|
||||
nubBy' ys [] = []
|
||||
nubBy' ys (z:zs)
|
||||
-- Ignore this element, something equivalent is in ys
|
||||
| any (f z) ys = nubBy' ys zs
|
||||
-- Append this element to the result and our internal list
|
||||
| otherwise = z:nubBy' (z:ys) zs
|
||||
|
||||
allIntegers :: [(Nat, Nat)]
|
||||
-- Remove duplicates under integer equality
|
||||
allIntegers = nubBy intEqual allPairs
|
||||
|
||||
allIntegersMap :: Nat -> (Nat, Nat)
|
||||
allIntegersMap n = allIntegers !! n
|
||||
|
||||
allRationals :: [(Nat, Nat)]
|
||||
-- Add 1 to the numerator and denominator to get rid of 0,
|
||||
-- then remove duplicates under fraction equality
|
||||
allRationals = nubBy ratEqual $ map (\(a,b) -> (a+1, b+1)) allPairs
|
||||
|
||||
allRationalsMap :: Nat -> (Nat, Nat)
|
||||
allRationalsMap n = allRationals !! n
|
||||
```
|
||||
|
||||
For completeness's sake, the resulting pairs of each map are as follows
|
||||
|
||||
| *n* | `allIntegersMap n` | `allRationalsMap n` |
|
||||
|-----|--------------------|---------------------|
|
||||
| 0 | (0, 0) = 0 | (1, 1) = 1 |
|
||||
| 1 | (0, 1) = -1 | (1, 2) = 1/2 |
|
||||
| 2 | (1, 0) = 1 | (2, 1) = 2/1 |
|
||||
| 3 | (0, 2) = -2 | (1, 3) = 1/3 |
|
||||
| 4 | (2, 0) = 2 | (3, 1) = 3/1 |
|
||||
| 5 | (0, 3) = -3 | (1, 4) = 1/4 |
|
||||
| 6 | (3, 0) = 3 | (2, 3) = 2/3 |
|
||||
| 7 | (0, 4) = -4 | (3, 2) = 3/2 |
|
||||
| 8 | (4, 0) = 4 | (4, 1) = 4/1 |
|
||||
| 9 | (0, 5) = -5 | (1, 5) = 1/5 |
|
||||
| ... | ... | ... |
|
||||
|
||||
Note that the tuples produced by `allIntegers`, when interpreted as integers, happen to coincide with the earlier enumeration given by `listIntegers`.
|
||||
|
||||
|
||||
Tree of Fractions
|
||||
-----------------
|
||||
|
||||
There's an entirely separate structure which contains all rationals in least terms. It relies on an operation between two fractions called the *mediant*. For two rational numbers in least terms *p* and *q*, such that *p* < *q*, the mediant is designated *p* ⊕ *q* and will:
|
||||
|
||||
1. also be in least terms (with some exceptions, see below),
|
||||
2. be larger than *p*, and
|
||||
3. be smaller than *q*
|
||||
|
||||
$$
|
||||
p = {a \over b} < {c \over d} = q, \quad \gcd(a,b) = \gcd(c,d) = 1 \\ ~ \\
|
||||
p < p \oplus q < q \quad \phantom{\gcd(a+c, b+d) = 1} \\ ~ \\
|
||||
{a \over b} < {a+c \over b+d} < {c \over d}, \quad \gcd(a+c, b+d) = 1
|
||||
$$
|
||||
|
||||
We know our sequence of rationals starts with 1/1, 1/2, and 2/1. If we start as before with 1/1 and want to get the other quantities, then we can take its mediants with 0/1 and 1/0, respectively (handwaving the fact that the latter isn't a legitimate fraction).
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
&& && \large{1 \over 1} && && \\
|
||||
{ \oplus {0 \over 1} } && \large{/} && && \large{\backslash} ~ && \oplus {1 \over 0} \\
|
||||
&& \large{1 \over 2} && && \large{2 \over 1} &&
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
We might try continuing this pattern by doing the same thing to 1/2. We can take its mediant with 0/1 to get 1/3. Unfortunately, the mediant of 1/2 and 1/0 is 2/2 (as is the mediant of 2/1 with 0/1), which isn't in least terms, and has already appeared as 1/1.
|
||||
|
||||
We could try another fraction that's appeared in the tree. Unfortunately, 2/1 suffers from the same issue as 1/0 -- 1/2 ⊕ 2/1 = 3/3, which is the same quantity as before, despite both fractions being in least terms. On the other hand, 1/2 ⊕ 1/1 = 2/3, which is in least terms. Similarly, 2/1 ⊕ 1/1 is 3/2, its reciprocal.
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
&& && \large{1 \over 2} && && \\
|
||||
{ \oplus {0 \over 1} } && \large{/} && && \large{\backslash} ~ && \oplus {1 \over 1} \\
|
||||
&& \large{1 \over 3} && && \large{2 \over 3} &&
|
||||
\end{align*}
|
||||
\qquad \qquad
|
||||
\begin{align*}
|
||||
&& && \large{2 \over 1} && && \\
|
||||
{ \oplus {1 \over 1} } && \large{/} && && \large{\backslash} ~ && \oplus {1 \over 0} \\
|
||||
&& \large{3 \over 2} && && \large{3 \over 1} &&
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
The trick is to notice that a step to the left "updates" what the next step to the right looks like. Steps to the right behave symmetrically. For example, in the row we just generated, the left child of 2/3 is its mediant with 1/2, its right child is its mediant with 1/1.
|
||||
|
||||
Continuing this iteration ad infinitum forms the so-called [Stern-Brocot tree](https://en.wikipedia.org/wiki/Stern%E2%80%93Brocot_tree). A notable feature of this is that it is a [binary search tree](https://en.wikipedia.org/wiki/Binary_search_tree) (of infinite height). This means that for any node, the value at the node is greater than all values in the left subtree are and less than all values in the right subtree.
|
||||
|
||||
![]()
|
||||
|
||||
There's a bit of a lie in presenting the tree like this. As a binary tree, it's most convenient to show the nodes spaced evenly, but the distance between 1/1 and 2/1 is not the same as the distance between 1/1 and 1/2.
|
||||
|
||||
We can implement this in Haskell using `Data.Tree`. This package actually lets you describe trees with any number of child nodes, but we only need two for the sake of the Stern-Brocot tree.
|
||||
|
||||
```{.haskell}
|
||||
import Data.Tree
|
||||
|
||||
-- Make a tree by applying the function `make` to each node
|
||||
-- Start with the root value (1, 1), along with
|
||||
-- its left and right steps, (0, 1) and (1, 0)
|
||||
sternBrocot = unfoldTree make $ ((1,1), (0,1), (1,0)) where
|
||||
-- Place the first value in the tree, then describe the next
|
||||
-- values for `make` in a list:
|
||||
make (v@(vn, vd), l@(ln, ld), r@(rn, rd))
|
||||
= (v, [
|
||||
-- the left value, and its left (unchanged) and right steps...
|
||||
(((ln + vn), (ld + vd)), l, v),
|
||||
-- and the right value, and its left and right (unchanged) steps
|
||||
(((vn + rn), (vd + rd)), v, r)
|
||||
])
|
||||
```
|
||||
|
||||
|
||||
### Cutting the Tree Down
|
||||
|
||||
We're halfway there. All that remains is to read off every value in the tree as a sequence. Perhaps the most naive way would be to read off by always following the left or right child. Unfortunately, these give some fairly dull sequences.
|
||||
|
||||
```{.haskell}
|
||||
treePath :: [Int] -> Tree a -> [a]
|
||||
treePath xs (Node y ys)
|
||||
-- If we don't have any directions (xs), or the node
|
||||
-- has no children (ys), then there's nowhere to go
|
||||
| null xs || null ys = [y]
|
||||
-- Otherwise, go down subtree "x", then recurse with that tree
|
||||
-- and the rest of the directions (xs)
|
||||
| otherwise = y:treePath (tail xs) (ys !! head xs)
|
||||
|
||||
-- Always go left (child 0)
|
||||
alwaysLeft = treePath (repeat 0) sternBrocot
|
||||
-- = [(1,1),(1,2),(1,3),(1,4),(1,5),(1,6),(1,7),(1,8),(1,9),(1,10),...]
|
||||
-- i.e., numbers with numerator 1
|
||||
|
||||
-- Always go right (child 1)
|
||||
alwaysRight = treePath (repeat 1) sternBrocot
|
||||
-- = [(1,1),(2,1),(3,1),(4,1),(5,1),(6,1),(7,1),(8,1),(9,1),(10,1),...]
|
||||
-- i.e., numbers with denominator 1
|
||||
```
|
||||
|
||||
Rather than by following paths in the tree, we can instead do a breadth-first search. In other words, we read off each row individually, in order. This gives us our sequence of rational numbers with no repeats.
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
\mathbb{N^+}& ~\rightarrow~ \mathbb{Q} \\
|
||||
n & ~\mapsto~ \text{bfs}[n]
|
||||
\end{align*}
|
||||
\\ ~ \\
|
||||
1 \mapsto 1/1,~ \\
|
||||
2 \mapsto 1/2,\quad 3 \mapsto 2/1,~ \\
|
||||
4 \mapsto 1/3,\quad 5 \mapsto 2/3, \quad 6 \mapsto 3/2, \quad 7 \mapsto 3/1,~ ...
|
||||
$$
|
||||
|
||||
For convenience, this enumeration is given starting from 1 rather than from 0. This numbering makes it clearer that each row starts with a power of 2, since the structure is a binary tree, and the complexity doubles with each row. The enumeration could just as easily start from 0 by starting with $\N$, then getting to $\N^+$ with $n \mapsto n+1$.
|
||||
|
||||
We can also write a breadth-first search in Haskell, for posterity:
|
||||
|
||||
```{.haskell}
|
||||
bfs :: Tree a -> [a]
|
||||
bfs (Node root children) = bfs' root children where
|
||||
-- Place the current node in the list
|
||||
bfs' v [] = [v]
|
||||
-- Pluck one node off our list of trees, then recurse with
|
||||
-- the rest, along with that node's children
|
||||
bfs' v ((Node y ys):xs) = v:bfs' y (xs ++ ys)
|
||||
|
||||
sternBrocotRationals = bfs sternBrocot
|
||||
```
|
||||
|
||||
The entries in this enumeration have already been given.
|
||||
|
||||
|
||||
### Another Tree
|
||||
|
||||
Another tree of fractions to consider is the tree of binary fractions. These fractions simply consist of odd numbers divided by powers of two. The most convenient way to organize these into a tree is to keep denominators equal if the nodes have the same depth from the root. We also stipulate that we arrange the nodes as a binary search tree, like the Stern-Brocot tree.
|
||||
|
||||
The tree starts from 1/1 as before. Its children have denominator 2, so we have 1/2 to the left and 3/2 to the right. This is equivalent to subtracting 1/2 for the left step and adding 1/2 for the right step. At the next layer, we want fractions with denominator 1/4, and do similarly. In terms of adding and subtracting, we just use 1/4 instead of 1/2.
|
||||
|
||||
![]()
|
||||
|
||||
We can describe this easily in Haskell:
|
||||
|
||||
```{.haskell}
|
||||
-- Start with 1/1 (i.e., (1, 1))
|
||||
binFracTree = unfoldTree make $ (1,1) where
|
||||
-- Place the first value in the tree, then describe the next
|
||||
-- values for `make` in a list:
|
||||
make v@(vn, vd)
|
||||
= (v, [
|
||||
-- double the numerator and denominator, then subtract 1 from the numerator
|
||||
(2*vn - 1, 2*vd),
|
||||
-- same, but add 1 to the numerator instead
|
||||
(2*vn + 1, 2*vd)
|
||||
])
|
||||
```
|
||||
|
||||
The entries of this tree have an additional interpretation when converted to their binary expansions. These fractions always terminate in a "1" in binary, but ignoring this final entry, starting from the root and following "left" for 0 and "right" for 1 places us at that fraction in the tree. In other words, the binary expansions encode the path from the root to the node.
|
||||
|
||||
|
||||
Why Bother?
|
||||
-----------
|
||||
|
||||
The tree of binary fractions and the Stern-Brocot tree are both infinite binary search trees, so we might imagine overlaying one tree over the other, pairing up the individual entries.
|
||||
|
||||
![]()
|
||||
|
||||
In Haskell, we can pair up entries recursively:
|
||||
|
||||
```{.haskell}
|
||||
zipTree :: Tree a -> Tree b -> Tree (a,b)
|
||||
-- Pair the values in the nodes together, then recurse with the child trees
|
||||
zipTree (Node x xs) (Node y ys) = Node (x,y) $ zipWith zipTree xs ys
|
||||
|
||||
binarySBTree = zipTree sternBrocot binFracTree
|
||||
```
|
||||
|
||||
Conveniently, both left subtrees of the root fall in the interval (0, 1). It also pairs up 1 and 1/2 with themselves. Doing so establishes a bijection between the rationals and the binary rationals in that interval. Rationals are more continuous than integers, so it might be of some curiosity to plot this function. We only have to look at a square over the unit interval. Doing so reveals a curious shape:
|
||||
|
||||
:::: {}
|
||||
::: {}
|
||||
![]()
|
||||
:::
|
||||
|
||||
::: {}
|
||||
![]()
|
||||
:::
|
||||
|
||||
Left: binary rationals on the x-axis, rationals on the y-axis. <br>
|
||||
Right: rationals on the x-axis, binary rationals on the y-axis.
|
||||
::::
|
||||
|
||||
The plot on the right which maps the rationals to the binary rationals is known as [Minkowski's question mark function](https://en.wikipedia.org/wiki/Minkowski%27s_question-mark_function). Notice that this function is nearly 1/2 for values near 1/2 (nearly 1/4 for values near 1/3, nearly 1/8 for values near 1/4, etc.).
|
||||
|
||||
|
||||
### I'm Repeating Myself
|
||||
|
||||
The inverse question mark map (which I'll call ¿ for short), besides mapping binary rationals to rationals, has an interesting relationship with other rational numbers. Recall that we only defined the function in terms of fractions which happen to have finite binary expansions. Those with infinite binary expansions, such as 1/3 (and indeed, any fraction whose denominator isn't a power of 2) aren't defined.
|
||||
|
||||
$$
|
||||
{1 \over 2} = 0.1_2 \\
|
||||
{1 \over 3} = 0.\overline{01} = 0.\textcolor{red}{01}\textcolor{green}{01}\textcolor{blue}{01}... \\
|
||||
{1 \over 4} = 0.01_2 \\
|
||||
{1 \over 5} = 0.\overline{0011} = 0.\textcolor{red}{0011}\textcolor{green}{0011}\textcolor{blue}{0011}... \\
|
||||
\vdots
|
||||
$$
|
||||
|
||||
We can persevere if we continue to interpret the binary strings as a path in the tree. This means that for 1/3, we go left initially, then alternate between going left and right. As we do so, let's take note of the values we pass along the way:
|
||||
|
||||
```{.haskell}
|
||||
-- Follow the path described by the expansion of 1/3
|
||||
treePath (0:cycle [0,1]) $ zipTree sternBrocot binFracTree
|
||||
```
|
||||
|
||||
| | Binary fraction | Binary fraction (decimal) | Stern-Brocot rational | Stern-Brocot rational (decimal)
|
||||
|-----|-------------------|---------------------------|-----------------------|--------------------------------|
|
||||
| 0 | 1/1 | 1 | 1/1 | 1 |
|
||||
| 1 | 1/2 | 0.5 | 1/2 | 1/2 |
|
||||
| 2 | 1/4 | 0.25 | 1/3 | 0.3333333... |
|
||||
| 3 | 3/8 | 0.375 | 2/5 | 0.4 |
|
||||
| 4 | 5/16 | 0.3125 | 3/8 | 0.375 |
|
||||
| 5 | 11/32 | 0.34375 | 5/13 | 0.38461538... |
|
||||
| 6 | 21/64 | 0.328125 | 8/21 | 0.32812538... |
|
||||
| 7 | 43/128 | 0.3359375 | 13/34 | 0.3823529... |
|
||||
| 8 | 85/256 | 0.33203125 | 21/55 | 0.38181818... |
|
||||
| ⋮ | ⋮ | ⋮ | ⋮ | ⋮ |
|
||||
| 100 | (too big to show) | 0.3333333333... | (too big to show) | 0.381966011... |
|
||||
| ⋮ | ⋮ | ⋮ | ⋮ | ⋮ |
|
||||
|
||||
:::: {layout-ncol = "2"}
|
||||
::: {}
|
||||
![]()
|
||||
:::
|
||||
|
||||
::: {}
|
||||
![]()
|
||||
:::
|
||||
|
||||
Left: binary convergents of 1/3 <br>
|
||||
Right: ¿ applied to binary convergents of 1/3, which also appear to converge
|
||||
::::
|
||||
|
||||
Both sequences appear to converge to a number, with the binary fractions obviously converging to 1/3. The rationals from the Stern-Brocot don't appear to be converging to a repeating decimal. Looking closer, the numerators and denominators of the fractions appear to come from the Fibonacci numbers. In fact, the quantity that the fractions approach is $2 - \varphi$, where φ is the golden ratio. This number is the root of the polynomial $x^2 - 3x + 1$.
|
||||
|
||||
In fact, all degree 2 polynomials have roots that are encoded by a repeating path in the Stern-Brocot tree. Put another way, ¿ can be extended to map rationals other than binary fractions to quadratic roots (and ? maps quadratic roots to rational numbers). This is easier to understand when writing the quantity as its [continued fraction expansion](https://en.wikipedia.org/wiki/Continued_fraction), but that's an entirely separate discussion.
|
||||
|
||||
Either way, it tells us something interesting: not only can all rational numbers be enumerated, but so can quadratic *irrationals*.
|
||||
|
||||
|
||||
### The Other Side
|
||||
|
||||
I'd like to briefly digress from talking about enumerations and mention the right subtree. The question mark function, as defined here, is only defined on numbers between 0 and 1 (and even then, technically only rational numbers). According to Wikipedia's definition, the question mark function is quasi-periodic -- $?(x + 1) = ?(x) + 1$. On the other hand, according to the definition by pairing up the two trees, rationals greater than 1 get mapped to binary fractions between 1 and 2.
|
||||
|
||||
:::: {layout-ncol = "2"}
|
||||
::: {}
|
||||
![]()
|
||||
:::
|
||||
|
||||
::: {}
|
||||
![]()
|
||||
:::
|
||||
|
||||
Question mark function including right subtree. <br>
|
||||
Left: linear x-axis. <br>
|
||||
Right: (base 2)-logarithmic x-axis.
|
||||
::::
|
||||
|
||||
Here are graphs describing *our* question mark function, on linear and logarithmic plots. Instead of repeating, the function continues its self-similar behavior as it proceeds onward to infinity (logarithmically). The right graph stretches from -∞, where its value would be 0, to ∞, where its value would be 2.
|
||||
|
||||
Personally, I like this definition a bit better, if only because it matches other ways of thinking about the interval (0, 1). For example,
|
||||
|
||||
- In topology, it's common to show that this interval is homeomorphic to the entire real line
|
||||
|
||||
- It's similar to the [rational functions which appear in stereography](), which continue to infinity instead of being periodic
|
||||
|
||||
- It showcases how the Stern-Brocot tree sorts rational numbers by complexity better
|
||||
|
||||
However, it's also true that different definitions are good for different things. For example, periodicity matches the intuition that numbers can be decomposed into a fractional and integral part. Integral parts grow without bound, while fractional parts are periodic, just like the function would be.
|
||||
|
||||
|
||||
Closing
|
||||
-------
|
||||
|
||||
I'd like to draw this discussion of enumerating numbers to a close for now. I wrote this article to establish some preliminaries regarding *another* post that I have planned. On the other hand, since I was describing the Stern-Brocot tree, I felt it also pertinent to show the question mark function, since it's a very interesting self-similar curve. Even then, I have shown them as a curiosity instead of giving them their time in the spotlight.
|
||||
|
||||
I have omitted some things I would like to have discussed, such as [order type](https://en.wikipedia.org/wiki/Order_type), and enumerating things beyond just the quadratic irrationals. I may return to some of these topics in the future, such as to show a way to order integer polynomials.
|
||||
|
||||
Diagrams created with GeoGebra (because trying to render them in LaTeX would have taken too long) and Matplotlib (yes, I called into a Python interpreter from Haskell out of laziness).
|
||||
508
number_number/2/index.qmd
Normal file
508
number_number/2/index.qmd
Normal file
@ -0,0 +1,508 @@
|
||||
---
|
||||
format:
|
||||
html:
|
||||
html-math-method: katex
|
||||
---
|
||||
|
||||
<style>
|
||||
.red {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.green {
|
||||
color: green;
|
||||
}
|
||||
</style>
|
||||
|
||||
Numbering Numbers, Part 2: Ordering Obliquely
|
||||
=============================================
|
||||
|
||||
This post assumes you have read the [previous one](), which discusses the creation of sequences containing every integer, every binary fraction, and in fact, every fraction. From the fractions, we also could enumerate quadratic irrational numbers using the *question-mark function*.
|
||||
|
||||
|
||||
Other Irrationals
|
||||
-----------------
|
||||
|
||||
Because rationals -- and even some irrationals -- can be enumerated, we might imagine that it would be nice to enumerate *all* irrational numbers. Unfortunately, we're not very lucky this time.
|
||||
|
||||
Let's start by making what we mean by "number" more direct. We've already been exposed to infinite expansions, like 1/3 in binary (and conveniently, also decimal). In discussing the question mark function, I mentioned the utility of repeating expansions as a means of accessing quadratic rationals in the Stern-Brocot tree. A general sequence need not repeat, so we choose to treat these extra "infinite expansions" as numbers. Proving that a rational number must have a repeating expansion is difficult, but if we accept this premise, then the new non-repeating expansions are our irrationals. For example, $2 - \varphi \approx 0.381966...$, which we encountered last time, is such a number.
|
||||
|
||||
Doing this introduces a number of headaches, the least of which is attempting to do arithmetic with such quantities. However, we're only concerned with the contents of these sequences to show why we can't list out all irrationals.
|
||||
|
||||
|
||||
### Diagonalization
|
||||
|
||||
We can narrow our focus to the interval between 0 and 1, since the numbers outside this interval are their reciprocals. Now (if we agree to use base ten), "all numbers between 0 and 1" as we've defined them begin with "0.", followed by an infinite sequence of digits 0-9. Suppose that we have an enumeration of every infinite sequence on this list -- no sequence is left out. [Cantor's diagonal argument](https://en.wikipedia.org/wiki/Cantor%27s_diagonal_argument) shows that a new sequence can be found by taking the sequence on the diagonal and changing each individual digit to another one. The new sequence differs in at least one place from every element on the list, so it cannot be on the list, showing that such an enumeration cannot exist.
|
||||
|
||||
This is illustrated for binary sequences in this diagram:
|
||||
|
||||
::: {}
|
||||
<figure class="wp-block-image size-full">
|
||||
<a title="Jochen Burghardt, CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0>, via Wikimedia Commons" href="https://commons.wikimedia.org/wiki/File:Diagonal_argument_01_svg.svg"><img width="256" alt="Diagonal argument 01 svg" style="background-color:white !important; border-radius: 5px" src="https://upload.wikimedia.org/wikipedia/commons/thumb/b/b7/Diagonal_argument_01_svg.svg/256px-Diagonal_argument_01_svg.svg.png"></a>
|
||||
<figcaption>
|
||||
Source: Jochen Burghardt, via Wikimedia Commons
|
||||
</figcaption>
|
||||
</figure>
|
||||
:::
|
||||
|
||||
- It's fairly common to show this argument without much further elaboration, but there are a few problems with it:
|
||||
- We're using infinite sequences of digits, not numbers
|
||||
- Equality between sequences is defined by having all elements coincide
|
||||
- We assume we have an enumeration of sequences to which the argument applies. The contents of the enumeration are a mystery.
|
||||
- We have no idea where the rational numbers are, or if we'd construct one by applying the diagonal argument
|
||||
|
||||
|
||||
### Equality
|
||||
|
||||
The purpose of the diagonal argument is to produce a new sequence which was not previously enumerated. The sequence is different in all positions, but what we actually want is equality with respect to the base. In base ten, we have the peculiar identity that [$0.\overline{9} = 1$](https://en.wikipedia.org/wiki/0.999...). This means that if the diagonal argument (applied to base ten sequences) constructs a new sequence with the digit 9 repeating forever, it might be equivalent to a sequence which was already in the list:
|
||||
|
||||
```{python}
|
||||
#| echo: false
|
||||
|
||||
from IPython.display import Markdown
|
||||
from tabulate import tabulate
|
||||
|
||||
red = lambda x: f"<span class=\"red\">{x}</span>"
|
||||
green = lambda x: f"<span class=\"green\">{x}</span>"
|
||||
|
||||
def zipconcat(*args):
|
||||
return [sum(z, start=[]) for z in zip(*map(list, args))]
|
||||
|
||||
diag = lambda f, yss: [
|
||||
[
|
||||
f(y) if i == j else y
|
||||
for j, y in enumerate(ys)
|
||||
]
|
||||
for i, ys in enumerate(yss)
|
||||
]
|
||||
|
||||
Markdown(tabulate(
|
||||
zipconcat(
|
||||
map(lambda x: [x], [0, 1, 2, green(3), 4, 5, "...", green("ω")]),
|
||||
diag(red, [
|
||||
[1, 2, 3, 4, 5, 6],
|
||||
[9, 2, 4, 8, 3, 7],
|
||||
[2, 2, 8, 2, 2, 2],
|
||||
map(green, [2, 3, 9, 3, 9, 9]),
|
||||
[9, 4, 9, 4, 9, 4],
|
||||
[8, 1, 2, 5, 7, 9],
|
||||
["...", "...", "...", "...", "...", "..."],
|
||||
map(green, [2, 3, 9, 4, 0, 0]),
|
||||
]),
|
||||
[["..."]]*8,
|
||||
),
|
||||
headers=["Sequence", *range(6), "..."],
|
||||
))
|
||||
```
|
||||
|
||||
A case in which the diagonal argument could construct a number already on the list.
|
||||
|
||||
In the above table, sequence 3 is assumed to continue with 9's forever. The new sequence ω comes from taking the red digits along the diagonal and mapping each digit to the next one (mod ten). In the enumeration given, the diagonal continues with 9's forever, so we end up with 0's forever in ω.
|
||||
|
||||
|
||||
### Picking a Sequence and Ensuring Rationals
|
||||
|
||||
"No problem, just pick another enumeration," you might say. Indeed, the example given relies on a simple function applied on the diagonal and an enumeration to which it is particularly ill-suited.
|
||||
|
||||
Instead, let's focus on something we *can* do. Instead of assuming we have all irrational numbers listed out already, let's start smaller. As of last post, we already have several ways to enumerate all rational numbers between 0 and 1. If we take this enumeration, convert rational numbers to their expansions in a base, and apply the diagonal argument, the resulting quantity *should* be an irrational number.
|
||||
|
||||
For convenience, we'll use the binary expansions of rationals as our sequences. That way, to get a unique sequence on the diagonal, we only have to worry about changing "0"s to "1"s (and vice versa). Since we have less flexibility in our digits, it also relieves us from some of responsibility of finding a "good" function, like in the decimal case. However, it's still possible the argument constructs a number already equal to something on the list.
|
||||
|
||||
|
||||
Converting to Silicon
|
||||
---------------------
|
||||
|
||||
Before going any further, let's convert the diagonal argument into a function. We want to, given an enumeration of binary sequences, produce a new sequence not on the list. This can be implemented in Haskell fairly easily:
|
||||
|
||||
|
||||
```{.haskell}
|
||||
diagonalize :: [[Int]] -> [Int]
|
||||
-- pair each sequence with its index
|
||||
diagonalize xs = let sequences = zip [0..] xs in
|
||||
-- read out the red diagonal
|
||||
let diagonal = map (\(n, s_n) -> (s_n !! n)) sequences in
|
||||
-- map 0 to 1 and vice versa
|
||||
map (1 -) diagonal
|
||||
-- or in point-free form
|
||||
-- diagonalize = map (\(x,y) -> 1 - (y !! x)) . zip [0..]
|
||||
```
|
||||
|
||||
Nothing about this function is specific to "binary sequences", since `Int` contains values other than `1` and `0`. It's just more intuitive to work with them instead of "`True`" and "`False`" (since `Bool` actually does have 2 values). You can replace `1 -` with `not` to get a similar function for the type `Bool`.
|
||||
|
||||
We also need a function to get a rational number's binary expansion. This is simple if you recall how to do long division. We try to divide the numerator by the denominator, "emit" the quotient as part of the result list, then continue with the remainder (and the denominator is unchanged). It's not *quite* that simple, since we also need to "bring down" more zeroes. In binary, we can add more zeroes to an expansion by just multiplying by 2.
|
||||
|
||||
```{.haskell}
|
||||
binDiv :: Int -> Int -> [Int]
|
||||
-- Divide n by d; q becomes part of the result list
|
||||
-- The rest of the list comes from doubling the remainder and keeping d fixed
|
||||
binDiv n d = let (q,r) = n `divMod` d in q:binDiv (2*r) d
|
||||
|
||||
x = take 10 $ binDiv 1 3
|
||||
-- x = [0,0,1,0,1,0,1,0,1,0]
|
||||
-- 1/3 = 0.01010101...
|
||||
y = take 10 $ binDiv 1 5
|
||||
-- y = [0,0,0,1,1,0,0,1,1,0]
|
||||
-- 1/5 = 0.00110011...
|
||||
```
|
||||
|
||||
This function gives us the leading 0 (actually the integer part of the quotient of n by d), but we can peel it off by applying tail.
|
||||
|
||||
Since we intend to interpret these sequences as binary numbers, we might as well also convert this into a form we recognize as a number. All we need to do is take a weighted sum of each sequence by its binary place value.
|
||||
|
||||
```{.haskell}
|
||||
fromBinSeq :: Int -> [Int] -> Double
|
||||
-- Construct a list of place values starting with 1/2
|
||||
fromBinSeq p = let placeValues = (tail $ iterate (/2) 1) in
|
||||
-- Convert the sequence to a type that can multiply with
|
||||
-- the place values, then weight, truncate, and sum.
|
||||
sum . take p . zipWith (*) placeValues . map fromIntegral
|
||||
|
||||
oneFifth = fromBinSeq 64 $ tail $ binDiv 1 5
|
||||
-- oneFifth = 0.2 :: Double
|
||||
```
|
||||
|
||||
The precision *p* here is mostly useless, since we intend to take this to as far as doubles will go. *p* = 100 will do for most sequences, since it's rare that we'll encounter more than a few zeroes at the beginning of a sequence.
|
||||
|
||||
|
||||
Some Enumerations
|
||||
-----------------
|
||||
|
||||
Now, for the sake of argument, let's look at an enumeration that fails. Using the tree of binary fractions from the last post, we use a breadth-first search to create a list of terminating binary expansions.
|
||||
|
||||
::: {}
|
||||
![]()
|
||||
Tree of binary fractions, as the tree of terminating binary expansions. We perform a BFS on the left subtree to obtain the values between 0 and 1.
|
||||
:::
|
||||
|
||||
```{python}
|
||||
#| echo: false
|
||||
|
||||
Markdown(tabulate(
|
||||
zipconcat(
|
||||
[
|
||||
[0, "1/2"],
|
||||
[1, "1/4"],
|
||||
[2, "3/4"],
|
||||
[3, "1/8"],
|
||||
[4, "3/8"],
|
||||
[5, "5/8"],
|
||||
[6, "7/8"],
|
||||
[7, "1/16"],
|
||||
["...", "..."],
|
||||
["ω", ""],
|
||||
],
|
||||
diag(red, [
|
||||
[1, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0],
|
||||
[1, 1, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0],
|
||||
[0, 1, 1, 0, 0, 0],
|
||||
[1, 0, 1, 0, 0, 0],
|
||||
[1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0],
|
||||
["...", "...", "...", "...", "...", "..."],
|
||||
[0, 0, 1, 1, 1, 1],
|
||||
]),
|
||||
[["..."]]*10,
|
||||
),
|
||||
headers=["*n*", "Number", *range(6), "..."],
|
||||
))
|
||||
```
|
||||
|
||||
```{.haskell}
|
||||
-- Extract the left subtree (i.e., the first child subtree)
|
||||
badDiag = let (Node _ (tree:__)) = binFracTree in
|
||||
diagonalize $ map (tail . uncurry binDiv) $ bfs tree
|
||||
```
|
||||
|
||||
Computing the diagonal sequence, it quickly becomes apparent that we're going to keep getting "0"s along the diagonal. This is because we're effectively just counting in binary some number of digits to the right. The length of a binary expansion grows logarithmically rather than linearly; in other words, we can't count fast enough by just adding 1 (or adding any number, really).
|
||||
|
||||
Even worse than this, we get a sequence which is equal to sequence 1 as a binary expansion. We can't even rely on the diagonal argument to give us a new number that *isn't* equal to a binary fraction.
|
||||
|
||||
|
||||
### Stern-Brocot Diagonal
|
||||
|
||||
The Stern-Brocot tree contains more than just binary fractions, so we're bound to encounter more than "0" forever when running along the diagonal. Again, looking at the left subtree, we can read off fractions between 0 and 1.
|
||||
|
||||
![]()
|
||||
|
||||
We end up with the following enumeration:
|
||||
|
||||
```{python}
|
||||
#| echo: false
|
||||
|
||||
Markdown(tabulate(
|
||||
zipconcat(
|
||||
[
|
||||
[0, "1/2"],
|
||||
[1, "1/3"],
|
||||
[2, "2/3"],
|
||||
[3, "1/4"],
|
||||
[4, "2/5"],
|
||||
[5, "3/5"],
|
||||
[6, "3/4"],
|
||||
[7, "1/5"],
|
||||
["...", "..."],
|
||||
["ω", ""],
|
||||
],
|
||||
diag(red, [
|
||||
[1, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 1, 0, 1, 0, 1],
|
||||
[1, 0, 1, 0, 1, 0, 1, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 0, 0, 1, 1, 0],
|
||||
[1, 0, 0, 1, 1, 0, 0, 1],
|
||||
[1, 1, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 0, 0, 1, 1],
|
||||
["...", "...", "...", "...", "...", "...", "...", "..."],
|
||||
[0, 0, 0, 1, 1, 1, 1, 0],
|
||||
]),
|
||||
[["..."]]*10,
|
||||
),
|
||||
headers=["*n*", "Number", *range(8), "..."],
|
||||
))
|
||||
```
|
||||
|
||||
```{.haskell}
|
||||
-- Extract the left subtree (i.e., the first child subtree)
|
||||
sbDiag = let (Node _ (tree:__)) = sternBrocot in
|
||||
diagonalize $ map (tail . uncurry binDiv) $ bfs tree
|
||||
|
||||
-- take 20 sbDiag = [0,0,0,1,1,1,1,0,1,1,0,1,1,1,1,1,0,0,1,1]
|
||||
-- in other words, 0.00011110110111110011...
|
||||
```
|
||||
|
||||
When expressed as a decimal, the new sequence corresponds to the value 0.12059395276... . Dually, its continued fraction expansion begins \[0; 8,3,2,2,1,2,12, ...\]. While the number is (almost) certainly irrational, I have no idea whether it is algebraic or transcendental.
|
||||
|
||||
|
||||
### Pairs, without repeats
|
||||
|
||||
We have a second, similar enumeration given by `allRationalsMap` in the previous post. We'll need to filter out the numbers greater than 1 from this sequence, but that's not too difficult since we're already filtering out repeats.
|
||||
|
||||
```{python}
|
||||
#| echo: false
|
||||
|
||||
Markdown(tabulate(
|
||||
zipconcat(
|
||||
[
|
||||
[0, "1/2"],
|
||||
[1, "1/3"],
|
||||
[2, "1/4"],
|
||||
[3, "2/3"],
|
||||
[4, "1/5"],
|
||||
[5, "1/6"],
|
||||
[6, "2/5"],
|
||||
[7, "3/4"],
|
||||
["...", "..."],
|
||||
["ω", ""],
|
||||
],
|
||||
diag(red, [
|
||||
[1, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 1, 0, 1, 0, 1],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0],
|
||||
[1, 0, 1, 0, 1, 0, 1, 0],
|
||||
[0, 0, 1, 1, 0, 0, 1, 1],
|
||||
[0, 0, 1, 0, 1, 0, 1, 0],
|
||||
[0, 1, 1, 0, 0, 1, 1, 0],
|
||||
[1, 1, 0, 0, 0, 0, 0, 0],
|
||||
["...", "...", "...", "...", "...", "...", "...", "..."],
|
||||
[0, 0, 1, 1, 1, 1, 0, 1],
|
||||
]),
|
||||
[["..."]]*10,
|
||||
),
|
||||
headers=["*n*", "Number", *range(8), "..."],
|
||||
))
|
||||
```
|
||||
|
||||
```{.haskell}
|
||||
-- Only focus on the rationals whose denominator is bigger
|
||||
arDiag = let rationals01 = filter (uncurry (<)) allRationals in
|
||||
diagonalize $ map (tail . uncurry binDiv) $ rationals01
|
||||
|
||||
-- take 20 arDiag = [0,0,1,1,1,1,0,1,0,1,1,1,0,1,0,0,0,1,0,0]
|
||||
-- in other words, 0.00111101011101000100...
|
||||
```
|
||||
|
||||
This new sequence has a decimal expansion equivalent to 0.24005574958... (continued fraction expansion begins \[0; 4, 6, 28, 1, 1, 5, 1, ...\]). Again, this is probably irrational, since WolframAlpha has no idea on whether a closed form exists.
|
||||
|
||||
|
||||
The Diagonal Transform
|
||||
----------------------
|
||||
|
||||
Why stop with just one? This new number can just be tacked onto the beginning of the list. Then, we re-apply the diagonal argument to obtain a new number. And so on ad infinitum.
|
||||
|
||||
```{python}
|
||||
#| echo: false
|
||||
|
||||
diag2 = lambda d, yss: [
|
||||
[
|
||||
d.get(i - j, lambda x: x)(y)
|
||||
for j, y in enumerate(ys)
|
||||
]
|
||||
for i, ys in enumerate(yss)
|
||||
]
|
||||
|
||||
Markdown(tabulate(
|
||||
[["...", "", *(["..."]*9)]]
|
||||
+ zipconcat(
|
||||
[
|
||||
[green("-2"), ""],
|
||||
[red("-1"), ""],
|
||||
[0, "1/2"],
|
||||
[1, "1/3"],
|
||||
[2, "2/3"],
|
||||
[3, "1/4"],
|
||||
[4, "2/5"],
|
||||
[5, "3/5"],
|
||||
[6, "3/4"],
|
||||
[7, "1/5"],
|
||||
["...", "..."],
|
||||
],
|
||||
diag2(
|
||||
{
|
||||
1: green,
|
||||
2: red,
|
||||
},
|
||||
[
|
||||
[1, 1, 1, 1, 1, 0, 1, 1],
|
||||
[0, 0, 0, 1, 1, 1, 1, 0],
|
||||
[1, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 1, 0, 1, 0, 1],
|
||||
[1, 0, 1, 0, 1, 0, 1, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 0, 0, 1, 1, 0],
|
||||
[1, 0, 0, 1, 1, 0, 0, 1],
|
||||
[1, 1, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 0, 0, 1, 1],
|
||||
["...", "...", "...", "...", "...", "...", "...", "..."],
|
||||
]
|
||||
),
|
||||
[["..."]]*12,
|
||||
),
|
||||
headers=["*n*", "Number", *range(8), "..."],
|
||||
))
|
||||
```
|
||||
Using the Stern-Brocot enumeration ~~because I like it better~~
|
||||
|
||||
```{.haskell}
|
||||
transform :: [[Int]] -> [[Int]]
|
||||
-- Emit the new diagonal sequence, then recurse with the new sequence
|
||||
-- prepended to the original enumeration
|
||||
transform xs = let ds = diagonalize xs in ds:transform (ds:xs)
|
||||
|
||||
sbDiagSeq = let (Node _ (tree:__)) = sternBrocot in
|
||||
transform $ map (tail . uncurry binDiv) $ bfs tree
|
||||
-- take 10 $ map (fromBinSeq 100) $ sbDiagSeq
|
||||
-- = [0.12059395276295479, 0.9839683315741587, 0.39401828609177836,
|
||||
-- 0.7474914867558182, 3.876798422930419e-2, 0.7802097209903278,
|
||||
-- 0.3215249242021624, 0.6795283379777878, 0.1938245109955674,
|
||||
-- 0.947203605609322]
|
||||
```
|
||||
|
||||
Does this list out "all" irrational numbers? Not even close. In fact, this just gives us a bijection between the original enumeration and a new one containing our irrationals. The new numbers we get depend heavily on the order of the original sequence. This is obvious just by looking at the first entry produced by our two good enumerations. Perhaps if we permuted the enumeration of rationals in all possible ways, we would end up listing all irrational numbers, but we'd also run through [all ways of ordering the natural numbers](https://en.wikipedia.org/wiki/Baire_space_%28set_theory%29).
|
||||
|
||||
The fact that "bad" enumerations exist tells us that it's not even guaranteed that we don't collide with any rationals. I conjecture that the good enumerations won't do so, since we shouldn't ever encounter an infinite sequence of "0"s, and a sequence should eventually differ in at least one position from one already listed.
|
||||
|
||||
|
||||
### Almost Involution
|
||||
|
||||
Since the function has the same input and output type, you may wonder what happens when it's applied multiple times. Perhaps you assume that, since `\x -> 1 - x` swaps 0 and 1, we'll get the original sequence back again. Alas, experimentation proves us wrong.
|
||||
|
||||
```{.haskell}
|
||||
sbDiagSeq2 = transform sbDiagSeq
|
||||
-- take 10 $ map (fromBinSeq 100) $ sbDiagSeq2
|
||||
-- [0.5, 1/2
|
||||
-- 0.3333333333333333, 1/3
|
||||
-- 0.9166666666666666, 11/12
|
||||
-- 0.125, 1/8
|
||||
-- 0.7124999999999999, 57/80
|
||||
-- 0.2875, 23/80
|
||||
-- 0.859375, 55/64
|
||||
-- 7.5e-2, 3/40
|
||||
-- 0.6841517857142857, ???, possibly 613/896
|
||||
-- 0.486328125] 249/512
|
||||
```
|
||||
|
||||
The fraction forms of the above numbers are my best guesses. Either way, it only matches for the first two terms before going off the rails.
|
||||
|
||||
Even stranger than this distorted reflection is the fact that going through the mirror again doesn't take us anywhere new
|
||||
|
||||
```{.haskell}
|
||||
sbDiagSeq3 = transform sbDiagSeq2
|
||||
-- take 10 $ map (fromBinSeq 100) $ sbDiagSeq3
|
||||
-- = [0.12059395276295479, 0.9839683315741587, 0.39401828609177836,
|
||||
-- 0.7474914867558182, 3.876798422930419e-2, 0.7802097209903278,
|
||||
-- 0.3215249242021624, 0.6795283379777878, 0.1938245109955674,
|
||||
-- 0.947203605609322]
|
||||
--
|
||||
-- (take 10 $ map (fromBinSeq 100) $ sbDiagSeq3) \
|
||||
-- == (take 10 $ map (fromBinSeq 100) $ sbDiagSeq)
|
||||
-- = True
|
||||
```
|
||||
|
||||
In other words, applying the transform thrice is the same as applying the transform once. Not that this has been shown rigorously in the least -- it's only been tested numerically for the first 10 numbers that we generate.
|
||||
|
||||
Regardless, this semi-involutory behavior is strange and nontrivial. And it's not limited to this enumeration.
|
||||
|
||||
```{.haskell}
|
||||
arDiagSeq = let rationals01 = filter (uncurry (<)) allRationals in
|
||||
transform $ map (tail . uncurry binDiv) rationals01
|
||||
|
||||
-- -- What goes in isn't what comes out
|
||||
-- take 10 $ map (fromBinSeq 100) $ transform arDiagSeq
|
||||
-- [0.5, 1/2
|
||||
-- 0.3333333333333333, 1/3
|
||||
-- 0.75, 3/4
|
||||
-- 0.16666666666666666, 1/6
|
||||
-- 0.7, 7/10
|
||||
-- 0.41666666666666663, 5/12
|
||||
-- 0.8687499999999999, 139/160
|
||||
-- 0.1015625, 13/128
|
||||
-- 0.5295758928571428, ???, possibly 949/1792
|
||||
-- 0.49453125] 633/1280
|
||||
--
|
||||
--
|
||||
-- -- And applying the transform twice is sometimes an identity (maybe)
|
||||
-- (take 10 $ map (fromBinSeq 100) $ transform $ transform arDiagSeq) \
|
||||
-- == (take 10 $ map (fromBinSeq 100) $ arDiagSeq)
|
||||
-- = True
|
||||
|
||||
bfDiagSeq = let (Node _ (tree:__)) = binFracTree in
|
||||
transform $ map (tail . uncurry binDiv) $ bfs tree
|
||||
-- -- The bad enumeration just gives more binary fractions...
|
||||
-- take 10 $ map (fromBinSeq 100) bfDiagSeq
|
||||
-- [0.25, 1/4
|
||||
-- 1.0, 1/1
|
||||
-- 0.5, 1/2
|
||||
-- 0.625, 5/8
|
||||
-- 6.25e-2, 1/16
|
||||
-- 0.78125, 25/32
|
||||
-- 0.390625, 25/64
|
||||
-- 0.6328125, 81/128
|
||||
-- 0.19140625, 49/256
|
||||
-- 0.814453125] 417/512
|
||||
|
||||
-- -- But it still doesn't mean double-transforming gives the original
|
||||
-- take 10 $ map (fromBinSeq 100) $ transform bfDiagSeq
|
||||
--
|
||||
-- [0.5, 1/2
|
||||
-- 0.25, 1/4
|
||||
-- 0.75, 3/4
|
||||
-- 0.125, 1/8
|
||||
-- 0.6875, 11/16
|
||||
-- 0.46875, 15/32
|
||||
-- 0.859375, 55/64
|
||||
-- 0.1171875, 15/128
|
||||
-- 0.55859375, 143/256
|
||||
-- 0.435546875] 223/512
|
||||
```
|
||||
|
||||
What does it mean that the initial enumeration comes back as a completely different one? Since the new one is "stable" with respect to applying the diagonal transform twice, are the results somehow preferable or significant?
|
||||
|
||||
|
||||
Closing
|
||||
-------
|
||||
|
||||
These questions, and others I have about the products of this process, I unfortunately have no answer for. I was mostly troubled by how rare it is to find people applying the diagonal argument to something to anything oh random sequences. Random sequences, either by choice or by algorithm (if you can even call it random at that point), are really only good to show how to *apply* the diagonal argument. Without knowing *what* the argument is applied to, it's useless as a tool. It also still remains to prove that your new sequences are sufficient for the purpose (and I've only shown a case in which it fails).
|
||||
|
||||
(Recycled) diagrams made in Geogebra. Downloadable Haskell code, including things from the previous post available [here]().
|
||||
363
permutations/appendix/index.qmd
Normal file
363
permutations/appendix/index.qmd
Normal file
@ -0,0 +1,363 @@
|
||||
---
|
||||
format:
|
||||
html:
|
||||
html-math-method: katex
|
||||
---
|
||||
|
||||
|
||||
Appendix: Partial Cayley Graphs of Coxeter Diagrams
|
||||
===================================================
|
||||
|
||||
This post is an appendix to [another post]() discussing the basics of Coxeter diagrams. It focuses on transforming path-like swap diagrams into proper $A_n$ Coxeter diagrams, which correspond to symmetric groups. This post focuses on the graphs made by the cosets made by removing a single generator (Coxeter diagram vertex).
|
||||
|
||||
For finite diagrams whose order is not prohibitively big, I will provide an embedding as a permutation group by labelling each generator in the Coxeter diagram. Since each generator is the product of disjoint swaps, I will also show their swap diagrams, as well as interactions via the edges.
|
||||
|
||||
|
||||
Platonic Symmetry
|
||||
-----------------
|
||||
|
||||
The symmetric group $S_n$ also happens to describe the symmetries of an $(n-1)$-dimensional simplex. The 3-simplex is simply a tetrahedron and has symmetry group $S_4$, also called $T_h$. We know that $S_4$ can be encoded by the diagram $A_3$. The string {3, 3} can be read across the edges of $A_3$, denoting the order of certain symmetries.
|
||||
|
||||
This happens to coincide with another description of the tetrahedron: its [Schläfli symbol](https://en.wikipedia.org/wiki/Schl%C3%A4fli_symbol). It describes triangles (the first 3) which meet in triples (the second 3) at a vertex. It may also be interpreted as the symmetry of the 2-dimensional components (faces) and the vertex-centered symmetry. [The Wikipedia article](https://en.wikipedia.org/wiki/Tetrahedron) on the tetrahedron presents both of these objects in its information column.
|
||||
|
||||
::: {}
|
||||
![]()
|
||||
|
||||
From the Wikipedia article on the tetrahedron. The Conway notation, Schläfli symbol, Coxeter diagram, and symmetry groups are shown.
|
||||
:::
|
||||
|
||||
The image above shows more sophisticated diagrams alongside $A_3$, which I will not attempt describing (mostly because I don't completely understand them myself). Other Platonic solids and their higher-dimensional analogues have different Schläfli symbols, and correspond to different Coxeter diagrams.
|
||||
|
||||
|
||||
### $B_3$: Octahedral Group
|
||||
|
||||
Adding an order-4 product into the mix makes things a lot more interesting. The cube ({4, 3}) and octahedron ({3, 4}) share a symmetry group, $O_h$, which corresponds to the Coxeter diagram $B_3$.
|
||||
|
||||
:::: {layout-ncol="3"}
|
||||
::: {}
|
||||
![]()
|
||||
:::
|
||||
|
||||
::: {}
|
||||
![]()
|
||||
:::
|
||||
|
||||
::: {}
|
||||
![]()
|
||||
:::
|
||||
::::
|
||||
|
||||
The third graph is entirely path-like, similar to the ones created by removing an endpoint from the $A_n$ diagrams. In the same vein, the graph for $B_3$ resembles the graph for $A_3$ made by removing the center vertex, albeit with two extra vertices. The center diagram is the first to have a hexagon created by removing a single vertex. Going across left to right, the order suggested by each index is
|
||||
|
||||
::: {}
|
||||
- $8 \cdot |A_2| = 8 \cdot 6 = 48$
|
||||
- $12 \cdot |A_1 A_1| = 12 \cdot 4 = 48$
|
||||
- $6 \cdot |B_2| = 6 \cdot 8 = 48$
|
||||
- $B_2$ describes the symmetry of a square, i.e., $Dih_4$, the dihedral group of order 8
|
||||
:::
|
||||
|
||||
Each diagram suggests the same order, which is good. A simple embedding which obeys the edge condition assigns $(1 ~ 2)(3 ~ 4)$ to *a*, $(2 ~ 3)$ to *b*, and $(1 ~ 2)$ to *c*. Then $ab = (1 ~ 2 ~ 4 ~ 3)$ and $bc = (1 ~ 3 ~ 2)$, with *ac* obviously having order 2.
|
||||
|
||||
There's a problem though. These generate a group of order 24 (actually, the rotational symmetries of a cube, $O \cong S_4 \cong T_h$). The group we want is $O_h \cong S_4 \times \mathbb{Z}_2$. If we want to embed a group of order 48 in a symmetric group, we need one for which 48 divides its order. $|S_6| = 720$ divides 48, and indeed, a quick fix is just to multiply each generator by $(5 ~ 6)$.
|
||||
|
||||
These two embeddings generate different (proper) Cayley graphs. The one for O has 24 vertices and is nonplanar. On the other hand, the one for $O_h$ is planar, and is the skeleton of the [truncated cuboctahedron](https://en.wikipedia.org/wiki/Truncated_cuboctahedron), a figure containing octagons, hexagons, and squares. This is exactly what is suggested by the orders of the products in the Coxeter diagram. Note also that the cuboctahedron is the rectification of the cube and octahedron, i.e., is midway between them with respect to the dual operation.
|
||||
|
||||
In either case, the products of adjacent generators (the permutations on the edges) are the same. When made into a Cayley graph, these products generate the [rhombicuboctahedron](https://en.wikipedia.org/wiki/Rhombicuboctahedron), which is another shape midway between the cube and octahedron. Since all of these generators are in $S_4$, it only has half the number of vertices as the truncated cuboctahedron.
|
||||
|
||||
:::: {layout-ncol="3"}
|
||||
::: {.column width="32%"}
|
||||
![]()
|
||||
|
||||
"Bad" embedding
|
||||
:::
|
||||
|
||||
::: {.column width="32%"}
|
||||
![]()
|
||||
|
||||
Good embedding
|
||||
:::
|
||||
|
||||
::: {.column width="32%"}
|
||||
![]()
|
||||
|
||||
Edge-generated
|
||||
:::
|
||||
::::
|
||||
|
||||
![]()
|
||||
|
||||
|
||||
### $H_3$: Icosahedral Group
|
||||
|
||||
Continuing with groups based on 3D shapes, the dodecahedron ({5, 3}) and icosahedron ({3, 5}) also share symmetry groups. It is known as $I_h$ and corresponds to Coxeter diagram $H_3$.
|
||||
|
||||
:::: {layout-ncol="3"}
|
||||
::: {.column width="32%"}
|
||||
![]()
|
||||
:::
|
||||
|
||||
::: {.column width="32%"}
|
||||
![]()
|
||||
:::
|
||||
|
||||
::: {.column width="32%"}
|
||||
![]()
|
||||
:::
|
||||
::::
|
||||
|
||||
Two of these graphs are similar to the cube/octahedron graphs. The other contains a decagon, corresponding to the order 5 product between *a* and *b*. We have the orders:
|
||||
|
||||
::: {}
|
||||
- $20 \cdot |A_2| = 20 \cdot 6 = 120$
|
||||
- Graph resembles an extension of $B_3 / A_1 A_1$, as hexagons joining blocks of squares
|
||||
- $30 \cdot |A_1 A_1| = 30 \cdot 4 = 120$
|
||||
- $12 \cdot |H_2| = 12 \cdot 10 = 120$
|
||||
- Graph resembles an extension of $B_3 / A_2$, as a single square joining paths
|
||||
:::
|
||||
|
||||
The order 120 is the same as the order of $S_5$, which corresponds to diagram $A_4$. However, these are not the same group, since $I_h \cong \text{Alt}_5 \times \mathbb{Z}_2 \ncong S_5$. A naive (though slightly less obvious) embedding, found similarly to $B_3$'s, incorrectly assigns the following:
|
||||
|
||||
::: {}
|
||||
- $a = (1 ~ 2)(3 ~ 4)$
|
||||
- $b = (2 ~ 3)(4 ~ 5)$
|
||||
- $c = (2 ~ 3)(1 ~ 4)$
|
||||
:::
|
||||
|
||||
This is certainly wrong, since all these permutations are within $S_5$. Actually, they are all even permutations and in fact generate $I \cong \text{Alt}_5$, with order 60. Yet again, multiplying $(6 ~ 7)$ to each vertex boosts the order to 120 and gives a proper embedding of $I_h$.
|
||||
|
||||
Similarly, the first, incorrect embedding gives a nonplanar Cayley graph. Correspondingly, the second one gives a planar graph, the skeleton of the [truncated icosidodecahedron](https://en.wikipedia.org/wiki/Truncated_icosidodecahedron). It consists of decagons, hexagons, and squares, just like those which appear in the graphs above. Again, note that icosidodecahedron is the rectification of the dodecahedron and icosahedron. In this case, the edges generate the [rhombicosidodecahedronal graph](https://en.wikipedia.org/wiki/Rhombicosidodecahedron).
|
||||
|
||||
:::: {layout-ncol="3"}
|
||||
::: {.column width="32%"}
|
||||
![]()
|
||||
|
||||
"Bad" embedding
|
||||
:::
|
||||
|
||||
::: {.column width="32%"}
|
||||
![]()
|
||||
|
||||
Good embedding
|
||||
:::
|
||||
|
||||
::: {.column width="32%"}
|
||||
![]()
|
||||
|
||||
Edge-generated
|
||||
:::
|
||||
::::
|
||||
|
||||
![]()
|
||||
|
||||
It is remarkable that the truncations of the rectifications (also called omnitruncation) have skeleta that are the same as the Cayley graphs generated by their respective Platonic solids' Coxeter diagrams. In a way, these figures describe their own symmetry. Notably, both of these figures belong to a class of polyhedra known as [zonohedra](https://en.wikipedia.org/wiki/Zonohedron).
|
||||
|
||||
|
||||
### $B_4$: Hyperoctahedral Group
|
||||
|
||||
Up a dimension from the cube and octahedron lie their 4D counterparts, the tesseract ({4, 3, 3}, interpreted as three cubes ({4, 3}) around an edge) and 16-cell ({3, 3, 4}). They correspond to Coxeter diagram $B_4$.
|
||||
|
||||
:::: {layout-ncol="2"}
|
||||
::: {.column width="49%"}
|
||||
![]()
|
||||
:::
|
||||
|
||||
::: {.column width="49%"}
|
||||
![]()
|
||||
:::
|
||||
::::
|
||||
|
||||
Three of these graphs are *also* similar to those encountered one dimension lower. The remaining graph is best understood in three dimensions, befitting the 4D symmetries it encodes. It appears to have similar regions to the [omnitruncated tesseract](https://en.wikipedia.org/wiki/Runcinated_tesseracts#Omnitruncated_tesseract), featuring both the truncated octahedron and hexagonal prism cells. We have the orders:
|
||||
|
||||
::: {}
|
||||
- $16 \cdot |A_3| = 16 \cdot 24 = 384$
|
||||
- Graph resembles an extension of $B_3 / A_2$, as squares connecting paths
|
||||
- $32 \cdot |A_1 A_2| = 32 \cdot 2 \cdot 6 = 384$
|
||||
- $24 \cdot |B_2 A_1| = 24 \cdot 8 \cdot 2 = 384$
|
||||
- Graph resembles an extension of $B_3 / A_1 A_1$, as hexagons joining blocks of squares
|
||||
- $8 \cdot |B_3| = 8 \cdot 48 = 384$
|
||||
- Graph resembles an extension of $B_3 / B_2$, a simple path
|
||||
:::
|
||||
|
||||
The order of the group, 384, suggests that it needs to be embedded in at least $S_8$ since $384 ~ \vert ~ 8! ~ ( = 40320)$. Indeed, such an embedding exists, found by computer search rather than by hand:
|
||||
|
||||
::: {}
|
||||
- $a = (1 ~ 3)$
|
||||
- $b = (1 ~ 2)(3 ~ 4)(5 ~ 6)(7 ~ 8)$
|
||||
- $c = (1 ~ 3)(2 ~ 6)(4 ~ 5)(7 ~ 8)$
|
||||
- $d = (1 ~ 3)(2 ~ 4)(5 ~ 7)(6 ~ 8)$
|
||||
:::
|
||||
|
||||
Notably, this embedding takes advantage of the possibility of the product of an order 2 and an order 4 element having order 4. A similar computer search yielded an insufficient embedding in $S_8$, with order 192:
|
||||
|
||||
::: {}
|
||||
- $a = (1 ~ 2)(3 ~ 4)(5 ~ 6)(7 ~ 8)$
|
||||
- $b = (1 ~ 3)(2 ~ 5)(4 ~ 7)(6 ~ 8)$
|
||||
- $c = (1 ~ 2)(3 ~ 4)(5 ~ 7)(6 ~ 8)$
|
||||
- $d = (1 ~ 2)(3 ~ 5)(4 ~ 6)(7 ~ 8)$
|
||||
:::
|
||||
|
||||
The latter embedding *cannot* be "fixed" by going to $S_{10}$, either by multiplying one or all elements by $(9 ~ 10)$. Only *a* is a viable choice for the former since its products with the rest of the generators have an order divisible by 2. Quickly "running" the generators shows that the order of the group is unchanged by this maneuver. Much of the structure permutations ensures that nonadjacent vertices still have order-2 products.
|
||||
|
||||
![]()
|
||||
|
||||
I won't try to identify either of these generating sets' Cayley graphs since they will in all likelihood correspond to a 4D object's skeleton and because it is impractical to try comparing graphs of this size. In fact, *H* appears to not be isomorphic to a subgroup of $W(B_4)$. The latter has at least 2 subgroups of order 192: one generated by the edges in the above embedding, and one containing only the even permutations. These are distinct from one another, since the number of elements of a particular order is different. The latter subgroup is closer to *H*, matching the number of elements of each order, but the even permutations have commutator subgroup has order 96 while *H* has a commutator subgroup of order 48.
|
||||
|
||||
|
||||
Other Finite Diagrams
|
||||
---------------------
|
||||
|
||||
Higher-dimensional Platonic solids are hardly the limits of what these diagrams can encode. The following three diagrams also give rise to finite graphs.
|
||||
|
||||
### $D_4$
|
||||
|
||||
$D_4$ is the first Coxeter diagram with a branch. Like $B_4$ before it, it is corresponds to the symmetries of a 4D object. We only really have two choices in which vertex to remove, which generate the following diagrams
|
||||
|
||||
:::: {layout-ncol="2"}
|
||||
::: {.column width="49%"}
|
||||
![]()
|
||||
:::
|
||||
|
||||
::: {.column width="49%"}
|
||||
![]()
|
||||
:::
|
||||
::::
|
||||
|
||||
While we could also remove *c* or *d*, this would just produce a graph identical to the one on the left, just with different labelling. The right diagram is rather interesting, as it can be described geometrically as two cubes attached to a triple of coaxial hexagons. Both of these cases give the order
|
||||
|
||||
::: {}
|
||||
- $8 \cdot |A_3| = 8 \cdot 24 = 192$
|
||||
- $24 \cdot |A_1 A_1 A_1| = 24 \cdot 8 = 192$
|
||||
:::
|
||||
|
||||
The order of 192 requires at minimum, $S_8$, since $192 ~ \vert ~ 40320$. Fortunately, a computer search yields a correct embedding immediately:
|
||||
|
||||
::: {}
|
||||
- $b = (1 ~ 2)(3 ~ 4)(5 ~ 6)(7 ~ 8)$
|
||||
- $a = (1 ~ 8)(2 ~ 3)(4 ~ 7)(5 ~ 6)$
|
||||
- $c = (1 ~ 5)(2 ~ 7)(3 ~ 4)(6 ~ 8)$
|
||||
- $d = (1 ~ 8)(2 ~ 4)(3 ~ 7)(5 ~ 6)$
|
||||
:::
|
||||
|
||||
::: {}
|
||||
![]()
|
||||
:::
|
||||
|
||||
In fact, the group generated by this diagram is isomorphic to the even permutation subgroup of $W(B_4)$. This can be verified by selecting order 2 elements from the latter which obey the laws in $D_4$. *H*, and the edge-generated subgroup, on the other hand do not satisfy this diagram.
|
||||
|
||||
|
||||
### $D_5$
|
||||
|
||||
D-type diagrams continue by elongating one of the paths. The next diagram, $D_5$ has really only four distinct graphs, of which I will show only two:
|
||||
|
||||
:::: {layout-ncol="2"}
|
||||
::: {.column width="49%"}
|
||||
![]()
|
||||
:::
|
||||
|
||||
::: {.column width="49%"}
|
||||
![]()
|
||||
:::
|
||||
::::
|
||||
|
||||
First, note how the graph to the right is generated by removing the vertex *e*, but the left and right sides of the diagram are asymmetrical. This is because *d* and *e* are equivalent with respect to the branch, and shows us that removing either vertex *d* or *e* results in the same graph. The order suggested by each is
|
||||
|
||||
::: {}
|
||||
- $10 \cdot |D_4| = 10 \cdot 192 = 1920$
|
||||
- $16 \cdot |A_4| = 16 \cdot 120 = 1920$
|
||||
:::
|
||||
|
||||
If we were to remove vertex b, we'd end up with the diagram $A_1 A_3$, which has order 48. The diagram would have 1920 / 48 = 40 vertices, which would be fairly difficult to render. Removing vertex c would be even worse, since the resulting diagram, $A_2 A_1 A_1$, has order 24, and would require 80 vertices, twice as many.
|
||||
|
||||
Finding an embedding at this point is difficult. The order of 1920 also divides $40320 = |S_8|$, but computer search has failed to find an embedding in up to $S_{11}$.
|
||||
|
||||
|
||||
### $E_6$
|
||||
|
||||
If one of the shorter paths of $D_5$ is extended, then we end up with the diagram $E_6$. I will only show one of its graphs.
|
||||
|
||||
![]()
|
||||
|
||||
Similarly to one of the graphs for $D_5$, this graph goes in with *a* and comes out with *e*, which are again symmetric with respect to the branch. I particularly like how on this graph, most of the squares are structured so that they can bridge to the *ae* commuting square in the middle.
|
||||
|
||||
The order of this group is $27 \cdot 1920 = 51840$, which is starting to be incomprehensible, if it hasn't been already. The graphs not shown would have the following number of vertices, which is precisely why I won't draw them out:
|
||||
|
||||
::: {}
|
||||
- Removing *f*: $[E_6 : A_5] = 51840 / 720 = 72$ vertices
|
||||
- Removing *b* or *d*: $[E_6 : A_1 A_4] = 51840 / 240 = 216$ vertices
|
||||
- Removing *c*: $[E_6 : A_2 A_2 A_1] = 51840 / 72 = 720$ vertices
|
||||
:::
|
||||
|
||||
Going by order alone, $E_6$ should embed in $S_9$ ($51840 ~ \vert ~ 9!$), but since $S_11$ was too small for its subgroup $D_5$, this too optimistic. I don't know what the minimum degree is required to embed $S_6$, but finding it directly it is beyond my computational power.
|
||||
|
||||
The E diagrams continue with $E_7$ and $E_8$. Each of the three corresponds to the symmetries of semi-regular higher-dimensional objects, whose significance and structure I can't even begin to comprehend. Their size alone makes continuing onward by hand a fool's errand, and I won't be attempting to draw them out right just now.
|
||||
|
||||
|
||||
Infinite (Affine) Diagrams
|
||||
--------------------------
|
||||
|
||||
Not every Coxeter diagram results in a closed graph. Instead, they may proliferate vertices forever. They are termed either "affine" or "hyperbolic", depending respectively on whether the diagrams join up with themselves or seem to require more and more room as the algorithm advances. This is also related to the collection of fundamental domains and roots that the diagram describes.
|
||||
|
||||
Since hyperbolic graphs are difficult to draw, I'll be restricting myself to the affine diagrams. Unlike hyperbolic diagrams, which are unnamed, affine ones are typically named by altering finite diagrams.
|
||||
|
||||
### $\widetilde A_2$
|
||||
|
||||
The Coxeter diagram associated to the triangle graph $K_3$ is called $\widetilde A_2$. This graph is also the line graph of $\bigstar_3$ so it might make sense to assign to the vertices the generators $(1 ~ 2)$, $(2 ~ 3)$, and $(1 ~ 3)$, which we know to generate $S_3$. However, $S_3$ already has a diagram, $A_2$, which is clearly a subdiagram of $\widetilde A_2$, so the new group must be larger.
|
||||
|
||||
![]()
|
||||
|
||||
Attempting to make a graph by following the generators results in an infinite tiling by hexagons. There are three distinct types of hexagon -- *ab*, *ac*, and *bc* -- since each pair of elements has a product of order 3. Removing a pair of vertices from the diagram would get rid of the initial edge (*a* in this case), and the "limiting case" where all three vertices are removed is just the true hexagonal tiling.
|
||||
|
||||
|
||||
### $\widetilde G_2$
|
||||
|
||||
The hexagonal tiling has a Schläfli symbol of {6, 3}, and is dual to the triangular tiling. As a Coxeter diagram, this symbol matches the Coxeter diagram $\widetilde G_2$.
|
||||
|
||||
![]()
|
||||
|
||||
The graph generated by removing a vertex is another infinite tiling, in this case the [truncated trihexagonal tiling](https://en.wikipedia.org/wiki/Truncated_trihexagonal_tiling). Once again, this tiling is the truncation of the rectification of the symmetry it typically describes. Each of the three figures corresponds to a unique pair of products: dodecagons to *ab*, squares to *ac*, and hexagons to *bc*.
|
||||
|
||||
|
||||
### $\widetilde C_2$
|
||||
|
||||
The only remaining regular 2D tiling is the square tiling ({4, 4}), whose Coxeter diagram is named $\widetilde C_2$.
|
||||
|
||||
![]()
|
||||
|
||||
The tiling generated by this diagram is known as the [truncated square tiling](https://en.wikipedia.org/wiki/Truncated_square_tiling). Mirroring the other cases, it is also the truncated *rectified* square tiling, since rectifying the square tiling merely rotates it by 45°. In this tiling, while the squares distinctly correspond to the product *ac*, the octagons are either order-4 product, *ab* or *bc*.
|
||||
|
||||
|
||||
### $\widetilde A_3$
|
||||
|
||||
The above diagrams are the only rank-2 affine diagrams. The simplest diagram of rank 3 is $\widetilde A_3$, which appears similar to 4-cyclic graph. Similar to how $\widetilde A_2$'s graph is the tiling of $A_2$'s, its Cayley graph is the honeycomb of $A_3$'s.
|
||||
|
||||
:::: {layout-ncol="2"}
|
||||
::: {.column width="49%"}
|
||||
![]()
|
||||
:::
|
||||
|
||||
::: {.column width="49%"}
|
||||
![]()
|
||||
:::
|
||||
::::
|
||||
|
||||
The left diagram shows the initial branches, while the left image shows the four distinct cells which form the honeycomb. From left to right, the diagrams contain only *abd*, *abc*, *bcd*, and *acd*. The squares always represent commuting pairs on opposite ends of the diagram: *ac* and *bd*.
|
||||
|
||||
I am not certain in general whether $\widetilde A_n$ generates the honeycomb formed by $A_n$, but this tessellation should always exist, since the solids formed by the generators of $A_n$ are always permutohedra.
|
||||
|
||||
|
||||
Closing
|
||||
-------
|
||||
|
||||
The diagrams I have studied here are only the smaller ones which I can either visualize or compute. I might have gotten carried away with studying the groups themselves in the 4D case, since there appears to be so much contention. Despite this, I examined only half of the available Platonic solids in 4D, missing out on the 24-cell ({3, 4, 3}, $F_4$), 120-cell ({5, 3, 3}, $H_4$), and 600-cell ({3, 3, 5}, $H_4$). If 4D symmetries are hard to understand, then things can only get worse in higher dimensions.
|
||||
|
||||
One of the things I'm curious about is the minimal degree of symmetric group needed to embed the finite diagrams (known as $\mu$). While $S_6$ is minimal for the octahedral group, it doesn't appear to be big enough for the icosahedral one. Certain groups obey $\mu(G \times H) = \mu(G) + \mu(H)$ (typically abelian ones), but I'm not sure whether that's the case here.
|
||||
|
||||
Coset and embedding diagrams made with GeoGebra. Cayley graph images made with NetworkX (GraphViz).
|
||||
|
||||
|
||||
### Additional Links
|
||||
|
||||
- [Point groups in three dimensions](https://en.wikipedia.org/wiki/Point_groups_in_three_dimensions) (Wikipedia)
|
||||
- [Point groups in four dimensions](https://en.wikipedia.org/wiki/Point_groups_in_four_dimensions) (Wikipedia)
|
||||
- [Finding the minimal n such that a given finite group G is a subgroup of Sn](https://math.stackexchange.com/questions/1597347/finding-the-minimal-n-such-that-a-given-finite-group-g-is-a-subgroup-of-s-n) (Mathematics Stack Exchange)
|
||||
- [Omnitruncated](https://community.wolfram.com/groups/-/m/t/774393), by Clayton Shonkwiler
|
||||
405
stereo/1/index.qmd
Normal file
405
stereo/1/index.qmd
Normal file
@ -0,0 +1,405 @@
|
||||
---
|
||||
format:
|
||||
html:
|
||||
html-math-method: katex
|
||||
---
|
||||
|
||||
|
||||
Algebraic Rotations and Stereography
|
||||
====================================
|
||||
|
||||
Expressing rotation mathematically can be rather challenging, even in two dimensional space. Typically the subject is approached from complicated-looking rotation matrices, with the occasional accompanying comment about complex numbers. The challenge only increases when examining three dimensional space, where everyone has different ideas about the best implementations. In what follows, I attempt to provide a derivation of 2D and 3D rotations based in intuition and without the use of trigonometry.
|
||||
|
||||
|
||||
Complex Numbers: 2D Rotations
|
||||
-----------------------------
|
||||
|
||||
Multiplication between complex numbers is frequently analyzed as a combination of scaling (a ratio of two scales) and rotation (a point on the complex unit circle). However, the machinery used in these analyses usually expresses complex numbers in a polar form involving the complex exponential, concepts which leverage knowledge of trigonometry and some calculus. In fact, they obfuscate the algebraic properties of complex numbers when neither concept is truly necessary to understand points on the complex unit circle, or complex number multiplication in general.
|
||||
|
||||
|
||||
### A review of complex multiplication
|
||||
|
||||
A complex number $z = a + bi$ multiplies with another complex number $w = c + di$ in the obvious way: by distributing over addition.
|
||||
|
||||
$$
|
||||
zw = (a+bi)(c+di) = ac + bci + adi + bdi^2 = (ac -\ bd) + i(ad + bc)
|
||||
$$
|
||||
|
||||
*z* also has associated to it a conjugate $z^* = a -\ bi$. The product $zz^*$ is the *norm* $a^2 + b^2$, a positive real quantity whose square root is typically called the *magnitude* of the complex number. Taking the square root is frequently unnecessary, as many of the properties of this quantity do not depend on this normalization. One such property is that for two complex numbers *z* and *w*, the norm of the product is the product of the norm.
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
(zw)(zw)^* &= (ac -\ bd)^2 + (ad + bc)^2 \\
|
||||
&= a^2c^2 -\ 2abcd + b^2d^2 + a^2d^2 + 2abcd + b^2c^2 \\
|
||||
&= a^2c^2 + b^2d^2 + a^2d^2 + b^2c^2 = a^2(c^2 + d^2) + b^2(c^2 + d^2) \\
|
||||
&= (a^2 + b^2)(c^2 + d^2)
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
Conjugation also distributes over multiplication, and complex numbers possess multiplicative inverses. Also, the norm of the multiplicative inverse is the inverse of the norm.
|
||||
|
||||
$$
|
||||
\begin{gather*}
|
||||
z^*w^* = (a -\ bi)(c -\ di) = ac -\ bci -\ adi + bdi^2 \\
|
||||
= (ac -\ bd) -\ i(ad + bc) = (zw)^* \\
|
||||
{1 \over z} = {z^* \over zz^*} = {a -\ bi \over a^2 + b^2} \\
|
||||
\left({1 \over z}\right)
|
||||
\left({1 \over z}\right)^*
|
||||
= {1 \over zz^*} = {1 \over a^2 + b^2} \\
|
||||
\end{gather*}
|
||||
$$
|
||||
|
||||
A final interesting note about complex multiplication is the product $z^*w$
|
||||
|
||||
$$
|
||||
z^* w = (a -\ bi)(c + di) = ac -\ bci + adi -\ bdi^2 = (ac + bd) + i(ad -\ bc)
|
||||
$$
|
||||
|
||||
The real part is the same as the dot product $(a, c) \cdot (b, d)$ and the imaginary part looks like the determinant of $\begin{pmatrix}a & b \\ c & d\end{pmatrix}$. This shows the inherent value of complex arithmetic, as both of these quantities have important interpretations in vector algebra.
|
||||
|
||||
|
||||
### Onto the Circle
|
||||
|
||||
If $zz^* = a^2 + b^2 = 1$, then *z* lies on the complex unit circle. Since the norm is 1, a general complex number *w* has the same norm as the product *zw*. Thus, multiplication by *z* results in the rotation that maps 1 to *z*. But how do you get such a point on the unit circle?
|
||||
|
||||
Simple. There are four complex numbers directly related to any complex number *w* that are guaranteed to share its norm: $w, -w, w^*$, and $-w^*$. Therefore, dividing any of these numbers by any other of these numbers results in a complex number on the unit circle. Division by -*w* is boring and just results in -1. However, division by $w^*$ turns out to be important:
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
z &= {w \over w^*} =
|
||||
\left( {a + bi \over a -\ bi} \right) \left( {a + bi \over a + bi} \right) = 1 \\[8pt]
|
||||
&= {(a^2 -\ b^2) + (2ab)i \over a^2 + b^2}
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
Dividing the numerator and denominator of this expression through by $a^2$ yields an expression in $t = b/a$, where t can be interpreted as the slope of the line joining 0 and *w*.
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
{(a^2 -\ b^2) + (2ab)i \over a^2 + b^2}
|
||||
&= {1/a^2 \over 1/a^2} \cdot {(a^2 -\ b^2) + (2ab)i \over a^2 + b^2} \\
|
||||
&= {(1 -\ b^2/a^2) + (2b/a)i \over 1 + b^2/a^2}
|
||||
= {(1 -\ t^2) + (2t)i \over 1 + t^2} \\
|
||||
&= {1 -\ t^2 \over 1 + t^2} + i{2t \over 1 + t^2} = z(t)
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
Dividing the numerator and denominator of this expression through by $a^2$ yields an expression in $t = b/a$, where *t* can be interpreted as the slope of the line joining 0 and *w*.
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
{(a^2 -\ b^2) + (2ab)i \over a^2 + b^2}
|
||||
&= {1/a^2 \over 1/a^2} \cdot {(a^2 -\ b^2) + (2ab)i \over a^2 + b^2} \\
|
||||
&= {(1 -\ b^2/a^2) + (2b/a)i \over 1 + b^2/a^2}
|
||||
= {(1 -\ t^2) + (2t)i \over 1 + t^2} \\
|
||||
&= {1 -\ t^2 \over 1 + t^2} + i{2t \over 1 + t^2} = z(t)
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
Slopes range from $-\infty$ to $\infty$, which means that the variable *t* does as well. Moreover, if *z* is on the unit circle, then its reciprocal is:
|
||||
|
||||
$$
|
||||
{1 \over z} = {z^* \over zz^*} = z^*
|
||||
$$
|
||||
|
||||
In other words, for points already on the unit circle, $z / z^* = z^2$ describes the rotation from 1 to $z^2$. This demonstrates the decomposition of the rotation into two parts. For a general complex number *w* which may not lie on the unit circle, multiplication by this number dilates the complex plane as well rotating 1 toward the direction of *w*. Then, division by $w^*$ undoes the dilation and performs the rotation again. The proper description of the rotation described by $w/w^*$ is the combination of these half steps.
|
||||
|
||||
|
||||
### Two Halves, Twice Over
|
||||
|
||||
Plugging in some values for t reveals something else: $z(0) = 1 + 0i$, $z(1) = 0 + 1i$, and $z(-1) = 0 -\ 1i$. This shows that the semicircle in the right half-plane exists where $t \in (-1, 1)$. The other half of the circle comes from *t* with magnitude greater than 1. The point where $z(t) = -1$ does not properly exist unless it is allowed that $t = \pm \infty$.
|
||||
|
||||
Is it possible to get the other half into the nicer interval? No problem, just double the rotation.
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
z(t)^2 &= (\Re[z]^2 -\ \Im[z]^2) + i(2\Re[z]\Im[z]) \\[8pt]
|
||||
&= {(1 -\ t^2)^2 -\ (2t)^2 \over (1 + t^2)^2} + i{2(1 -\ t^2)(2t) \over (1 + t^2)^2} \\[8pt]
|
||||
&= {1 -\ 6t^2 + t^4 \over 1 + 2t^2 + t^4}
|
||||
+i {4t -\ 4t^3 \over 1 + 2t^2 + t^4} \\
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
Now the entire circle fits in the interval $t \in (-1, 1)$, and $z(-1)^2 = z(1)^2 = -1$. This action of squaring *z* means that as *t* ranges from $-\infty$ to $\infty$, the point $z^2$ loops around the circle twice, since the rotation *z* is applied twice.
|
||||
|
||||
I'd now like to go on a brief tangent and compare these expressions to their transcendental trigonometric counterparts. Graphing the real and imaginary parts separately shows how much they resemble $\cos(\pi t)$ and $\sin(\pi t)$ around 0.
|
||||
|
||||
:::: {layout-ncol = "2"}
|
||||
::: {}
|
||||
![]()
|
||||
:::
|
||||
|
||||
::: {}
|
||||
![]()
|
||||
:::
|
||||
::::
|
||||
|
||||
Incidentally, an approximation of a function as the ratio of two polynomials is called a [Padé approximant](https://en.wikipedia.org/wiki/Pad%C3%A9_approximant). Specifically, the real part (which approximates cosine) has order \[4/4\] and the imaginary part (which approximates sine) has order \[3/4\]. Ideally, the zeroes of the real part should occur at $t = \pm 1/2$, since $\cos(\pm \pi/2) = 0$. Instead, they occur at
|
||||
|
||||
$$
|
||||
0 = 1 -\ 6t^2 + t^4 = (t^2 -\ 2t -\ 1)(t^2 + 2t -\ 1)
|
||||
\implies t = \pm {1 \over \delta_s} = \pm(\sqrt 2 -\ 1) \approx \pm0.414
|
||||
$$
|
||||
|
||||
With a bit of polynomial interpolation, this can be rectified. A cubic interpolation is given by:
|
||||
|
||||
$$
|
||||
\begin{gather*}
|
||||
P(x) = ax^3 + bx^2 + cx + d \\
|
||||
\begin{pmatrix}
|
||||
1 & 0 & 0 & 0 \\
|
||||
1 & 1/2 & 1/4 & 1/8 \\
|
||||
1 & -1/2 & 1/4 & -1/8 \\
|
||||
1 & 1 & 1 & 1 \\
|
||||
\end{pmatrix} \begin{pmatrix}
|
||||
d \\ c \\ b \\ a
|
||||
\end{pmatrix} = \begin{pmatrix}
|
||||
0 \\ \sqrt 2 -\ 1 \\ -\sqrt 2 + 1 \\ 1
|
||||
\end{pmatrix} \\
|
||||
\implies \begin{pmatrix}
|
||||
d \\ c \\ b \\ a
|
||||
\end{pmatrix} = \begin{pmatrix}
|
||||
0 \\ -3 + 8\sqrt 2/3 \\ 0 \\ 4 -\ 8\sqrt 2/3 \end{pmatrix}
|
||||
\implies P(x) \approx 0.229 x^3 + 0.771x
|
||||
\end{gather*}
|
||||
$$
|
||||
|
||||
:::: {layout-ncol = "2"}
|
||||
::: {}
|
||||
![]()
|
||||
:::
|
||||
|
||||
::: {}
|
||||
![]()
|
||||
:::
|
||||
The error over the interval [-1, 1] in these approximations is around 1% (RMS).
|
||||
::::
|
||||
|
||||
Notably, this interpolating polynomial is entirely odd, which gives it some symmetry about 0. Along with being a very good approximation, it has the feature that a rotation by an *n*^th^ of a turn is about $z(P(1/n))^2$. The approximation be improved further by taking *z* to higher powers and deriving another interpolating polynomial.
|
||||
|
||||
It is impossible to shrink this error to 0 because the derivative of the imaginary part at $t = 1$ would be $\pi$ with no error. But the imaginary part is a rational expression (technically with the interpolating polynomial, a ratio of two irrational numbers), so its derivative is also a rational expression. Since $\pi$ is transcendental, there must always be some error.
|
||||
|
||||
Even though it is arguably easier to interpret, there probably isn't a definite benefit to this approximation. For example,
|
||||
|
||||
- Padé approximants can be calculated directly from the power series for sine and cosine.
|
||||
- There are no inverse functions like `acos` or `asin` to complement these approximations.
|
||||
- This isn't that bad, since I have refrained from describing rotations with an angle. Even so, the inverse functions have their use cases.
|
||||
- Trigonometric functions can be hardware-accelerated (at least in some FPUs).
|
||||
- If no such hardware exists, approximations of `sin` and `cos` can be calculated by software beforehand and stored in a lookup table (which is also probably involved at some stage of the FPU).
|
||||
|
||||
Elaborating on the latter two points, using a lookup table means evaluation happens in roughly constant time. A best-case analysis of the above approximation, given some value *t* is
|
||||
|
||||
$$
|
||||
\begin{matrix}
|
||||
\text{Expression} & \text{Additions} & \text{Multiplications} & \text{Divisions} \\ \hline
|
||||
p = t \cdot (0.228763834 \cdot t \cdot t + 0.771236166) &
|
||||
1 & 3 \\[4pt]
|
||||
q = p \cdot p &
|
||||
& 1 \\[4pt]
|
||||
r = 1 + q &
|
||||
1 \\[4pt]
|
||||
c = {1 -\ q \over r} &
|
||||
1 & & 1 \\[4pt]
|
||||
s = {2 \cdot p \over r} &
|
||||
2 & 1 & 1 \\[4pt]
|
||||
real = c \cdot c -\ s \cdot s &
|
||||
1 & 2 \\[4pt]
|
||||
imag = 2 \cdot c \cdot s &
|
||||
& 2 \\
|
||||
\hline
|
||||
\Sigma &
|
||||
6 & 9 & 2 \\
|
||||
\end{matrix}
|
||||
$$
|
||||
|
||||
Or 17 FLOPs. On a more optimistic note, a Monte Carlo test of the above approximation on my computer yields promising results when compared with the GCC `libmath` implementations of sin and cos (also tested with `cexp`).
|
||||
|
||||
```{}
|
||||
Timing for 100000 math.h sin and cos: 5409619ns
|
||||
Timing for 100000 approximations: 2408645ns
|
||||
Approximation faster, speedup: 3000974ns (2.25x)
|
||||
Squared error in cosines:
|
||||
Average: 0.000051 (0.713743% error)
|
||||
Largest: 0.000174 (1.320551% error)
|
||||
Input: 0.729202
|
||||
Value: -0.659428
|
||||
Approximation: -0.672634
|
||||
Squared error in sines:
|
||||
Average: 0.000070 (0.835334% error)
|
||||
Largest: 0.000288 (1.698413% error)
|
||||
Input: 0.729202
|
||||
Value: 0.475669
|
||||
Approximation: 0.458685
|
||||
```
|
||||
|
||||
For the source which I used to generate this output, see the repository linked below.
|
||||
|
||||
Even though results can be inaccurate, this exercise in the algebraic manipulation of complex numbers is fairly interesting since it requires no calculus to define, unlike sine, cosine, and their Padé approximants (and to a degree, $\pi$).
|
||||
|
||||
|
||||
From Circles to Spheres
|
||||
-----------------------
|
||||
|
||||
The expression for complex points on the unit circle coincides with the stereographic projection of a circle. This method is achieved by selecting a point on the circle, fixing *t* along a line (such as the *y*-axis), and letting *t* range over all possible values.
|
||||
|
||||
![]()
|
||||
|
||||
The equations of the line and circle appear in the diagram above. Through much algebra, expressions for *x* and *y* in *t* can be formed.
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
y &= t(x + 1) \\
|
||||
1 &= x^2 + y^2 = x^2 + (tx + t)^2 \\
|
||||
&= x^2 + t^2x^2 + 2t^2 x + t^2 \\
|
||||
{1 \over t^2 + 1} -\ {t^2 \over t^2 + 1} &=
|
||||
x^2 + {t^2 \over t^2 + 1} 2x \\
|
||||
{1 -\ t^2 \over t^2 + 1} + \left( {t^2 \over t^2 + 1} \right)^2 &=
|
||||
\left(x + {t^2 \over t^2 + 1} \right)^2 + {t^2 \over t^2 + 1} \\
|
||||
{1 -\ t^4 \over (t^2 + 1)^2} + {t^4 \over (t^2 + 1)^2} &=
|
||||
\left(x + {t^2 \over t^2 + 1} \right)^2 \\
|
||||
{1 \over (t^2 + 1)^2} &=
|
||||
\left(x + {t^2 \over t^2 + 1} \right)^2 \\
|
||||
{1 \over t^2 + 1} &=
|
||||
x + {t^2 \over t^2 + 1} \\
|
||||
x &= {1 -\ t^2 \over 1 + t^2} \\
|
||||
y &= t(x + 1) =
|
||||
t\left( {1 -\ t^2 \over 1 + t^2} + {1 + t^2 \over 1 + t^2} \right) \\
|
||||
&= {2t \over 1 + t^2}
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
These are exactly the same expressions which appear in the real and imaginary components of the ratio between $1 + ti$ and its conjugate. Compared to the algebra using complex numbers, this is quite a bit more work. Therefore, one might also ask whether, given a proper arithmetic setting, the projection of a sphere might be obtained in the same manner as the circle with respect to complex numbers.
|
||||
|
||||
::: {}
|
||||
![]()
|
||||
What we are not doing: intersecting the sphere with a line through a pole and a point in the xy plane
|
||||
:::
|
||||
|
||||
|
||||
### Algebraic Projection
|
||||
|
||||
The simplest thing to do is try it out. Let's say an extended number $h = 1 + si + tj$ has a conjugate of $h^* = 1 -\ si -\ tj$. It's a bit of an assumption to be able to take the reciprocal of the latter (we don't know whether *i* and *j* create a field or division ring), but following our noses:
|
||||
|
||||
$$
|
||||
\begin{gather*}
|
||||
{h \over h^*} = {1 + si + tj \over 1 -\ si -\ tj} =
|
||||
\left({1 + si + tj \over 1 -\ si -\ tj}\right)
|
||||
\left({1 + si + tj \over 1 + si + tj}\right) = \\[8pt] {
|
||||
1 + si + tj + si + s^2i^2 + stij + tj + stji + t^2j^2
|
||||
\over
|
||||
1 -\ si -\ tj + si -\ s^2 i^2 -\ stij + tj -\ stji -\ t^2j^2
|
||||
}
|
||||
\end{gather*}
|
||||
$$
|
||||
|
||||
While there is some cancellation in the denominator, both products are rather messy. To get more cancellation, we apply some nice algebraic properties between *i* and *j*. For the $stij$ terms to cancel, we let i and j be *anticommutative*, meaning that $ij = -ji$. So that the denominator is totally real (and therefore can be guaranteed to divide), we also apply the constraint that $i^2$ and $j^2$ are both real. Then the expression becomes
|
||||
|
||||
$$
|
||||
{1 + si + tj \over 1 -\ si -\ tj} =
|
||||
{1 + s^2i^2 + t^2j^2 \over 1 -\ s^2 i^2 -\ t^2j^2} +
|
||||
i{2s \over 1 -\ s^2 i^2 -\ t^2j^2} +
|
||||
j{2t \over 1 -\ s^2 i^2 -\ t^2j^2}
|
||||
$$
|
||||
|
||||
If it is chosen that $i^2 = j^2 = -1$, this produces the correct equations ([Wikipedia](https://en.wikipedia.org/wiki/Stereographic_projection#First_formulation)) parametrizing the unit sphere. One can also check that the squares of the real, *i*, and *j* components sum to 1.
|
||||
|
||||
If both *i* and *j* anticommute, then their product also anticommutes with both *i* and *j*.
|
||||
|
||||
$$
|
||||
\textcolor{red}{ij}j = -j\textcolor{red}{ij} ~,~
|
||||
i\textcolor{red}{ij} = -\textcolor{red}{ij}i
|
||||
$$
|
||||
|
||||
Calling this product *k* and noticing that $k^2 = (ij)(ij) = -ijji = i^2 = -1$, this completely characterizes the [quaternions](https://en.wikipedia.org/wiki/Quaternion).
|
||||
|
||||
Choosing different values for $i^2$ and $j^2$ yield different shapes than a sphere. If you know a little group theory, you might know there are only two nonabelian (noncommutative) groups of order 8: the [quaternion group](https://en.wikipedia.org/wiki/Quaternion_group) and the [dihedral group of degree 4](https://en.wikipedia.org/wiki/Examples_of_groups#dihedral_group_of_order_8). In the latter group, *j* and *k* are both imaginary, but square to 1 (*i* still squares to -1). I'm being a bit careless with the meanings of "1" and "-1" here, but these objects come from the additive structure of the algebra, rather than the multiplicative.
|
||||
|
||||
Changing the sign of one (or both) of the imaginary squares in the expression $h/h^*$ above switches the multiplicative structure from quaternions to the dihedral group. In this group, picking $i^2 = -j^2 = -1$ parametrizes a hyperboloid of one sheet, and picking $i^2 = j^2 = 1$ parametrizes a hyperboloid of two sheets.
|
||||
|
||||
|
||||
Quaternions and Rotation
|
||||
------------------------
|
||||
|
||||
Complex numbers are useful for describing 2D rotations and elegantly describe the stereographic projection of a circle. Consequently, since quaternions elegantly describe the stereographic projection of a sphere, they are useful for 3D rotations.
|
||||
|
||||
Since the imaginary units *i*, *j*, and *k* are anticommutative, a general quaternion does not commute with other quaternions like complex numbers do. This embodies a difficulty with 3D rotations in general: unlike 2D rotations, they do not commute.
|
||||
|
||||
Only three of the four components in a quaternion are necessary to describe a point in 3D space. The three imaginary axes are symmetric in some way (i.e., they all square to -1), so we use the *ijk* subspace. Quaternions in this subspace are called *vectors*. Another reason for using the *ijk* subspace comes from considering a point $u = ai + bj + ck$ on the unit sphere ($a^2 + b^2 + c^2 = 1$). Then the square of this point is:
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
u^2 &= (ai + bj + ck)(ai + bj + ck) \\
|
||||
&= a^2i^2 + abij + acik + abji + b^2j^2 + bcjk + acki + bckj + c^2k^2 \\
|
||||
&= (a^2 + b^2 + c^2)(-1) + ab(ij + ji) + ac(ik + ki) + bc(jk + kj) \\
|
||||
&= -1 + 0 + 0 + 0
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
This property means that *u* behaves similarly to a typical imaginary unit. If we form pseudo-complex numbers of the form $a + b u$, then ${1 + tu \over 1 -\ tu} = \alpha + \beta u$ specifies *some kind of rotation* in terms of the parameter t. Identically as with complex numbers, the inverse rotation is the conjugate $1 -\ tu \over 1 + tu$.
|
||||
|
||||
Another useful feature of quaternion algebra deals with symmetry transformations of the imaginary units. If an imaginary unit (one of *i*, *j*, *k*) is left-multiplied by a quaternion $q$ and right-multiplied by its conjugate $q^*$, then the result is still imaginary. In other words, if p is a vector, then $qpq^*$ is also a vector since *i*, *j*, and *k* form a basis and multiplication distributes over addition. I will not demonstrate this fact, as it requires a large amount of algebra.
|
||||
|
||||
These two features combine to characterize a transformation for a vector quaternion. For a vector *p*, if $q_u(t)$ is a "rotation" as above for a point *u* on the unit sphere, then another vector (the image of *p*) can be described by
|
||||
|
||||
$$
|
||||
\begin{gather*}
|
||||
qpq^* = qpq^{-1}
|
||||
= \left({1 + tu \over 1 -\ tu}\right) p \left({1 -\ tu \over 1 + tu}\right)
|
||||
= (\alpha + \beta u)p(\alpha -\ \beta u) \\[4pt]
|
||||
= (\alpha + \beta u)(\alpha p -\ \beta pu)
|
||||
= \alpha^2 p -\ \alpha \beta pu + \alpha \beta up -\ \beta^2 upu
|
||||
\end{gather*}
|
||||
$$
|
||||
|
||||
|
||||
Testing a Transformation
|
||||
------------------------
|
||||
|
||||
If this transformation is a rotation, then it should recreate the 2D case which can be described more simply by complex numbers. For example, if $u = k$ and $p = xi + yj$, then this can be expanded as:
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
qpq^{-1} &= (\alpha + \beta k)(xi + yj)(\alpha -\ \beta k) \vphantom{\over} \\
|
||||
&= \alpha^2(xi + yj) -\ \alpha \beta (xi + yj)k + \alpha \beta k(xi + yj) -\ \beta^2 k(xi + yj)k
|
||||
\vphantom{\over} \\
|
||||
&= \alpha^2(xi + yj) -\ \alpha \beta(-xj + yi) + \alpha \beta(xj -\ yi) -\ \beta^2(xi + yj)
|
||||
\vphantom{\over} \\
|
||||
&= (\alpha^2 -\ \beta^2)(xi + yj) + 2\alpha \beta(xj - yi)
|
||||
\vphantom{\over} \\
|
||||
&= [(\alpha^2 -\ \beta^2)x -\ 2\alpha \beta y]i + [(\alpha^2 -\ \beta^2)y + 2\alpha \beta x]j
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
If *p* is rewritten as a vector (in the linear algebra sense), then this final expression can be rewritten as a linear transformation of *p*:
|
||||
|
||||
$$
|
||||
\begin{pmatrix}
|
||||
(\alpha^2 -\ \beta^2)x -\ 2 \alpha \beta y \\
|
||||
(\alpha^2 -\ \beta^2)y + 2 \alpha \beta x
|
||||
\end{pmatrix} =
|
||||
\begin{pmatrix}
|
||||
\alpha^2 -\ \beta^2 & -2\alpha \beta \\
|
||||
2\alpha \beta & \alpha^2 -\ \beta^2
|
||||
\end{pmatrix}
|
||||
\begin{pmatrix} x \\ y \end{pmatrix}
|
||||
$$
|
||||
|
||||
The matrix which appears above is the square of the matrix $\begin{pmatrix}\alpha & -\beta \\ \beta & \alpha \end{pmatrix}$. This is isomorphic to the complex number $\alpha + \beta i$. Since $\alpha$ and $\beta$ were chosen so to lie on the (pseudo-) complex unit circle, this means that the vector (*x*, *y*) is rotated by $\alpha + \beta i$ twice. This also demonstrates that u specifies the axis of rotation, or equivalently, the plane of a great circle. This means that the "some kind of rotation" specified by $q = {1 + tu \over 1 -\ tu}$ is a rotation around the great circle normal to *u*.
|
||||
|
||||
The double rotation resembles the half-steps of rotation in the complex numbers, where $1 + ti$ performs a dilation, ${1 \over 1 -\ ti}$ reverses it, and together they describe a rotation. Since quaternions do not commute, sandwiching *p* between *q* and its conjugate makes the algebra simpler. The product of a vector with itself should be totally real, as shown above (and equal to the negative of the norm of *p*, the sum of the squares of its components). The rotated *p* shares the same norm as *p*, so it should equal $p^2$. Indeed, this is the case, as
|
||||
|
||||
$$
|
||||
(qpq^{-1})^2 = (qpq^{-1})(qpq^{-1}) = q p p q^{-1} = p^2 q q^{-1} = p^2
|
||||
$$
|
||||
|
||||
As a final remark, due to rotation through quaternions being doubled, the interpolating polynomial used above to smooth out double rotations can also be used here. That is, $q_u(P(t))pq_u^{-1}(P(t))$ rotates *p* by (approximately) the fraction of a turn specified by t through the great circle which intersects the plane normal to *u*.
|
||||
|
||||
|
||||
Closing
|
||||
-------
|
||||
|
||||
It is difficult to find a treatment of rotations which does not hesitate to use the complex exponential $e^{ix} = \cos(x) + i \sin(x)$. The true criterion for rotations is simply that a point lies on the unit circle. Perhaps this contributes to why understanding the 3D situation with quaternions is challenging for many. Past the barrier to entry, I believe them to be rather intuitive. I have outlined some futher benefits of this approach in this post.
|
||||
|
||||
As a disclaimer, this post was inspired by [this video](https://www.youtube.com/watch?v=d4EgbgTm0Bg) by 3blue1brown on YouTube (at least subconsciously; I had not seen the video in years before checking that I wasn't just plagiarizing it). It *also* uses stereographic projections of the circle and sphere to describe rotations in the complex plane and quaternion vector space. However, I feel like it fails to provide an algebraic motivation for quaternions, or even stereography in the first place. Hopefully, my remarks on the algebraic approach can be used to augment the information in the video.
|
||||
|
||||
Diagrams created with GeoGebra. Repository with approximations (as well as GeoGebra files) available [here](https://github.com/queue-miscreant/approx-trig).
|
||||
411
stereo/2/index.qmd
Normal file
411
stereo/2/index.qmd
Normal file
@ -0,0 +1,411 @@
|
||||
---
|
||||
format:
|
||||
html:
|
||||
html-math-method: katex
|
||||
---
|
||||
|
||||
|
||||
Further Notes on Algebraic Stereography
|
||||
=======================================
|
||||
|
||||
In my previous post, I discussed the stereographic projection of a circle as it pertains to complex numbers, as well as its applications in 2D and 3D rotation. In an effort to document more interesting facts about this mathematical object (of which scarce information is immediately available online), I will now elaborate on more of its properties.
|
||||
|
||||
|
||||
Chebyshev Polynomials
|
||||
---------------------
|
||||
|
||||
[Previously](), I derived the [Chebyshev polynomials](https://en.wikipedia.org/wiki/Chebyshev_polynomials) with the archetypal complex exponential. These polynomials express the sines and cosines of a multiple of an angle from the sine and cosine of the base angle.
|
||||
|
||||
$$
|
||||
\begin{gather*}
|
||||
\cos(n\theta) = T_n(\cos(\theta)) \\
|
||||
\sin(n\theta) = U_{n-1}(\cos(\theta)) \sin(\theta)
|
||||
\end{gather*}
|
||||
$$
|
||||
|
||||
Where $T_n(t)$ are Chebyshev polynomials of the first kind and $U_n(t)$ are those of the second kind. The complex exponential derivation begins by squaring and developing a second-order recurrence.
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
(e^{i\theta})^2 &= (\cos + i\sin)^2 \\
|
||||
&= \cos^2 + 2i\cos \cdot \sin -\ \sin^2 + (0 = \cos^2 + \sin^2 -\ 1) \\
|
||||
&= 2\cos^2 + 2i\cos \cdot \sin -\ 1 \\
|
||||
&= 2\cos \cdot (\cos + i\sin) -\ 1 \\
|
||||
&= 2\cos(\theta)e^{i\theta} -\ 1 \\
|
||||
(e^{i\theta})^{n+2} &= 2\cos(\theta)(e^{i\theta})^{n+1} -\ (e^{i\theta})^n
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
This recurrence relation can then be used to obtain the Chebyshev polynomials, and hence, the expressions using sine and cosine above. Presented this way with such a simple derivation, it appears as though these relationships are inherently trigonometric. However, these polynomials actually have *nothing* to do with sine and cosine. For one, they appear in graph theory, and for two, the importance of the complex exponential is overstated.
|
||||
|
||||
$e^{i\theta}$ really just specifies a point on the complex unit circle. This property is used on the second line to coax the equation into a quadratic in $e^{i\theta}$. This is also the *only* property upon which the recurrence depends; all else is algebraic manipulation.
|
||||
|
||||
|
||||
### Back to the Stereograph
|
||||
|
||||
Knowing this, let's start over with the stereographic projection of the circle:
|
||||
|
||||
$$
|
||||
o_1(t) = {1 + it \over 1 -\ it}
|
||||
= {1 -\ t^2 \over 1 + t^2} + i {2t \over 1 + t^2}
|
||||
= \text{c}_1 + i\text{s}_1
|
||||
$$
|
||||
|
||||
The subscript "1" is because as *t* ranges over $(-\infty, \infty)$, a single loop around the circle is made. Thanks to the properties of complex numbers, it is impossible to grow the norm by exponentiating, and therefore impossible for points to stray from the unit circle. Thus, more loops can be made around the circle by raising this expression to higher powers:
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
o_n &= (o_1)^n
|
||||
= \left( {1 + it \over 1 -\ it} \right)^n \\
|
||||
\text{c}_n + i\text{s}_n
|
||||
&= (\text{c}_1 + i\text{s}_1)^n
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
This mirrors raising the complex exponential to a power, i.e., increasing the number of loops *it* makes in the interval $(-\pi, \pi)$. The final line is analogous to de Moivre's formula, but in a form where everything is a ratio of polynomials in *t*. This also means that the Chebyshev polynomials can be obtained directly from these rational expressions:
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
o_2 = (o_1)^2 &= (\text{c}_1 + i\text{s}_1)^2 \\
|
||||
&= \text{c}_1^2 + 2i\text{c}_1\text{s}_1 -\ \text{s}_1^2 +
|
||||
(0 = \text{c}_1^2 + \text{s}_1^2 -\ 1) \\
|
||||
&= 2\text{c}_1^2 + 2i\text{c}_1\text{s}_1 -\ 1 \\
|
||||
&= 2\text{c}_1(\text{c}_1 + i\text{s}_1) -\ 1 \\
|
||||
&= 2\text{c}_1 o_1 -\ 1 \\
|
||||
o_2 \cdot (o_1)^n &= 2\text{c}_1 o_1 \cdot (o_1)^n -\ (o_1)^n \\
|
||||
o_{n+2} &= 2\text{c}_1 o_{n+1} -\ o_n \\
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
This matches the earlier recurrence relation with the complex exponential and therefore the recurrence relation of the Chebyshev polynomials. This also means that the the rational functions obey the same relationship as sine and cosine
|
||||
|
||||
$$
|
||||
\begin{matrix}
|
||||
\begin{gather}
|
||||
\text{c}_n = T_n(\text{c}_1) \\
|
||||
\text{s}_n = U_{n-1}(\text{c}_1) \text{s}_1
|
||||
\end{gather} &
|
||||
\text{where }
|
||||
\text{c}_1 = {1 -\ t^2 \over 1 + t^2}, &
|
||||
\text{s}_1 = {2t \over 1 + t^2}
|
||||
\end{matrix}
|
||||
$$
|
||||
|
||||
Thus it is shown that the Chebyshev polynomials are tied to (coordinates on) circles, rather than explicitly to trigonometry. It is a bit strange that these polynomials are in terms of rational functions, but no stranger than them being in terms of *ir* rational functions like sine and cosine.
|
||||
|
||||
|
||||
Calculus
|
||||
--------
|
||||
|
||||
Since these functions behave similarly to sine and cosine, one might wonder about the nature of these expressions in the context of calculus. For comparison, the complex exponential (as it is a parallel construction) has a simple derivative:
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
e^{it} &= \cos(t) + i\sin(t) \\
|
||||
{d \over dt} e^{it} &= {d \over dt} \cos(t) + {d \over dt} i\sin(t) \\
|
||||
i e^{it} &= -\sin(t) + i\cos(t) \\
|
||||
i[\cos(t) + i\sin(t)] &\stackrel{\checkmark}{=} -\sin(t) + i\cos(t)
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
By applying the chain rule, one can easily differentiate the complex exponential. Since the exponential function is its own derivative, the expression acquires an imaginary coefficient through the chain rule. Meanwhile, the complex stereograph has derivative
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
{d \over dt} o_1(t) &= {d \over dt} {1 + it \over 1 -\ it}
|
||||
= {i(1 -\ it) + i(1 + it) \over (1 -\ it)^2} \\
|
||||
&= {2i \over (1 -\ it)^2}
|
||||
= {2i(1 + it)^2 \over (1 + t^2)^2}
|
||||
= {2i(1 -\ t^2 + 2it) \over (1 + t^2)^2} \\
|
||||
&= {-4t \over (1 + t^2)^2} + i {2(1 -\ t^2) \over (1 + t^2)^2} \\
|
||||
&= {-2 \over 1 + t^2}s_1 + i {2 \over 1 + t^2}c_1 \\
|
||||
&= -(1 + c_1)s_1 + i(1 + c_1)c_1 \\
|
||||
&= i(1 + c_1)o_1
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
Just like the complex exponential, an imaginary coefficient falls out. However, the expression also accrues a $1 + c_1$ term, almost like an adjustment factor for its failure to be the complex exponential. Sine and cosine obey a simpler relationship with respect to the derivative, and thus need no adjustment.
|
||||
|
||||
|
||||
Complex Analysis
|
||||
----------------
|
||||
|
||||
Since $o_n$ is a curve which loops around the unit circle *n* times, that possibly suits it to showing a simple result from complex analysis. Integrating along a contour which wraps around a sufficiently nice function's pole (i.e., where its magnitude grows without bound) yields a familiar value. This is easiest to see with $f(z) = 1 / z$:
|
||||
|
||||
$$
|
||||
\oint_\Gamma {1 \over z} dz =
|
||||
\int_a^b {\gamma'(t) \over \gamma(t)} dt
|
||||
= 2\pi i
|
||||
$$
|
||||
|
||||
In this example, $\Gamma$ loops once counterclockwise around the pole at *z* = 0; more loops will scale this by a factor according to the number of loops. Normally this is demonstrated with the complex exponential, but will $o_1$ work just as well? If $\Gamma$ is the unit circle, the integral is:
|
||||
|
||||
$$
|
||||
\oint_\Gamma {1 \over z} dz =
|
||||
\int_{-\infty}^\infty {o_1'(t) \over o_1(t)} dt
|
||||
= \int_{-\infty}^\infty i(1 + c_1(t)) dt
|
||||
= 2i\int_{-\infty}^\infty {1 \over 1 + t^2} dt
|
||||
$$
|
||||
|
||||
If one has studied their integral identities, the indefinite version of the final integral will be obvious as $\arctan(t)$, which has horizontal asymptotes of $\pi / 2$ and $-\pi / 2$. Therefore, the value of the integral is indeed $2\pi i$.
|
||||
|
||||
If there are *n* loops, then naturally there are *n* of these $2\pi i$s. Fortunately, powers of *o* are more loops around the circle, and the chain and power rules can be used to show:
|
||||
|
||||
$$
|
||||
\begin{gather*}
|
||||
{d \over dt} (o_1)^n = n(o_1)^{n-1} {d \over dt} o_1 \\ \\
|
||||
\oint_\Gamma {1 \over z} dz =
|
||||
\int_{-\infty}^\infty {n o_1(t)^{n-1} o_1'(t) \over o_1(t)^n} dt
|
||||
= n \int_{-\infty}^\infty {o_1'(t) \over o_1(t)} dt
|
||||
= 2 \pi i n
|
||||
\end{gather*}
|
||||
$$
|
||||
|
||||
It is certainly possible to perform these contour integrals along straight lines; in fact, integrating along lines from 1 to *i* to -1 to -*i* similarly deals with an indefinite integral involving arctangent. However, the best one can do to construct more loops with lines is to count each line multiple times, which isn't extraordinarily convincing.
|
||||
|
||||
Perhaps the use of $\infty$ in the integral bounds is also unconvincing, but the integral can be shifted back into the realm of plausibility by considering simpler bounds on $o_2$:
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
\oint_\Gamma {1 \over z} dz &=
|
||||
\int_{-1}^1 {2 o_1(t) o_1'(t) \over o_1(t)^2} dt \\
|
||||
&= 2 \int_{-1}^1 {o_1'(t) \over o_1(t)} dt \\
|
||||
&= 2(2i\arctan(1) -\ 2i\arctan(-1)) \\
|
||||
&= 2\pi i
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
This has an additional benefit: using the series form of $1 / (1 + t^2)$ and integrating, one obtains the series form of the arctangent. This series converges for $-1 \le t \le 1$, which happens to match the bounds of integration. The convergence of this series is fairly important, since it is tied to formulas for $\pi$, in particular [Leibniz's formula](https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80).
|
||||
|
||||
Were one to integrate with the complex exponential, the bounds $(0, 2\pi)$ would be invoked, since at this point a full loop has been made. But think to yourself -- how do you know that $2\pi$ radians is equivalent to 0 radians? How do you know the period of the complex exponential? The result using stereography relies on neither of these prior results and is directly pinned to a formula for $\pi$ instead an apparent detour through the number e.
|
||||
|
||||
|
||||
Polar Curves
|
||||
------------
|
||||
|
||||
Polar coordinates are useful for expressing for which the distance from the origin is a function of the angle with respect to the positive *x*-axis. They can also be readily converted to parametric forms:
|
||||
|
||||
$$
|
||||
\begin{gather*}
|
||||
r(\theta) &\Longleftrightarrow&
|
||||
\begin{matrix}
|
||||
x(\theta) = r \cos(\theta) \\
|
||||
y(\theta) = r \sin(\theta)
|
||||
\end{matrix}
|
||||
\end{gather*}
|
||||
$$
|
||||
|
||||
Polar curves frequently loop in on themselves, and so choosing appropriate bounds for $\theta$ (usually as multiples of $\pi$) is necessary for plotting. Evidently, this is due to the use of sine and cosine in the above parametrization. Fortunately, $s_n$ and $c_n$ (as shown by the calculus above) potentially have much simpler bounds. So what happens when one substitutes the rational functions in place of the trig ones?
|
||||
|
||||
|
||||
### Polar Roses
|
||||
|
||||
[Polar roses](https://en.wikipedia.org/wiki/Rose_(mathematics)) are beautiful shapes which have a simple form when expressed in polar coordinates.
|
||||
|
||||
$$
|
||||
r(\theta) = \cos \left( {p \over q} \cdot \theta \right)
|
||||
$$
|
||||
|
||||
The ratio $p/q$ in least terms uniquely determines the shape of the curve.
|
||||
|
||||
If this weren't this post, you might assume this curve is transcendental since it uses cosine, but you probably know better at this point. The Chebyshev examples above demonstrate the resemblance between $c_n$ and $\cos(n\theta)$. The subscript of $c$ is easiest to work with as an integer, so let $q = 1$.
|
||||
|
||||
:::: {layout-ncol = "2"}
|
||||
::: {}
|
||||
![]()
|
||||
:::
|
||||
|
||||
::: {}
|
||||
$$
|
||||
x(t) = c_p(t) c_1(t) \qquad y(t) = c_p(t) s_1(t)
|
||||
$$
|
||||
|
||||
will plot a $p/1$ polar rose as t ranges over $(-\infty, \infty)$. Since *t* never reaches infinity, a bite appears to be taken out of the graphs near (-1, 0).
|
||||
:::
|
||||
::::
|
||||
|
||||
$q = 1$ happens to match the subscript *c* term of *x* and *s* term of *y*, so one might wonder whether the other polar curves can be obtained by allowing it to vary as well. And you'd be right.
|
||||
|
||||
:::: {layout-ncol = "2"}
|
||||
::: {}
|
||||
![]()
|
||||
:::
|
||||
|
||||
::: {}
|
||||
$$
|
||||
x(t) = c_p(t) c_q(t) \qquad y(t) = c_p(t) s_q(t)
|
||||
$$
|
||||
|
||||
will plot a $p/q$ polar rose as t ranges over $(-\infty, \infty)$.
|
||||
:::
|
||||
::::
|
||||
|
||||
Just as with the prior calculus examples, doubling all subscripts of *c* and s will only require t to range over $(-1, 1)$, which removes the ugly bite mark. Perhaps it is also slightly less satisfying, since the fraction $p/q$ directly appears in the typical polar incarnation with cosine. On the other hand, it exposes an important property of these curves: they are all rational.
|
||||
|
||||
This approach lends additional precision to a prospective pseudo-polar coordinate system. In the next examples, I will be using the following notation for compactness:
|
||||
|
||||
$$
|
||||
\begin{gather*}
|
||||
R_n(t) = f(t) &\Longleftrightarrow&
|
||||
\begin{matrix}
|
||||
x(t) = f(t) c_n(t) \\
|
||||
y(t) = f(t) s_n(t)
|
||||
\end{matrix}
|
||||
\end{gather*}
|
||||
$$
|
||||
|
||||
|
||||
### Conic Sections
|
||||
|
||||
The polar equation for a conic section (with a particular unit length occurring somewhere) in terms of its eccentricity $\varepsilon$ is:
|
||||
|
||||
$$
|
||||
r(\theta) = {1 \over 1 -\ \varepsilon \cos(\theta)}
|
||||
$$
|
||||
|
||||
Correspondingly, the rational polar form can be expressed as
|
||||
|
||||
$$
|
||||
R_1(t) = {1 \over 1 -\ \varepsilon c_1}
|
||||
$$
|
||||
|
||||
Since polynomial arithmetic is (slightly) easier to work with than trigonometric identities, it is a matter of pencil-and-paper algebra to recover the implicit form from a parametric one.
|
||||
|
||||
|
||||
#### Parabola ($|\varepsilon| = 1$)
|
||||
|
||||
The conic section with the simplest implicit equation is the parabola. Since $c_n$ is a simple ratio of polynomials in *t*, it is much simpler to recover the implicit equation. For $\varepsilon = 1$,
|
||||
|
||||
:::: {layout-ncol = "2"}
|
||||
::: {}
|
||||
![]()
|
||||
:::
|
||||
|
||||
::: {}
|
||||
$$
|
||||
\begin{align*}
|
||||
1 -\ c_1 &= 1 -\ {1 -\ t^2 \over 1 + t^2} = {2 t^2 \over 1 + t_1} \\
|
||||
y &= {s_1 \over 1 -\ c_1} = {2t \over 1 + t^2} {1 + t^2 \over 2 t_1} = {1 \over t} \\
|
||||
x &= {c_1 \over 1 -\ c_1} = {1 -\ t^2 \over 1 + t^2} \cdot {1 + t^2 \over 2 t^2} = {1 -\ t^2 \over 2t^2} \\
|
||||
&= {1 \over 2t^2} -\ {1 \over 2} = {y^2 \over 2} -\ {1 \over 2}
|
||||
\end{align*}
|
||||
$$
|
||||
:::
|
||||
::::
|
||||
|
||||
*x* is a quadratic polynomial in *y*, so trivially the figure formed is a parabola. Technically it is missing the point where $y = 0 (t = \infty)$, and this is not a circumstance where using a higher $c_n$ would help. It is however, similar to the situation where we allow $o_1(\infty) = -1$, and an argument can be made to waive away any concerns one might have.
|
||||
|
||||
|
||||
#### Ellipse ($|\varepsilon| < 1$)
|
||||
|
||||
Ellipses are next. The simplest fraction between zero and one is 1/2, so for $\varepsilon = 1/2$,
|
||||
|
||||
:::: {layout-ncol = "2"}
|
||||
::: {}
|
||||
![]()
|
||||
:::
|
||||
|
||||
::: {}
|
||||
$$
|
||||
\begin{align*}
|
||||
1 -\ {1 \over 2}c_1 &= 1 -\ {1 \over 2} \cdot {1 -\ t^2 \over 1 + t^2} = {3 t^2 + 1 \over 2 + 2t^2} \\
|
||||
y &= {s_1 \over 1 -\ {1 \over 2}c_1} = {4t \over 3t^2 + 1} \\
|
||||
x &= {c_1 \over 1 -\ {1 \over 2}c_1} = {2 -\ 2t^2 \over 3t^2 + 1} \\ \\
|
||||
\end{align*}
|
||||
$$
|
||||
:::
|
||||
::::
|
||||
|
||||
There isn't an obvious way to combine products of *x* and *y* into a single equation. However, we do know that such an equation must have total degree 2. Any monomial of degree 2 must has a squared denominator, but must be able to add with the lower-degree terms. Therefore, the numerator of the sum of these degree 2 terms has the (degree 1) denominator as a factor.
|
||||
|
||||
Since the coefficient of $t^4$ in $x^2$ is 4, it must be multiplied by a multiple of 3 (the coefficient of $t^2$ in the denominator) for any factorization to occur.
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
x^2 &= {4 -\ 8t^2 + 4t^4 \over (3t^2 + 1)^2} \qquad
|
||||
y^2 = {16t^2 \over (3t^2 + 1)^2} \\ \\
|
||||
3x^2 + 4y^2 &=
|
||||
{12 -\ 24t^2 + 12t^4 + 64t^2 \over (3t^2 + 1)^2} \\
|
||||
&= {12 -\ 40t^2 + 12t^4 \over (3t^2 + 1)^2} \\
|
||||
&= {(4t^2 + 12) (3t^2 + 1) \over (3t^2 + 1)^2} \\
|
||||
&= {4t^2 + 12 \over 3t^2 + 1}
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
The linear term *t* occurs nowhere in the result, so this is clearly some combination of *x* and a constant. By the previous line of thought, the constant term must be a multiple of 4, and picking the smallest option finally results in the implicit form:
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
4 &= {4(3t^2 + 1) \over 3t^2 + 1} = {12t^2 + 4) \over 3t^2 + 1} \\
|
||||
{4t^2 + 12 \over 3t^2 + 1} -\ 4 &= {8 -\ 8t^2 \over 3t^2 + 1} = 4x \\ \\
|
||||
3x^2 + 4y^2 &= 4x + 4 \\
|
||||
3x^2 + 4y^2 -\ 4x -\ 4 &= 0
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
Notably, the coefficients of x and y are 3 and 4; simultaneously, $o_1(\varepsilon) = o_1(1/2) = {3 \over 5} + i{4 \over 5}$. This binds together three concepts: the simplest case of the Pythagorean theorem, the 3-4-5 right triangle; the coefficients of the implicit form; and the role of eccentricity with respect to stereography.
|
||||
|
||||
|
||||
#### Hyperbola ($|\varepsilon| > 1$)
|
||||
|
||||
As evidenced by the bound on the eccentricity above, hyperbolae are in some way the inverses of ellipses. Since $o_1(2)$ is a reflection of $o_1(1/2)$, you might think the implicit equation for $\varepsilon = 2$ to be the same, but with a flipped sign or two. Unfortunately, you'd be wrong.
|
||||
|
||||
:::: {layout-ncol = "2"}
|
||||
::: {}
|
||||
![]()
|
||||
:::
|
||||
|
||||
::: {}
|
||||
$$
|
||||
\begin{align*}
|
||||
x &= {c_1 \over 1 -\ 2c_1} = {1 -\ t^2 \over 3t^2 -\ 1} \\
|
||||
y &= {s_1 \over 1 -\ 2c_1} = {2t \over 3t^2 -\ 1} \\ \\
|
||||
3x^2 -\ y^2 &= {3 -\ 6t^2 + 3t^4 -\ 4t^2 \over 3t^2 -\ 1} \\
|
||||
&= {3 -\ 10t^2 + 3t^4 \over 3t^2 -\ 1} \\
|
||||
&= {(t^2 -\ 3)(3t^2 -\ 1) \over (3t^2 -\ 1)^2 } \\
|
||||
&= {t^2 -\ 3 \over 3t^2 -\ 1 } \\
|
||||
&= {3t^2 -\ 1 + t^2 -\ 3 \over 3t^2 -\ 1 } -\ 1 \\
|
||||
&= {4t^2 -\ 4 \over 3t^2 -\ 1 } -\ 1
|
||||
= -4x -\ 1
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
$$
|
||||
3x^2 -\ y^2 + 4x + 1 = 0
|
||||
$$
|
||||
:::
|
||||
::::
|
||||
|
||||
At the very least, the occurrences of 1 in the place of 4 have a simple explanation: 1 = 4 - 3. This reinforces the previous example's link to the Pythagorean triple 3-4-5.
|
||||
|
||||
|
||||
### Archimedean Spiral
|
||||
|
||||
Arguably the simplest (non-circular) polar curve is $r(\theta) = \theta$. This is also the unit [Archimedean spiral](https://en.wikipedia.org/wiki/Archimedean_spiral). Since the curve is defined by a constant turning, this is a natural application of the properties of sine and cosine. The closest equivalent in rational polar coordinates is $R_1(t) = t$. But this can be converted to an implicit form:
|
||||
|
||||
$$
|
||||
\begin{gather*}
|
||||
x = tc_1 \qquad y = ts_1 \\ \\
|
||||
x^2 + y^2 = t^2(c_1^2 + s_1^2) = t^2\\
|
||||
y = {2t^2 \over 1 + t^2} = {2(x^2 + y^2) \over 1 + (x^2 + y^2)} \\ \\
|
||||
(1 + x^2 + y^2)y = 2(x^2 + y^2)
|
||||
\end{gather*}
|
||||
$$
|
||||
|
||||
The curve produced by this equation is a [right strophoid](https://mathworld.wolfram.com/RightStrophoid.html) with a node at (0, 1) and asymptote $y = 2$. This form suggests something interesting about this curve: it approximates the Archimedean spiral (specifically the one with polar equation $r(\theta) = \theta/2$). Indeed, the sequence of curves with parametrization $R_n(t) = 2nt$ approximate the (unit) spiral for larger *n*, as can be seen in the following video.
|
||||
|
||||
![]()
|
||||
|
||||
Since R necessarily defines a rational curve, the curves will never be equal, just as any stretching of $c_n$ will never exactly become cosine.
|
||||
|
||||
|
||||
Closing
|
||||
-------
|
||||
|
||||
There is nothing wrong with using sine, cosine, the exponential function, or any of the entailing math, especially in a calculus setting. The benefit to using these functions is their constant "velocity" around the circle as their parameter varies continuously. Indeed, velocity, change, and continuity are some of the concepts which calculus deals with in the first place. Further, nearly every modern scientific calculator in the world features buttons for trigonometric functions.
|
||||
|
||||
We can however be misled by their apparent omnipresence. Stereographic projection has been around for *millennia*; just because Newton invented calculus 3.5 centuries ago does not mean every formula needs to be rewritten in its language. For example (and as previously mentioned), the Chebyshev polynomials are tied to the multiplication of two complex numbers whose norm cannot grow. This explanation requires only an understanding of complex number arithmetic, and not of trigonometry and dividing angles. Many other instances of sine and cosine merely rely on a number (or ratio) of loops around a circle, rather than their properties in calculus, and for these instances it will obviously do to "stay rational".
|
||||
|
||||
One of my favorite things to plot as a kid were polar roses, so it was somewhat of a shock to see that they are, in fact, rational curves. On the other hand, their rationality follows immediately from the rationality of the circle (which itself follows from the existence of Pythagorean triples). If I were more experienced with manipulating Chebyshev polynomials or willing to set up a linear system in (way too) many terms, I might have considered attempting to find an implicit form for them as well. However, only the latter approach would involve polynomial arithmetic and therefore be relevant to the contents of this post.
|
||||
|
||||
Diagrams created with Sympy and Matplotlib.
|
||||
477
type_alg/1/index.qmd
Normal file
477
type_alg/1/index.qmd
Normal file
@ -0,0 +1,477 @@
|
||||
---
|
||||
format:
|
||||
html:
|
||||
html-math-method: katex
|
||||
jupyter: python3
|
||||
---
|
||||
|
||||
|
||||
Type Algebra and You, Part 1: Basics
|
||||
====================================
|
||||
|
||||
When learning a new programming language, its type system can be a point of contention. Dynamic systems are fairly common in scripting languages like Python and JavaScript, which makes them great for beginners. However, it appears to be a general principle that imposing type-strictness (staticness, strength, ...) will generally allow errors to be caught before someone discovers them.
|
||||
|
||||
To the computer, types are illusory, but they are not to the human. Beyond their use in programming languages, mathematicians also work with types in a purely abstract way (putting aside the fact that "type" is already an abstract concept). Types bear some resemblance to sets, yet in other ways resemble numbers and can be manipulated via algebra. In the following couple of posts, I would like to investigate these resemblances. This post will primarily focus on making the numerical resemblance clear, since it is more fundamental and everyone tends to agree on [how to define arithmetic](https://en.wikipedia.org/wiki/Peano_axioms).
|
||||
|
||||
Since it can be difficult to appreciate relatively simple expressions without retreating back to programming language, I will use Haskell to demonstrate some interesting consequences of this topsy-turvy world.
|
||||
|
||||
|
||||
### Starting from Square Zero
|
||||
|
||||
If we can treat types as numbers, then we best start with 0. We interpret this as the number of possible values which populate the type. There are exactly zero possible values of type **0**, i.e., there is no way to construct a value with this type. In Haskell, this type is referred to as `Void`. Here is a paraphrased definition from (an earlier version of) [`Data.Void`](https://hackage.haskell.org/package/void-0.7/docs/src/Data-Void.html):
|
||||
|
||||
|
||||
```{.haskell}
|
||||
newtype Void = V Void
|
||||
```
|
||||
|
||||
In other words, `V`, the only constructor of `Void`, requires already having a `Void`. This requires infinite regress, so it is logically impossible.
|
||||
|
||||
|
||||
### Wait a minute, "only"?
|
||||
|
||||
Notice that `Void` has a constructor, `V`. It has type `Void -> Void`, which means that this type is definitely populated. It resembles the [empty function](https://en.wikipedia.org/wiki/Function_%28mathematics%29#empty_function) from set theory, of which there is only one for the empty set. This means that by constructing an empty type, we've also constructed a singleton.
|
||||
|
||||
Haskell already has a type with only one possible value: `()` (pronounced "unit", its only value is written identically). Can we prove that this is somehow the "same" type as `Void -> Void`? Of course! Remember, Haskell is a functional language. Therefore, we should focus on writing functions between the two types.
|
||||
|
||||
```{.haskell}
|
||||
toUnit :: (Void -> Void) -> ()
|
||||
fromUnit :: () -> (Void -> Void)
|
||||
```
|
||||
|
||||
The "to" and "from" in the names suggests that if we do one followed by the other, we should end up where we started. Or, in the language of functions, the composition of the functions is the identity.
|
||||
|
||||
```{.haskell}
|
||||
-- fromUnit . toUnit = id :: (Void -> Void) -> (Void -> Void)
|
||||
-- toUnit . fromUnit = id :: () -> ()
|
||||
```
|
||||
|
||||
In other words, we can reversibly map values from one type to the other (and vice versa). If such functions (also known as bijections) exist, then the types are *isomorphic*. I haven't actually defined the bijection yet; it is simple, if exhaustive:
|
||||
|
||||
```{.haskell}
|
||||
toUnit V = ()
|
||||
fromUnit () = V
|
||||
```
|
||||
|
||||
In future examples showing isomorphic types, I will define only of the bijections to save a bit of vertical space. For the other, I'll show only the type annotation.
|
||||
|
||||
|
||||
Hyperoperative (Multi)functors
|
||||
------------------------------
|
||||
|
||||
Another way to go from 0 to 1 is to just add one, an operation also called the [successor function](https://en.wikipedia.org/wiki/Successor_function). "Function" is vague here, since it is evocative of a mapping between values, rather than a mapping between types. [Kinds](https://wiki.haskell.org/Kind) live one step up from types, and we can conjecture the kind a successor "function" on types would have.
|
||||
|
||||
```{.haskell}
|
||||
succ :: Int -> Int -- the successor function constructs an integer from an integer
|
||||
Succ :: * -> * -- so we need to be able to construct a "bigger" type from a type
|
||||
```
|
||||
|
||||
The word with the closest meaning is [*functor*](https://wiki.haskell.org/Functor), which Haskell already uses to refer to types with a manipulable "inside" that can be mapped over. Fortunately, the kind of `Succ` matches what Haskell expects `Functor`s to look like (and in fact, since GHC 6.12.1, the compiler is smart enough to derive the definition on its own).
|
||||
|
||||
The functor in Haskell which most closely resembles the successor is `Maybe`.
|
||||
|
||||
```{.haskell}
|
||||
data Maybe a = Nothing | Just a
|
||||
type One = Maybe Void
|
||||
```
|
||||
|
||||
$$
|
||||
\begin{matrix}
|
||||
\scriptsize \text{A Maybe of $X$ is either} &
|
||||
\scriptsize \text{Nothing} &
|
||||
\scriptsize \text{or} &
|
||||
\scriptsize \text{a value of $X$} &
|
||||
\\
|
||||
S(X) := & 1 & + & X \\
|
||||
\scriptsize \text{The 1-valued type is} &
|
||||
\scriptsize \text{Nothing} &
|
||||
\scriptsize \text{or} &
|
||||
\scriptsize \text{logically impossible} & \\
|
||||
\bold{1} =& 1 &+& 0
|
||||
\end{matrix}
|
||||
$$
|
||||
|
||||
Since `Void` is has zero possible values, we should expect `Maybe Void` to have exactly one possible value. Constructing a `Just Void` would require that we already had a Void value, which is impossible. Therefore, `Nothing` is the only possible value of `Maybe Void`.
|
||||
|
||||
```{.haskell}
|
||||
toUnitM :: Maybe Void -> ()
|
||||
toUnitM Nothing = ()
|
||||
-- toUnitM (Just Void) is fictitious
|
||||
|
||||
-- fromUnitM :: () -> Maybe Void
|
||||
-- fromUnitM . toUnitM = id :: Maybe Void -> Maybe Void
|
||||
-- toUnitM . fromUnitM = id :: () -> ()
|
||||
```
|
||||
|
||||
|
||||
### Sums and Products
|
||||
|
||||
If there's a functor which resembles the successor, then are there ones which also resemble addition and multiplication? Not exactly. Functors are strictly a single-argument kind, and luckily the successor is a unary function. Addition and multiplication are binary, so they require a slightly more complicated kind:
|
||||
|
||||
```{.haskell}
|
||||
(+) :: Int -> Int -> Int -- we can add two things together
|
||||
Plus a b :: * -> * -> * -- so we want to be able to add two types together
|
||||
(*) :: Int -> Int -> Int -- likewise, we can multiply two things
|
||||
Mult a b :: * -> * -> * -- and we want to be able to multiply two types
|
||||
```
|
||||
|
||||
These kinds match what Haskell expects `Bifunctor`s to look like. Types get complicated when more than one parameter is involved, so GHC cannot derive `Bifunctor`. Alternatively, we can just consider bifunctors as they appear here: a functor with an arity of 2.
|
||||
|
||||
Putting aside semantics, Haskell indeed has two types resembling type addition and type multiplication. Respectively, they are `Either` and `(,)` (i.e., a 2-tuple):
|
||||
|
||||
```{.haskell}
|
||||
data Either a b = Left a | Right b
|
||||
data (,) a b = (,) a b
|
||||
```
|
||||
|
||||
$$
|
||||
\begin{matrix}
|
||||
\scriptsize \text{Either $X$ or $Y$ is} &
|
||||
\scriptsize \text{a value of $X$} &
|
||||
\scriptsize \text{or} &
|
||||
\scriptsize \text{a value of $Y$} &
|
||||
\\
|
||||
E(X,Y) := & X & + & Y
|
||||
\\
|
||||
\scriptsize \text{A tuple of $X$ and $Y$ is} &
|
||||
\scriptsize \text{a value of $X$} &
|
||||
\scriptsize \text{and} &
|
||||
\scriptsize \text{a value of $Y$} &
|
||||
\\
|
||||
P(X,Y) := & X & \times & Y
|
||||
\end{matrix}
|
||||
$$
|
||||
|
||||
Product types are common (consider any `struct` or tuple in a more common language, but sum types are less so. `union`s in C get close, but they are bound too memory for our purposes. Using addition, we can actually reconstruct the successor from **1**:
|
||||
|
||||
```{.haskell}
|
||||
type Maybe' a = Either () a
|
||||
|
||||
toMaybe' :: Maybe a -> Maybe' a
|
||||
toMaybe' Nothing = Left ()
|
||||
toMaybe' (Just x) = Right x
|
||||
|
||||
-- fromMaybe' :: Maybe' a -> Maybe a
|
||||
-- fromMaybe' . toMaybe' = id :: Maybe a -> Maybe a
|
||||
-- toMaybe' . fromMaybe' = id :: Maybe' a -> Maybe' a
|
||||
```
|
||||
|
||||
`Left ()` now takes on the role of `Nothing`, while all the `Right`s come from the type parameter `a`.
|
||||
|
||||
|
||||
### Rearranging and Rebracketing
|
||||
|
||||
Within the world of numbers, addition and multiplication are commutative and associative. Is the same true for types?
|
||||
|
||||
Commutativity is obvious, since we can construct functions which switch *X* and *Y* around. Haskell already provides these functions, whose definitions I have copied here
|
||||
|
||||
```{.haskell}
|
||||
swapEither :: Either a b -> Either b a
|
||||
swapEither (Left x) = Right x
|
||||
swapEither (Right x) = Left x
|
||||
|
||||
swap :: (a, b) -> (b, a)
|
||||
swap (x, y) = (y, x)
|
||||
|
||||
-- swapEither . swapEither = id :: Either a b -> Either a b
|
||||
-- swap . swap = id :: (a, b) -> (a, b)
|
||||
```
|
||||
[swapEither](https://hackage.haskell.org/package/either-5.0.2/docs/Data-Either-Combinators.html#v:swapEither) <br>
|
||||
[swap](https://hackage.haskell.org/package/base-4.16.1.0/docs/Data-Tuple.html#v:swap)
|
||||
|
||||
Associativity is a bit trickier, since it deals with triples rather than pairs.
|
||||
|
||||
```{.haskell}
|
||||
assocRightSum :: Either a (Either b c) -> Either (Either a b) c
|
||||
assocRightSum (Left x) = Left (Left x)
|
||||
assocRightSum (Right (Left y)) = Left (Right y)
|
||||
assocRightSum (Right (Right z)) = Right z
|
||||
|
||||
-- assocLeftSum :: Either (Either a b) c -> Either a (Either b c)
|
||||
-- assocLeftSum . assocRightSum = id :: Either a (Either b c) -> Either a (Either b c)
|
||||
-- assocRightSum . assocLeftSum = id :: Either (Either a b) c -> Either (Either a b) c
|
||||
|
||||
assocRightProd :: (a, (b, c)) -> ((a, b), c)
|
||||
assocRightProd (x, (y, z)) = ((x, y), z)
|
||||
|
||||
-- assocLeftProd :: ((a, b), c) -> (a, (b, c))
|
||||
-- assocLeftProd . assocRightProd = id :: (a, (b, c)) -> (a, (b, c))
|
||||
-- assocRightProd . assocLeftProd = id :: ((a, b), c) -> ((a, b), c)
|
||||
```
|
||||
|
||||
In fact, since we don't care about order or grouping, we can forget both entirely and map them instead into 3-tuples and a triple-sum type:
|
||||
|
||||
```{.haskell}
|
||||
data Either3 a b c = Left3 a | Center3 b | Right3 c
|
||||
data (,,) a b c = (,,) a b c
|
||||
|
||||
toLeftSum :: Either3 a b c -> Either (Either a b) c -- x+y+z = (x+y)+z
|
||||
toLeftSum (Left3 x) = Left (Left x)
|
||||
toLeftSum (Center3 y) = Left (Right y)
|
||||
toLeftSum (Right3 z) = Right z
|
||||
|
||||
-- fromLeftSum :: Either (Either a b) c -> Either3 a b c
|
||||
-- fromLeftSum . toLeftSum = id :: Either3 a b c -> Either3 a b c
|
||||
-- toLeftSum . fromLeftSum = id :: Either (Either a b) c -> Either (Either a b) c
|
||||
|
||||
toLeftProd :: (a,b,c) -> ((a,b),c)
|
||||
toLeftProd (x,y,z) = ((x,y),z)
|
||||
|
||||
-- fromLeftProd :: ((a,b),c) -> (a,b,c)
|
||||
-- fromLeftProd . toLeftProd = id :: (a,b,c) -> (a,b,c)
|
||||
-- toLeftProd . fromLeftProd = id :: ((a,b),c) -> ((a,b),c)
|
||||
```
|
||||
|
||||
There's still a bit of work to prove associativity *in general* from these local rules, but we're still primarily concerned with the equivalent functors `Either` and `(,)`, which are local anyway.
|
||||
|
||||
|
||||
### Other Arithmetic Properties
|
||||
|
||||
Naturally, **0** and **1** satisfy their typical properties. There are additional interpretations, however:
|
||||
|
||||
| Property | Arithmetic | Haskell | Reasoning
|
||||
|----------------------------|---------------------|---------|-----------
|
||||
| Additive Identity | $0 + X \cong X$ | `Either Void a` is equivalent to `a` | Since **0** is unoccupied, its sum with any other type can only ever contain values from that other type (i.e., only `Right` values).
|
||||
| Multiplicative Identity | $1 \cdot X \cong X$ | `((), a)` is equivalent to `a` | Since **1** has exactly one possible value, its product with any other type is this value and any value from the other type (i.e., tuples of the form `((), x)`).
|
||||
| Multiplicative Annihilator | $0 \cdot X \cong 0$ | `(Void, a)` is equivalent to `Void` | Since **0** is unoccupied, we cannot construct a type which requires a value from it (i.e., writing a tuple of the form `(Void, x)` requires having a `Void` value).
|
||||
|
||||
|
||||
### Distributivity
|
||||
|
||||
Finally, to hammer home the resemblance to normal arithmetic, we should consider the distribution of multiplication over addition. We can first see that multiplication does distribute over the successor:
|
||||
|
||||
```{.haskell}
|
||||
type SuccDist a b = ((Maybe a), (Maybe b))
|
||||
data SumProd a b = Nada | LProd a | RProd b | BothProd a b
|
||||
```
|
||||
|
||||
$$
|
||||
\begin{matrix}
|
||||
\scriptsize \text{Maybe $X$ and Maybe $Y$} & \scriptsize \text{is equivalent to} \\
|
||||
(1 + X) \cdot (1 + Y) \cong \\
|
||||
1
|
||||
& \scriptsize \text{Both Nothing}\\
|
||||
+ X \phantom{\cdot Y}
|
||||
& \scriptsize \text{or Just $x$, Nothing} \\
|
||||
+ \phantom{X \cdot} Y & \scriptsize \text{or Nothing, Just $y$} \\
|
||||
+ X \cdot Y & \scriptsize \text{or Just $x$, Just $y$} \\
|
||||
\end{matrix}
|
||||
$$
|
||||
|
||||
If we forbade the first and last cases (in other words, if we "subtract" them out), then the type would be equivalent to the sum of *X* and *Y*.
|
||||
|
||||
```{.haskell}
|
||||
toSuccDist :: Either a b -> SuccDist a b
|
||||
toSuccDist (Left x) = (Just x, Nothing)
|
||||
toSuccDist (Right y) = (Nothing, Just y)
|
||||
|
||||
fromSuccDist :: SuccDist a b -> Either a b
|
||||
fromSuccDist (Just x, Nothing) = Left x
|
||||
fromSuccDist (Nothing, Just y) = Right y
|
||||
fromSuccDist _ = undefined -- non-exhaustive!
|
||||
|
||||
-- fromSuccDist . toSuccDist = id :: Either a b -> Either a b
|
||||
-- toSuccDist . fromSuccDist /= id -- because of extra values
|
||||
```
|
||||
|
||||
Fortunately, general distributivity is also relatively simple. We do as grade schoolers do and FOIL out the results...
|
||||
|
||||
```{.haskell}
|
||||
type PreDist a b c d
|
||||
= (Either a b, Either c d)
|
||||
data PostDist a b c d
|
||||
= F a c | O a d | I b c | L b d
|
||||
-- first, outer, inner, last
|
||||
```
|
||||
|
||||
$$
|
||||
\begin{matrix}
|
||||
\scriptsize \text{Either $X$ $Y$ and Either $Z$ $W$} & \scriptsize \text{is equivalent to} \\
|
||||
(X + Y) \cdot (Z + W) \cong \\
|
||||
\phantom{+} X \cdot Z
|
||||
& \scriptsize \text{Firsts,}\\
|
||||
\vphantom{} + X \cdot W
|
||||
& \scriptsize \text{Outers,} \\
|
||||
\vphantom{}+ Y \cdot Z
|
||||
& \scriptsize \text{Inners,} \\
|
||||
\vphantom{}+ Y \cdot W
|
||||
& \scriptsize \text{or Lasts} \\
|
||||
\end{matrix}
|
||||
$$
|
||||
|
||||
...then construct our bijections:
|
||||
|
||||
```{.haskell}
|
||||
distribute :: PreDist a b c d -> PostDist a b c d
|
||||
distribute (Left x, Left z) = F x z
|
||||
distribute (Left x, Right w) = O x w
|
||||
distribute (Right y, Left z) = I y z
|
||||
distribute (Right y, Right w) = L y w
|
||||
|
||||
-- undistribute :: PostDist a b c d -> PreDist a b c d
|
||||
-- undistribute . distribute = id :: PreDist a b c d -> PreDist a b c d
|
||||
-- distribute . undistribute = id :: PostDist a b c d -> PostDist a b c d
|
||||
```
|
||||
|
||||
This works primarily because without further information about any of the types `a`, `b`, `c`, `d`, we can only rearrange them. It may be the case that `a ~ (b -> d)` (a function from `b` to `d`), meaning that we could lose the distinction between the instances `O` and `L` (and be unable to `undistribute`). But we don't have enough information to define `distribute` in any other way than the above. A similar argument holds for our other bijections.
|
||||
|
||||
|
||||
Bools, Bools, and more Bools
|
||||
----------------------------
|
||||
|
||||
While it's all well and good that these operations appear to mirror familiar arithmetic, we haven't counted farther than **0** and **1**. Everyone past the age of four recognizes that **2** comes next. Fortunately, this corresponds to a much better-known type: booleans. Rather than defining them as numbers like in lower-level languages (as a computer would understand), we can construct them directly as abstract values in Haskell
|
||||
|
||||
```{.haskell}
|
||||
data Bool = True | False
|
||||
```
|
||||
|
||||
$$
|
||||
\begin{matrix}
|
||||
\scriptsize \text{A bool is either} &
|
||||
\scriptsize \text{True} &
|
||||
\scriptsize \text{or} &
|
||||
\scriptsize \text{False} &
|
||||
\\
|
||||
\text{Bool} := & 1 & + & 1 &= \bold{2}
|
||||
\end{matrix}
|
||||
$$
|
||||
|
||||
Bools may also be defined using the above functors, either by two successors or by addition:
|
||||
|
||||
```{.haskell}
|
||||
type BoolSucc = Maybe (Maybe Void) -- succ (succ 0)
|
||||
|
||||
toBoolSucc :: Bool -> BoolSucc
|
||||
toBoolSucc False = Nothing
|
||||
toBoolSucc True = Just Nothing
|
||||
|
||||
-- fromBoolSucc :: BoolSucc -> Bool
|
||||
-- fromBoolSucc . toBoolSucc = id :: Bool -> Bool
|
||||
-- toBoolSucc . fromBoolSucc = id :: BoolSucc -> BoolSucc
|
||||
|
||||
type BoolEither = Either () () -- 1 + 1
|
||||
|
||||
toBoolEither :: Bool -> BoolEither
|
||||
toBoolEither False = Left ()
|
||||
toBoolEither True = Right ()
|
||||
|
||||
-- fromBoolEither :: BoolEither -> Bool
|
||||
-- fromBoolEither . toBoolEither = id :: Bool -> Bool
|
||||
-- toBoolEither . fromBoolEither = id :: BoolEither -> BoolEither
|
||||
```
|
||||
|
||||
|
||||
### Four Great Justice
|
||||
|
||||
Rather than progressing to **3**, let's jump to **4**. The number 2 has an interesting property: its sum with itself is its product with itself (is its exponent with itself...) is 4. Thus, the following types all encode a four-valued type:
|
||||
|
||||
```{.haskell}
|
||||
data Four = Zero | One | Two | Three
|
||||
type FourSucc = Maybe (Maybe Bool)
|
||||
type FourSum = Either Bool Bool
|
||||
type FourProd = (Bool, Bool)
|
||||
```
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
4 &:= 1+1+1+1 \\
|
||||
&\phantom{:}\cong S(S(2)) \\
|
||||
&\phantom{:}\cong E(2,2) = 2 + 2 \\
|
||||
&\phantom{:}\cong P(2,2) = 2 \cdot 2 \\
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
Showing isomorphism here can get a bit tricky, but there are natural bijections for `FourSum` and `FourProd` obtained by examining the binary expansions of 0, 1, 2, and 3.
|
||||
|
||||
```{.haskell}
|
||||
toFourSucc :: Four -> FourSucc
|
||||
toFourSucc Zero = Nothing
|
||||
toFourSucc One = Just Nothing
|
||||
toFourSucc Two = Just (Just False)
|
||||
toFourSucc Three = Just (Just True)
|
||||
|
||||
-- fromFourSucc :: FourSucc -> Four
|
||||
-- fromFourSucc . toFourSucc = id :: Four -> Four
|
||||
-- toFourSucc . fromFourSucc = id :: FourSucc -> FourSucc
|
||||
|
||||
toFourSum :: Four -> FourSum
|
||||
toFourSum Zero = Left False -- 00
|
||||
toFourSum One = Left True -- 01
|
||||
toFourSum Two = Right False -- 10
|
||||
toFourSum Three = Right True -- 11
|
||||
|
||||
-- fromFourSum :: FourSum -> Four
|
||||
-- fromFourSum . toFourSum = id :: Four -> Four
|
||||
-- toFourSum . fromFourSum = id :: FourSum -> FourSum
|
||||
|
||||
toFourProd :: Four -> FourProd
|
||||
toFourProd Zero = (False, False) -- 00
|
||||
toFourProd One = (False, True) -- 01
|
||||
toFourProd Two = (True, False) -- 10
|
||||
toFourProd Three = (True, True) -- 11
|
||||
|
||||
-- fromFourProd :: FourProd -> Four
|
||||
-- fromFourProd . toFourProd = id :: Four -> Four
|
||||
-- toFourProd . fromFourProd = id :: FourProd -> FourProd
|
||||
```
|
||||
|
||||
|
||||
### Hiding in the Clouds
|
||||
|
||||
Though I mentioned exponentiation earlier, I did not show the equivalent for types. Set theorists sometimes write [the set of functions from one set *A* to another set *B*](https://en.wikipedia.org/wiki/Function_type) as $B^A$ because the number of possible functions comes from the cardinalities of the sets: $|B|^{|A|}$. We previously examined how the constructor for Void is a singleton, which satisfies the equation $0^0 = 1$. At least in the finite world, sets and types are not so different, and indeed, we can write
|
||||
|
||||
```{.haskell}
|
||||
type FourFunc = Bool -> Bool
|
||||
-- there are exactly four functions:
|
||||
taut x = True -- tautology
|
||||
cont x = False -- contradiction
|
||||
id' x = x -- identity
|
||||
not' x = not x -- negation
|
||||
```
|
||||
|
||||
$$
|
||||
2 \rightarrow 2 = 2^2 \cong \bold 4
|
||||
$$
|
||||
|
||||
If we want to map these functions onto our `Four` type, we can again examine the binary expansion of the numbers 0-3. This time, the first place value encodes the value of the function on `False`, and the second value the same on `True`.
|
||||
|
||||
```{.haskell}
|
||||
toFourFunc :: Four -> FourFunc
|
||||
-- easiest to consider binary place values as input
|
||||
-- 10
|
||||
toFourFunc Zero = cont -- 00
|
||||
toFourFunc One = not' -- 01
|
||||
toFourFunc Two = id' -- 10
|
||||
toFourFunc Three = taut -- 11
|
||||
|
||||
-- fromFourFunc :: FourFunc -> Four
|
||||
-- fromFourFunc . toFourFunc = id :: Four -> Four
|
||||
-- toFourFunc . fromFourFunc = id :: FourFunc -> FourFunc
|
||||
```
|
||||
|
||||
For larger functions, we can see a more generalizable mapping from the truth table, shown here for AND
|
||||
|
||||
| Column Number | Binary | Input (x) | Input (y) | x AND y |
|
||||
|---------------|--------|-----------|-----------|---------|
|
||||
| 0 | 00 | False | False | False |
|
||||
| 1 | 01 | False | True | False |
|
||||
| 2 | 10 | True | False | False |
|
||||
| 3 | 11 | True | True | True |
|
||||
|
||||
Going up the final column and converting to binary digits, we can read "1000", which is the binary expansion of 8. The other numbers from 0 to $(2^2)^2 - 1$ can similarly be associated to boolean functions `Bool -> (Bool -> Bool)`. Note that function arrows are assumed to be right-associative, but this is opposite the right-associativity of typical exponentiation. I'll dive deeper into why this needs to be the case later.
|
||||
|
||||
|
||||
Closing
|
||||
-------
|
||||
|
||||
If you know your Haskell, especially the difference between `data` and `newtype`, then you know about the slight stretching of the truth I have performed. Yes, it is true that types defined with `data` implicitly add a possible `undefined` value which can tell the runtime to error out. Everyone who knows Haskell better than me says [not to worry about it in most cases](https://wiki.haskell.org/Newtype). At worst, it would make some of the pretty arithmetic above go away.
|
||||
|
||||
Other fascinating concepts live in this world, such as the [Curry-Howard correspondence](https://en.wikipedia.org/wiki/Curry%E2%80%93Howard_correspondence). Rather than discussing them, in the next post I would like to press onward with the algebra. I have spent this post laying the groundwork for arithmetic on types, so it will focus more upon recursion and fixed points.
|
||||
|
||||
A Haskell file containing all of the code on this page can be found [here]().
|
||||
|
||||
|
||||
### Additional Links
|
||||
|
||||
- [Algebraic Data Types](https://en.wikipedia.org/wiki/Algebraic_data_type) (Wikipedia)
|
||||
- [Generically Deriving Bifunctor](https://blog.csongor.co.uk/generic-deriving-bifunctor/) (Blogpost by Csongor Kiss)
|
||||
447
type_alg/2/index.qmd
Normal file
447
type_alg/2/index.qmd
Normal file
@ -0,0 +1,447 @@
|
||||
---
|
||||
format:
|
||||
html:
|
||||
html-math-method: katex
|
||||
jupyter: python3
|
||||
---
|
||||
|
||||
|
||||
Type Algebra and You, Part 2: A Fixer-upper
|
||||
===========================================
|
||||
|
||||
In the [previous post](), I wrote mostly about finite types, as well as type addition (`Either`) and multiplication (`(,)`). This post will focus on another important operation, exponentiation, before moving on to recursively-defined types.
|
||||
|
||||
|
||||
Exponent Laws
|
||||
-------------
|
||||
|
||||
I mentioned at the end of the previous post that exponents correspond to functions. It's not a stretch to say that without them, nothing would get done in a program. They are denoted in the following way
|
||||
|
||||
```{.haskell}
|
||||
type Exponent a b = (->) a b
|
||||
-- equivalent to `a -> b`
|
||||
```
|
||||
|
||||
$$
|
||||
\text{Exp}(X,Y) := Y^X = X \rightarrow Y
|
||||
$$
|
||||
|
||||
The arrow constructor (typically infixed) appears everywhere in Haskell types. But is the corresponding notation $Y^X$ fitting or merely illusory?
|
||||
|
||||
|
||||
### Exponentiation Distributes over Multiplication
|
||||
|
||||
One law of exponents states that if we have a product raise it to an exponent, then the exponent should distribute to both terms. In types, this is
|
||||
|
||||
$$
|
||||
\stackrel{\text{A function to a product}}{Z \rightarrow (X \times Y)}
|
||||
= (X\times Y)^{Z}
|
||||
\stackrel{?}{\cong} X^Z \times Y^Z
|
||||
= \stackrel{\text{A pair of functions with a common input}}{(Z \rightarrow X) \times (Z \rightarrow Y)}
|
||||
$$
|
||||
|
||||
This says that a function `f` returning a pair can be decomposed as a pair of functions `g` and `h` returning each component. If we wish to convert form the former to the latter, we can call `f`, then use the functions that give either element of the tuple (`fst` and `snd`). Conversely, if we instead have a pair of functions from the same type, then we can just send the same value to both and put it in a pair.
|
||||
|
||||
```{.haskell}
|
||||
distExp :: (a -> (b, c)) -> (a -> b, a -> c)
|
||||
distExp f = (\x -> fst (f x), \x -> snd (f x))
|
||||
-- distExp f = (fst . f, snd . f)
|
||||
|
||||
undistExp :: (a -> b, a -> c) -> (a -> (b, c))
|
||||
undistExp (g, h) = \x -> (g x, h x)
|
||||
|
||||
-- undistExp . distExp = id :: (a -> (b, c)) -> (a -> (b, c))
|
||||
-- distExp . undistExp = id :: (a -> b, a -> c) -> (a -> b, a -> c)
|
||||
```
|
||||
|
||||
Unlike with the sum and product, the identities here exist on functions themselves. Fortunately, Haskell is [referentially transparent](https://en.wikipedia.org/wiki/Referential_transparency), so we could deepen the proof by composing the functions and using lambda calculus rules until the expression is clearly an identity. If we wanted to, it would be possible to do the same for the identities on the previous post. In the interest of being concise, I won't be doing so.
|
||||
|
||||
|
||||
### Product of Exponents is an Exponent of a Sum
|
||||
|
||||
We also know that if two expressions with same base are multiplied, their exponents should add. So at the type level, we must check the equivalence of the following
|
||||
|
||||
$$
|
||||
\stackrel{\text{A pair of functions with a common return}}{(X \rightarrow Z) \times (Y \rightarrow Z)}
|
||||
= Z^{X} \times Z^Y
|
||||
\stackrel{?}{\cong} Z^{X + Y}
|
||||
= \stackrel{\text{A function on a sum}}{(X + Y) \rightarrow Z}
|
||||
$$
|
||||
|
||||
This says that a pair of functions `g` and `h` with the same return type can be composed into a function `f`. If we start with the former, we must create a function `f` on `Either` which decides which function to use. If we have this function already, we can get back to where we started by putting our values into `Either` with `Left` or `Right` before using `f`. The bijections look like
|
||||
|
||||
```{.haskell}
|
||||
toExpSum :: (a -> c, b -> c) -> (Either a b -> c)
|
||||
toExpSum (g, h) = \w -> case w of
|
||||
Left x -> g x
|
||||
Right y -> h y
|
||||
|
||||
fromExpSum :: (Either a b -> c) -> (a -> c, b -> c)
|
||||
fromExpSum f = (\x -> f (Left x), \y -> f (Right y))
|
||||
-- fromExpSum f = (f . Left, f . Right)
|
||||
|
||||
-- fromExpSum . toExpSum = id :: (a -> c, b -> c) -> (a -> c, b -> c)
|
||||
-- toExpSum . fromExpSum = id :: (Either a b -> c) -> (Either a b -> c)
|
||||
```
|
||||
|
||||
This identity actually tells us something very important regarding the definition of functions on sum types: they must have as many definitions (i.e., a *product* of definitions) as there are types in the sum. This has been an implicit part of all bijections involving `Either`, since they have a path for both `Left` and `Right`.
|
||||
|
||||
|
||||
### Exponent of an Exponent is an Exponent of a Product
|
||||
|
||||
Finally (arguably as consequence of the first law), if we raise a term with an exponent to another exponent, the exponents should multiply.
|
||||
|
||||
$$
|
||||
\stackrel{\text{A function returning a function}}{X \rightarrow (Y \rightarrow Z)} = (Z^{Y})^X
|
||||
\stackrel{?}{\cong}
|
||||
Z^{X \times Y} = \stackrel{\text{A function on a product}}{(X \times Y) \rightarrow Z}
|
||||
$$
|
||||
|
||||
We start with a function which returns a function and end up with a function from a product. The equivalence of these two types is [currying](https://en.wikipedia.org/wiki/Currying), an important concept in computer science. Haskell treats it so importantly that the bijections are almost trivial
|
||||
|
||||
```{.haskell}
|
||||
curry' :: (a -> (b -> c)) -> ((a, b) -> c)
|
||||
curry' f = \(x, y) -> (f x) y
|
||||
-- curry' f (x, y) = f x y
|
||||
|
||||
uncurry' :: ((a, b) -> c) -> (a -> b -> c)
|
||||
uncurry' f = \x -> \y -> f (x, y)
|
||||
-- uncurry' f x y = f (x, y)
|
||||
|
||||
-- uncurry' . curry' = id :: (a -> b -> c) -> (a -> b -> c)
|
||||
-- curry' . uncurry' = id :: ((a, b) -> c) -> ((a, b) -> c)
|
||||
```
|
||||
|
||||
These come pre-implemented in Haskell since tuples are harder to define than a chain of arrows.
|
||||
|
||||
|
||||
Double-Checking for Algebra
|
||||
---------------------------
|
||||
|
||||
Even though we've established that sums, products, and exponents obey their normal arithmetical rules, we've yet to lay the groundwork for algebra. In algebra, we start by introducing a symbol called *x* and allow it to interacting with numbers. This means that it obeys the arithmetical laws that have been established.
|
||||
|
||||
Functors allow us to parametrize a type in a similar way to this. We already know how to add and multiply finite types, for a "variable" type *X*, it bears checking that it can add and multiply with itself.
|
||||
|
||||
|
||||
### Coefficients
|
||||
|
||||
Is it truly the case that an object's sum with itself is its product with a 2-valued type? It should follow from addition laws, but it might behoove us to check
|
||||
|
||||
```{.haskell}
|
||||
type LeftRight a = Either a a
|
||||
-- LeftRight a ≅ (Bool, a)
|
||||
```
|
||||
$$
|
||||
X + X \stackrel{?}\cong 2 \times X
|
||||
$$
|
||||
|
||||
It's actually quite simple if we look back at the implementation of `Bool` as `Either () ()`. `False` values go on the `Left`, and `True` values go on the `Right`:
|
||||
|
||||
```{.haskell}
|
||||
to2Coeff :: LeftRight a -> (Bool, a)
|
||||
to2Coeff (Left x) = (False, x)
|
||||
to2Coeff (Right x) = (True, x)
|
||||
|
||||
-- from2Coeff :: (Bool, a) -> LeftRight a
|
||||
-- from2Coeff . to2Coeff = id :: Either a a -> Either a a
|
||||
-- to2Coeff . from2Coeff = id :: (Bool, a) -> (Bool, a)
|
||||
```
|
||||
|
||||
We can continue adding *X* to itself using `Either` to obtain arbitrarily high coefficients. This is simplest as `Either a (Either a (... (Either a a)))`, where values are a chain of `Right`s ending in a `Left`. Then, the maximum number of `Right`s allowed is *n* - 1. Counting zero, there are *n* values, demonstrating the type *n*.
|
||||
|
||||
|
||||
### Exponents
|
||||
|
||||
Multiplying *X* by a finite type is all well and good, but we should also check that it can multiply like itself. If all is well, then this should be equivalent to raising *X* to an exponent. We've already discussed those laws, but just in case, we should check
|
||||
|
||||
```{.haskell}
|
||||
type Pair a = (a, a)
|
||||
-- Pair a ≅ (Bool -> a)
|
||||
```
|
||||
|
||||
$$
|
||||
X \times X \stackrel{?}{\cong}
|
||||
X^2 = 2 \rightarrow X
|
||||
$$
|
||||
|
||||
To wit, we express the pair as a function from the finite type `Bool`. If we have our pair of *X*, then we know our output is one of the two, and return a function that decides to return. On the other hand, we need only apply a function from `Bool` to every possible value in `Bool` as many times as it appears (twice) and collect it in a product to get back to where we started. The bijections to and from this type look like this
|
||||
|
||||
```{.haskell}
|
||||
to2Exp :: (a, a) -> (Bool -> a)
|
||||
to2Exp (x, y) = \p -> case p of
|
||||
False -> x
|
||||
True -> y
|
||||
|
||||
from2Exp :: (Bool -> a) -> (a, a)
|
||||
from2Exp f = (f False, f True)
|
||||
|
||||
-- from2Exp . to2exp = id :: (a, a) -> (a, a)
|
||||
-- to2Exp . from2Exp = id :: (Bool -> a) -> (Bool -> a)
|
||||
```
|
||||
|
||||
This a special case of what are called *representable functors*. The functor in question is `Pair`, represented by functions from `Bool`. A similar argument can be shown for any *n* that $\overbrace{X \times X \times ... \times X}^n$ can be represented functions from *n*.
|
||||
|
||||
Together, these two isomorphisms mean that any polynomial whose coefficients are positive integers can be interpreted as a functor in *X*.
|
||||
|
||||
|
||||
Fixing What Ain't Broke
|
||||
-----------------------
|
||||
|
||||
Exponents, finite types and polynomials are wonderful, but we need to go further. Even though we have access to types for any natural number, we can only define finite arithmetic within them. We could inflate the number of possible values in a naive way...
|
||||
|
||||
|
||||
```{.haskell}
|
||||
data Nat = Zero | One | Two | Three | Four | Five | ...
|
||||
```
|
||||
|
||||
$$
|
||||
\begin{matrix} &
|
||||
\scriptsize \text{Zero} & \scriptsize \text{or} &
|
||||
\scriptsize \text{One} & \scriptsize \text{or} &
|
||||
\scriptsize \text{Two} & \scriptsize \text{or}
|
||||
\\
|
||||
X =& 1 &+& 1 &+& 1 &+& ...
|
||||
\end{matrix}
|
||||
$$
|
||||
|
||||
...but this is akin to programming without knowing how to use loops. In Haskell, we approach (potentially infinite) loops recursively. One of the simplest ways we can recurse is by using a [fixed-point combinator](https://en.wikipedia.org/wiki/Fixed-point_combinator). While typically discussed at the value level as a higher-order function, there also exists a similar object at the type level which operates on functors. Haskell implements this type in [`Data.Fix`](https://hackage.haskell.org/package/data-fix-0.3.2/docs/Data-Fix.html), from which I have copied the definition below
|
||||
|
||||
```{.haskell}
|
||||
-- Fix :: (* -> *) -> *
|
||||
data Fix f = In { out :: (f (Fix f)) }
|
||||
|
||||
-- compare with the functional fixed-point
|
||||
fix :: (a -> a) -> a
|
||||
fix f = let x = f x in x
|
||||
```
|
||||
|
||||
$$
|
||||
\mathcal{Y}(F(X)) := F(\mathcal Y (F))
|
||||
$$
|
||||
|
||||
("Y" here comes from the Y-combinator, which isn't entirely accurate, but is fitting enough)
|
||||
|
||||
If we `Fix` a functor `f` Values of this type are the functor `f` containing a `Fix f`. In other words, it produces a new type containing itself.
|
||||
|
||||
|
||||
Au Naturale
|
||||
-----------
|
||||
|
||||
To demonstrate how this extends what the kinds of types we can define, let's work out the natural numbers. [Peano arithmetic](https://en.wikipedia.org/wiki/Peano_axioms) gives them a recursive definition: they are either 0 or the successor of another natural number. The "naive" definition in Haskell is very simple. Arithmetically, though, things seem to go wrong.
|
||||
|
||||
```{.haskell}
|
||||
data Nat' = Zero | Succ Nat'
|
||||
|
||||
zero = Zero
|
||||
one = Succ Zero
|
||||
two = Succ one
|
||||
...
|
||||
```
|
||||
|
||||
Values in `Nat'` are a string of `Succ`s which terminate in a `Zero`. Of course, we also could define this in terms of `Fix`. First, we need to define a type to fix, then apply the operator
|
||||
|
||||
```{.haskell}
|
||||
type NatU a = Zero | Succ a
|
||||
type NatF = Fix NatU -- possible values are "Y Zero" or "Y (Succ (x :: Fix NatU))"
|
||||
```
|
||||
|
||||
If we look at things a little closer, we realize that this type has the exact same form as the successor functor, `Maybe`.
|
||||
|
||||
```{.haskell}
|
||||
type Nat = Fix Maybe
|
||||
|
||||
zero = In Nothing
|
||||
one = In (Just zero)
|
||||
two = In (Just one)
|
||||
...
|
||||
```
|
||||
|
||||
$$
|
||||
\text{Nat} \text{ or } \omega := \mathcal Y(S)
|
||||
$$
|
||||
|
||||
Looking back, the statement $X = 1 + X$ is identical to the statement "*X* is the fixed point of the successor", as its definition implies. Using the latter form, we can define a bijection to and from `Int` (which I'm regarding as only containing positive integers) as
|
||||
|
||||
```{.haskell}
|
||||
toNat :: Int -> Nat
|
||||
toNat 0 = In Nothing
|
||||
toNat x = In $ Just $ toNat $ x - 1 -- the successor Nat of the predecessor of x
|
||||
|
||||
fromNat :: Nat -> Int
|
||||
fromNat (In Nothing) = 0
|
||||
fromNat (In (Just x)) = 1 + fromNat x -- ...of the predecessor of In (Just x)
|
||||
|
||||
-- fromNat . toNat = id :: Int -> Int -- for positive integers
|
||||
-- toNat . fromNat = id :: Nat -> Nat
|
||||
```
|
||||
|
||||
These bijections are different from the ones we have seen thus far. Both of them are defined recursively, just like our functorial `Fix`. Consequently, rather than exhausting the possible cases, it relies on deconstructing `Nat`s and arithmetic on `Int`s.
|
||||
|
||||
```{.haskell}
|
||||
succNat :: Nat -> Nat
|
||||
succNat x = In (Just x)
|
||||
-- succNat x = In . Just
|
||||
|
||||
succInt :: Int -> Int
|
||||
succInt x = x + 1
|
||||
```
|
||||
|
||||
There's actually no way to know whether the functions above do the same thing, at least without knowing more about `Int`. At best, they afford us a way to bootstrap a way to display numbers that isn't iterated `Just`s.
|
||||
|
||||
|
||||
Functors from Bifunctors
|
||||
------------------------
|
||||
|
||||
If their first argument of a bifunctor is fixed, then what remains is a functor.
|
||||
|
||||
```{.haskell}
|
||||
forall a. Either a :: * -> *
|
||||
```
|
||||
|
||||
But since this is a functor, we can apply `Fix` to it.
|
||||
|
||||
```{.haskell}
|
||||
type Any a = Fix (Either a)
|
||||
--Any' a ~ (Either a (Any' a))
|
||||
|
||||
leftmost x = In (Left x)
|
||||
oneRight x = In (Right (leftmost x))
|
||||
twoRight x = In (Right (oneRight x))
|
||||
```
|
||||
|
||||
$$
|
||||
\text{Any}(X) := \mathcal Y(E(X,-))
|
||||
= \mathcal Y(X +)
|
||||
$$
|
||||
|
||||
Remember what was said earlier about types which look like nested `Either a`? This type encompasses all of those (granted, interspersed with the `Fix` constructor).
|
||||
|
||||
I used the names "one" and "two" in the above example functions to suggest similarity to Nat above. In fact, if `a ~ ()`, then because we already our functor `Either ()` is isomorphic to `Maybe`, we know that `Any ()` is isomorphic to `Nat`. If the type was `Any Bool`, We could terminate the chains in either `Left True` or `Left False`; it is isomorphic to `(Nat, Bool)`. In general, `Any a` is isomorphic to `(Nat, a)`.
|
||||
|
||||
```{.haskell}
|
||||
toAny :: (Nat, a) -> Any a
|
||||
toAny (In Nothing, x) = In (Left x)
|
||||
toAny (In (Just n), x) = In (Right (toAny (n, x)))
|
||||
|
||||
-- fromAny :: Any a -> (Nat, a)
|
||||
-- fromAny . toAny = id :: (Nat, a) -> (Nat, a)
|
||||
-- toAny . fromAny = id :: Any a -> Any a
|
||||
```
|
||||
|
||||
Note how fixing a sum-like object gives a product-like object.
|
||||
|
||||
|
||||
### What about Products?
|
||||
|
||||
If we can do it for sums, we should be able to do it for products. However, unlike sums, products don't have a builtin exit point. For `Either`, all we need to do is turn `Left` and be done, but fixing a product goes on endlessly.
|
||||
|
||||
```{.haskell}
|
||||
type Stream a = Fix ((,) a)
|
||||
-- Stream a ~ (a, Stream a)
|
||||
|
||||
repeatS x = In (x, repeatS x)
|
||||
-- = In (x, In (x, ...
|
||||
```
|
||||
|
||||
$$
|
||||
\text{Stream}(X) := \mathcal Y(P(X,-)) =
|
||||
\mathcal Y(X \times)
|
||||
$$
|
||||
|
||||
Unlike `Void`, whose "definition" would require an infinite chain of constructors, we can actually create values of this type, as seen above with the definition of `repeatS.` Fortunately, Haskell is lazy and doesn't actually try to evaluate the entire result.
|
||||
|
||||
If fixing a sum produced a product object with a natural number, then it stands to reason that fixing a product will produce an exponential object with a natural number. By this token, we should try to verify that $\text{Stream}(X) \cong X^{\omega} = \text{Nat} \rightarrow X$. Intuitively, we have an value of type *X* at every position of a stream defined by a natural number (the input type). This specifies every possible position.
|
||||
|
||||
```{.haskell}
|
||||
toStream :: (Nat -> a) -> Stream a
|
||||
toStream f = toStream' (In Nothing) where
|
||||
toStream' n = In (f n, toStream' (In (Just n)))
|
||||
|
||||
fromStream :: Stream a -> (Nat -> a)
|
||||
fromStream (In (x,y)) = \n -> case n of
|
||||
In Nothing -> x
|
||||
In (Just m) -> fromStream y m
|
||||
|
||||
-- fromStream . toStream = id :: (Nat -> a) -> (Nat -> a)
|
||||
-- toStream . fromStream = id :: Stream a -> Stream a
|
||||
```
|
||||
|
||||
Notice that in `toStream`, an equal number of "layers" are added to `Nat` and `Stream` with the `In` constructor, and in `fromStream`, an equal number of layers are peeled away.
|
||||
|
||||
As you might be able to guess, analogously to `Bool` functions representing `Pairs`, this means that `Stream`s are represented by functions from `Nat`.
|
||||
|
||||
|
||||
### And Exponents?
|
||||
|
||||
Fixing exponents is a bad idea. For one, we'd need to pick a direction to fix since exponentiation, even on types, is obviously non-commutative. Let's choose to fix the destination, just to match with the left-to-right writing order.
|
||||
|
||||
```{.haskell}
|
||||
type NoHalt a = Fix ((->) a)
|
||||
```
|
||||
|
||||
$$
|
||||
\text {NoHalt}(X) := \mathcal Y((-)^X)
|
||||
= \mathcal Y(X \rightarrow)
|
||||
$$
|
||||
|
||||
There is no final destination since it gets destroyed by `Fix`. If we regard functions as computations, this is one that never finishes (hence the name `NoHalt`). Haskell always wants functions to have a return type, so this suggests the type is unpopulated like `Void`.
|
||||
|
||||
|
||||
Closing
|
||||
-------
|
||||
|
||||
Function types are one of the reasons Haskell (and functional programming) is so powerful, and fixed-point types allow for recursive data structures. Functions on these structures are recursive, which are dangerous since they can generate paradoxes
|
||||
|
||||
```{.haskell}
|
||||
void = fix V :: Void -- old
|
||||
neverend = fix (id :: Void -> Void) -- new
|
||||
infinity = fix succNat :: Nat
|
||||
neverend' = fix (id :: Nat -> Nat)
|
||||
```
|
||||
|
||||
In practice, were any of these values actually present and strict computation were performed on them, the program would never halt. The [ `absurd`
|
||||
](https://hackage.haskell.org/package/void-0.6.1/docs/src/Data-Void.html#absurd) function the older `Data.Void` demonstrates this. The [newer version](https://hackage.haskell.org/package/base-4.16.2.0/docs/src/Data.Void.html) merely raises an exception and obviates giving `Void a` constructor altogether (though it still has an `id`).
|
||||
|
||||
Since we are now posed to look at polynomials as types and generate potentially infinite types, the next post will feature a heavy, perhaps surprising focus on algebra.
|
||||
|
||||
Haskell code used in this post can be found [here]().
|
||||
|
||||
|
||||
### A Note about Categories
|
||||
|
||||
Category theory is a complex and abstract field (that I myself struggle to apprehend) which is difficult to recommend to total novices. I would, however, like to point out something interesting regarding its definition of product and coproduct (sum).
|
||||
|
||||
![]()
|
||||
|
||||
In these diagrams, following arrows in one way should be equivalent to following arrows in another way. If you recall the exponential laws and rewrite them in their arrowed form, this definition makes a bit more sense.
|
||||
|
||||
$$
|
||||
\begin{gather*}
|
||||
\begin{matrix}
|
||||
\stackrel{f \vphantom{1 \over 2}}{(X \times Y)^{Z}} \vphantom{} \cong
|
||||
\vphantom{} \stackrel{f_1 \times f_2 \vphantom{1 \over 2}}{X^Z \times Y^Z} \\[0.5em]
|
||||
f: Z \rightarrow(X \times Y) \\[0.5em]
|
||||
f_1: Z \rightarrow X \\[0.5em]
|
||||
f_2: Z \rightarrow Y \\[0.5em]
|
||||
\pi_1,\pi_2= \tt fst, snd
|
||||
\end{matrix}
|
||||
&&
|
||||
\begin{matrix}
|
||||
\stackrel{g_1\times g_2 \vphantom{1 \over 2}}{Z^{X} \times Z^Y} \vphantom{} \cong
|
||||
\vphantom{} \stackrel{g \vphantom{1 \over 2}}{Z^{X + Y}} \\[0.5em]
|
||||
g: (X+Y) \rightarrow Z \\[0.5em]
|
||||
g_1:X \rightarrow Z \\[0.5em]
|
||||
g_2: Y \rightarrow Z \\[0.5em]
|
||||
\iota_1,\iota_2 = \tt Left, Right
|
||||
\end{matrix}
|
||||
\end{gather*}
|
||||
$$
|
||||
|
||||
Category theory is more general, since morphisms (function arrows) need not correspond to objects (types) in the category. The (pseudo-)category in which Haskell types live is "cartesian closed" (products and exponential objects exist), which lets more general categories maintain their flexibility, but which seem like arbitrary distinctions without mentioning counterexamples.
|
||||
|
||||
|
||||
### Additional Links
|
||||
|
||||
- [Representable Functors](https://bartoszmilewski.com/2015/07/29/representable-functors/) (from Bartosz Milewski's Categories for Programmers)
|
||||
|
||||
- An interesting note at the end of this article mentions an interesting formal manipulation: a functor $F(X)$ is represented by $\log_X( F(X) )$
|
||||
444
type_alg/3/index.qmd
Normal file
444
type_alg/3/index.qmd
Normal file
@ -0,0 +1,444 @@
|
||||
---
|
||||
format:
|
||||
html:
|
||||
html-math-method: katex
|
||||
jupyter: python3
|
||||
---
|
||||
|
||||
|
||||
Type Algebra and You, Part 3: Combinatorial Types
|
||||
=================================================
|
||||
|
||||
We know that types have the potential to behave like numbers; the laws for addition, multiplication, and exponentiation are satisfied. However, in the previous post, we also looked at some potentially dangerous inductive, self-referential types where arithmetic tries to include the infinite: `Nat`, the natural numbers; Any, a chain of `Right`s followed by a `Left` wrapped around a value; and `Stream`, an infinite chain of values. This post will focus on better-behaved types which can be made to resemble familiar objects from algebra.
|
||||
|
||||
|
||||
Geometric Lists
|
||||
---------------
|
||||
|
||||
With `Stream`, there is some difficulty in peeking into the values one contains. We can use its representation from `Nat` to glimpse one value at a time, but we can't group an arbitrary number of them into a collection without it on forever. On the other hand, types like `Any` which include a sum are allowed to terminate. We can combine a sum and a product to create a new object, a `List`. Haskell comes with its own implementation, but re-implementing them is a common exercise.
|
||||
|
||||
```{.haskell}
|
||||
data List a = Null | Cons a (List a)
|
||||
```
|
||||
|
||||
$$
|
||||
\begin{matrix}
|
||||
\scriptsize \text{A list of X is} &
|
||||
\scriptsize \text{empty} &
|
||||
\scriptsize \text{or} &
|
||||
\scriptsize \text{an X} &
|
||||
\scriptsize \text{and} &
|
||||
\scriptsize \text{a list of X} &
|
||||
\\
|
||||
L(X) = & 1 & + & X & \times & L(X)
|
||||
\end{matrix}
|
||||
\\ \ \\
|
||||
\begin{align*}
|
||||
L(X) &= 1 + X \cdot L(X) \\
|
||||
L(X) - X \cdot L(X) &= 1 \\
|
||||
L(X) \cdot (1 - X) &= 1 \\
|
||||
L(X) &= {1 \over 1 - X} \\
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
Unlike with the infinite types where 0 = 1, nothing goes wrong arithmetically. However, the resulting expression doesn't seem to make sense from the perspective of types. There are no analogues to subtraction or division in type theory. Fortunately, all we need is a trick from calculus, the Taylor series.
|
||||
|
||||
$$
|
||||
\begin{matrix}
|
||||
\scriptsize \text{A list of X is} &
|
||||
\scriptsize \text{empty} &
|
||||
\scriptsize \text{or} &
|
||||
\scriptsize \text{a value of X} &
|
||||
\scriptsize \text{or} &
|
||||
\scriptsize \text{two values of X} &
|
||||
\scriptsize \text{or} &
|
||||
\scriptsize \text{three values of X} &
|
||||
\scriptsize \text{or} &
|
||||
\\
|
||||
L(X) = {1 \over 1 - X} = & 1 & + & X & + & X^2 & + & X^3 & + & ...
|
||||
\end{matrix}
|
||||
$$
|
||||
|
||||
This is the [geometric series](https://en.wikipedia.org/wiki/Geometric_series), which appears throughout algebra and analysis. Interestingly, since `Stream` is more primitive than `List`, this series includes its supremum, $X^\omega$.
|
||||
|
||||
Something to note about the series is that there is an inherent order to the terms. The 1 term is defined first, then *X*, then all subsequent ones recursively. This detail also means that the $X^2$ term is more "ordered" than a typical pair like $X \times X$.
|
||||
|
||||
|
||||
### Another Interpretation
|
||||
|
||||
We can equally as well define `List` in terms of `Fix` and arithmetic. Instead, the definition is
|
||||
|
||||
```{.haskell}
|
||||
data ListU a b = L (Maybe (a,b))
|
||||
type List' a = Fix (ListU a)
|
||||
```
|
||||
|
||||
$$
|
||||
\hat L(X,Z) = 1 + XZ \\
|
||||
L(X) = \mathcal Y(\hat L (X,-))
|
||||
$$
|
||||
|
||||
The expression $1 + XZ$ certainly looks like $1 - X$, if we let *Z* = -1. However, we again run into the problem that -1 doesn't seem to correspond to a type. Worse than that, the actual type we are after is $(1 - X)^{-1} = -1 \rightarrow 1 - X$.
|
||||
|
||||
The latter type seems to resemble a function with -1 on both sides of the arrow. Since the resulting type is intended to resemble a fixed point, we could regard -1 as a sort of "initial type" that gets the list started. Then, we can use the initial definition to replace the occurrence of -1 on the LHS with the entire LHS.
|
||||
|
||||
$$
|
||||
(1 - X)^{-1} = -1 ~\rightarrow~ 1 - X \\ \\
|
||||
\begin{matrix}
|
||||
-1
|
||||
& \rightarrow&
|
||||
1 - X
|
||||
\\&&
|
||||
1 + (-1) \times X
|
||||
& \rightarrow &
|
||||
1 +(1 - X)X
|
||||
\\ & & & &
|
||||
1 + X + (- 1) \times X^2
|
||||
& \rightarrow &
|
||||
1 + X + (1-X)X^2
|
||||
\\ &&&&&& ...
|
||||
\end{matrix}
|
||||
$$
|
||||
|
||||
This interprets the series as a [rewrite rule](https://en.wikipedia.org/wiki/Rewriting#Arithmetic). Each step is shown before distributing and applying the rule once more. In the limit, when there are no more negative signs to rewrite, we recover the original series.
|
||||
|
||||
|
||||
### Convergence
|
||||
|
||||
Just like in math, things go wrong when this expression is applied naively. For example, "a list of bools" would seem to have the type
|
||||
|
||||
$$
|
||||
{\texttt{List Bool}} = L(2) \stackrel{?}{=} {1 \over 1 - 2} = -1
|
||||
$$
|
||||
|
||||
If this were true it would mean
|
||||
|
||||
- `Either () (List Bool)` is isomorphic to Void, since $1 + (-1) = 0$
|
||||
- A list of an object *X* is isomorphic to a function from a list of bools to either a unit or a tuple containing a list of bools and a value of *X*
|
||||
- `List a ≅ List Bool -> Either () (List Bool, a)`
|
||||
|
||||
The first statement is obviously false, since the values `Left ()` and `Right []` will always exist. The second statement is dubious. For it to be true, we must imagine values of *X* can be extracted out of some (possibly infinite) sequence of booleans (the *X* in the tuple), leaving behind enough information to convert the rest of the list (the `List Bool` in the tuple), or be exhausted.
|
||||
|
||||
The latter argument requires far too much information to be generally true, so I am satisfied with my previous description of -1. It should only show up in the context of a fixed point expression, and is only a valid type in a denominator (the return value of a function from -1).
|
||||
|
||||
|
||||
### Iteration
|
||||
|
||||
Consider also a list of lists of any type *X*. Then by iterating lists, it would appear that
|
||||
|
||||
$$
|
||||
\texttt{List (List a)} = L(L(X)) \stackrel{?}{=}
|
||||
{1 \over 1 - {1 \over 1 - X}} = {1 - X \over 1-X-1} = {X - 1 \over X}
|
||||
$$
|
||||
|
||||
If a were `Void`, then the only possible `List` to construct is an empty list ($L(0) = {1 \over 1 - 0} = 1$). Wrapped in another list, the only possible values are those made by repeating this any (natural) number of times. This means that it is itself isomorphic to `Nat`, and we can write a bijection.
|
||||
|
||||
```{.haskell}
|
||||
toLists :: Nat -> List (List Void)
|
||||
toLists Zero = Null
|
||||
toLists (Succ x) = Cons Null (toLists x)
|
||||
-- toLists x = replicate x Null
|
||||
|
||||
fromLists :: List (List Void) -> Nat
|
||||
fromLists Null = Zero
|
||||
fromLists (Cons x xs) = Succ (fromLists xs)
|
||||
-- fromLists = toNat . length
|
||||
|
||||
-- fromLists . toLists = id :: Nat -> Nat
|
||||
-- toLists . fromLists = id :: (List (List Void)) -> (List (List Void))
|
||||
```
|
||||
|
||||
Arithmetically, shows that $\omega = 1 + 1 + 1^2 + ... = {1 \over 1 - 1}$. More directly, the expression has no Taylor series at 0, and the infinitude of possible values spoils attempting to understand higher order terms. This has big implications, since if we wrap this in another layer of lists,
|
||||
|
||||
$$
|
||||
\texttt{List (List (List a))} = L(L(L(X))) \stackrel{?}{=}
|
||||
{1 \over 1 - {1 \over 1 - {1 \over 1 - X}}} = ... = X
|
||||
$$
|
||||
|
||||
we get back to where we started, which is perfectly absurd. The left-hand side can contain far more $X$s than the right-hand side. In other words, by going "past infinity" we have ruined the correspondence between the functor and a series.
|
||||
|
||||
|
||||
A Walk through the Woods
|
||||
------------------------
|
||||
|
||||
All types thus far have been of a linear form: a `List` is analogous to a line segment and a `Stream` is analogous to a ray. All we need to do to go from lists to binary trees is to, for every node, include two sub-structures rather than one.
|
||||
|
||||
```{.haskell}
|
||||
data Tree2 a = Leaf | Branch a (Tree2 a) (Tree2 a)
|
||||
```
|
||||
|
||||
$$
|
||||
\begin{matrix}
|
||||
\scriptsize \text{A 2-tree of X is} \\
|
||||
T_2(X) = & 1 &
|
||||
\scriptsize \text{a leaf} \\
|
||||
& + & \scriptsize \text{or} \\
|
||||
& X \times \phantom{1} &
|
||||
\scriptsize \text{a value of X and} \\
|
||||
& T_2(X) \times \phantom{1} &
|
||||
\scriptsize \text{a left subtree of X and} \\
|
||||
& T_2(X) \phantom{\times 1} &
|
||||
\scriptsize \text{a right subtree of X} &
|
||||
\end{matrix} \\ ~ \\
|
||||
\begin{align*}
|
||||
0 &= 1 - T_2(X) + X \times T_2(X)^2
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
Simple enough, but now we have an equation which is quadratic in our functor $T_2(X)$. At this point, we can try to invoke the quadratic formula. Luckily, one of the solutions has a Taylor series which consists of only positive integers, just like the geometric series.
|
||||
|
||||
$$
|
||||
a = X, b = -1, c = 1
|
||||
\\ \ \\
|
||||
T_2(X) = {1 - \sqrt{1 - 4X} \over 2X} = 1 + X + 2X^2 + 5X^3 + 14X^4 + 42X^5 + ...
|
||||
$$
|
||||
|
||||
The coefficients 1, 1, 2, 5, 14, 42, ... are known as the Catalan numbers ([OEIS A000108](http://oeis.org/A000108)). The OEIS article also pleasantly informs that these count a kind of binary tree on a given number of nodes -- the kind which distinguishes between left and right, as defined by the type.
|
||||
|
||||
::: {}
|
||||
![]()
|
||||
Rooted binary trees on a particular number of nodes.
|
||||
:::
|
||||
|
||||
These coefficients have a direct interpretation. If we have three nodes, then there are three locations to place a value of *X*. In other words, the tree holds an $X^3$. The coefficient of this term tells us that a value from the type 5 tells us the shape of the nodes on the tree.
|
||||
|
||||
Contrast this with the coefficients in the geometric series. They are all 1, telling us that there is only a single shape for a list of a particular size. As consequence, trees are in some sense "larger" than lists. For example, a [breadth-first search](https://en.wikipedia.org/wiki/Breadth-first_search) on a binary tree can collect all values at the nodes into a list. But multiple trees can return the same list, so this is clearly not a bijection. Algebraically, this is because the two series have different coefficients from one another.
|
||||
|
||||
|
||||
### Actually Solving the Polynomial
|
||||
|
||||
We invoked the quadratic formula to solve the polynomial $1 - T_2(X) + X \times T_2(X)^2$, but we haven't justified the ability to use it. Not only are square roots more suspect than division and subtraction, but the quadratic formula has a choice in sign. Combinatorially, an initial condition determines plus or minus, and the square root is interpreted as a [binomial expansion](https://en.wikipedia.org/wiki/Binomial_theorem#Newton's_generalized_binomial_theorem), since $\sqrt{1 - X} = (1 - X)^{1/2}$.
|
||||
|
||||
Naively, the solution seems to have the following interpretation as a type:
|
||||
|
||||
$$
|
||||
{1 - \sqrt{1 - 4X} \over 2X} = (1 - (1 - 4X)^{1/2}) \times (2X)^{-1}
|
||||
= \textcolor{red}{ (1 - ({\scriptsize 1 \over 2} \rightarrow 1-4X)) \times (-1 \rightarrow 2X) }
|
||||
$$
|
||||
|
||||
Compared to the geometric series, this doesn't make sense. -1 appears in the first term of the product, yet originates from the second term. It doesn't seem like the iteration can "get started" like it could previously. We can do some algebra to get terms into the denominator.
|
||||
|
||||
$$
|
||||
\begin{gather*}
|
||||
{1 - \sqrt{1 - 4X} \over 2X}
|
||||
= {2 \over 1 + \sqrt{1 - 4X}}
|
||||
= \textcolor{red}{ 2 \times (-1 \rightarrow1 + ({\scriptsize 1 \over 2} \rightarrow 1 - 4X) ) } \\
|
||||
{1 \over {1 \over 2}(1 + \sqrt{1 - 4X})}
|
||||
= \textcolor{red}{-1 \rightarrow {\scriptsize 1 \over 2} \times (1 + ({\scriptsize 1 \over 2} \rightarrow 1 - 4X) ) } \\
|
||||
\end{gather*}
|
||||
$$
|
||||
|
||||
The first expression keeps a term in the numerator. Without it, the expression would generate half-integer terms which do not make sense as finite types. To combat this, the second places its reciprocal in the denominator alongside the square root, which appears to describe a second rewrite rule. While it looks slightly better, I am still unsure whether this result is interpretable. I am also unsure whether it is significant that 1/2 can be expressed as $2^{-1} = (-1 \rightarrow 2)$. My best guess is that some kind of mutual recursion or alternating scheme is necessary.
|
||||
|
||||
|
||||
### Another Solution?
|
||||
|
||||
There might still be some hope. The earlier OEIS page mentions that the map which generates the Mandelbrot set has, in the limit, a Taylor series with the Catalan numbers as coefficients.
|
||||
|
||||
$$
|
||||
z_{n+1} = z_n^2 + c \\
|
||||
z \mapsto z^2 + c \\ \ \\
|
||||
\begin{matrix}
|
||||
c
|
||||
& \rightarrow&
|
||||
c^2 + c
|
||||
& \rightarrow &
|
||||
(c^2 + c)^2 + c
|
||||
\\ & & & &
|
||||
c^4 + 2c^3 + c^2 + c
|
||||
& \rightarrow &
|
||||
(c^4 + 2c^3 + c^2 + c)^2 + c
|
||||
\\ &&&&&&
|
||||
c^8 + 4c^7 + 6c^6 + 6c^5 + 5c^4 + 2c^3 + c^2 + c
|
||||
\\ &&&&&& ...
|
||||
\end{matrix}
|
||||
$$
|
||||
|
||||
The Mandelbrot map is more similar to a type in which leaves, rather than nodes, contain values. We could convert the expression to suit the trees we've been working with and to correspond better with the definition of the list.
|
||||
|
||||
$$
|
||||
z \mapsto 1 + cz^2 \\
|
||||
i \rightarrow 1-X = (1 - X)^i
|
||||
\\ \ \\
|
||||
\begin{gather*}
|
||||
i ~\rightarrow~ 1 - X ~\rightarrow~ 1 + (X - 2X^2 + X^3) ~\rightarrow~ \\
|
||||
\hline
|
||||
\vphantom{x^{x^{x^x}}}
|
||||
1 + X + (2X^2 - 3X^3 + 2X^4) &\rightarrow& ... & \textcolor{red}{\times} \\
|
||||
1 + X + 2X^2 + 5X^3 - 6X^4 -4X^5 + 12X^6 -8X^7 + 2X^8 &
|
||||
\rightarrow & ... & \textcolor{red}{\times}
|
||||
\end{gather*}
|
||||
$$
|
||||
|
||||
This interpretation is worse, though. In the case of lists, either the initial expression ($1 - X$) or the most recent expression could replace any occurrence of -1. Here, neither work, though one appears to do so initially. Finally, the resulting expression is not the Taylor series of $(1-X)^i$.
|
||||
|
||||
|
||||
Other Trees
|
||||
-----------
|
||||
|
||||
The OEIS article for the Catalan numbers also mentions that the sequence counts "ordered rooted trees with *n* nodes, not including the root". In this kind of tree, a node has any number of children rather than just two.
|
||||
|
||||
We can construct this type of tree by as the product of a value at the node and the node's children (possibly none, but themselves trees), called the *subforest*. A *forest* is just a list of trees. This defines trees and forests in a mutually recursive way that easily lends itself to algebraic manipulation. Haskell's `Data.Graph` agrees with the following definition
|
||||
|
||||
```{.haskell}
|
||||
data Tree a = Node a (List (Tree a))
|
||||
type Forest a = List (Tree a)
|
||||
```
|
||||
|
||||
$$
|
||||
\begin{matrix}
|
||||
\scriptsize \text{A tree of $X$ is} &
|
||||
\scriptsize \text{a value of $X$} &
|
||||
\scriptsize \text{and} &
|
||||
\scriptsize \text{a forest of $X$} \\
|
||||
T(X) = &
|
||||
X &
|
||||
\times & F(X)
|
||||
\end{matrix} \\
|
||||
\begin{matrix}
|
||||
\scriptsize \text{A forest of $X$} &
|
||||
\scriptsize \text{is} &
|
||||
\scriptsize \text{a list of trees of $X$}
|
||||
\\
|
||||
F(X)
|
||||
& = &
|
||||
L(T(X))
|
||||
\\ & = &
|
||||
{1 \over 1-T(X)}
|
||||
\\ & = &
|
||||
{1 \over 1-X \times F(X)}
|
||||
\end{matrix}
|
||||
\\ \ \\
|
||||
\begin{align*}
|
||||
(1 - X \times F(X)) \times F(X) = 1 \\
|
||||
-1 + F(X) - X \times F(X)^2 = 0 \\
|
||||
1 - F(X) + X \times F(X)^2 = 0 \\
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
The resulting equation for *F*(*X*) is the same as the one featured earlier for binary trees, so we know that it generates the same series.
|
||||
|
||||
$$
|
||||
F(X) = T_2(X) = {1 - \sqrt{1 - 4X} \over 2X} = 1 + X + 2X^2 + 5X^3 + 14X^4 + 42X^5 + ...
|
||||
$$
|
||||
|
||||
|
||||
### Binary Bijections
|
||||
|
||||
Two finite types were isomorphic when their algebraic equivalents were equal (e.g., `Bool ≅ Maybe ()` because 2 = 1 + 1). Since these structures have the same expression in *X*, we might assume that the functors are isomorphic with respect to the type *X* they contain.
|
||||
|
||||
For this to actually be the case, there should be a bijection between the two functors, else "isomorphic" ceases to be the proper word. With a bit of clever thinking, we can consider the left (or right) children in a binary tree as siblings, and construct functions that reframe the nodes accordingly.
|
||||
|
||||
::: {}
|
||||
![]()
|
||||
An example of the correspondence between binary trees and "bushes", or general trees. A root is shown for the latter to make it better resemble a tree. Leaves and empty subforests not shown.
|
||||
:::
|
||||
|
||||
```{.haskell}
|
||||
treeToForest :: Tree2 a -> Forest a
|
||||
treeToForest Leaf = Null
|
||||
treeToForest (Branch x l r) = Cons (Node x (treeToForest r)) (treeToForest l)
|
||||
|
||||
forestToTree :: Forest a -> Tree2 a
|
||||
forestToTree Null = Leaf
|
||||
forestToTree (Cons (Node x fs) ns) = Branch x (forestToTree ns) (forestToTree fs)
|
||||
|
||||
-- forestToTree . treeToForest = id :: Tree2 a -> Tree2 a
|
||||
-- treeToForest . forestToTree = id :: Forest a -> Forest a
|
||||
```
|
||||
|
||||
A weaker equality applies in combinatorics: two generating functions are equal if their coefficients are equal. If a closed algebraic expression is known, then it is sufficient to compare them. Instead, we have "generating functors", which satisfy the same algebraic equation, have the same coefficients as a series, and most strongly, are isomorphic in the sense that they have a bijection.
|
||||
|
||||
|
||||
### Ternary and Larger-order Trees
|
||||
|
||||
Even though the general tree structure used above seems to encompass ternary trees, *strictly* ternary trees have a bit more freedom. In a general tree, a subtree can only appear further along a list of children if all prior elements of the list are filled. On the other hand, in ternary trees, subtrees can occupy any of the three possible positions.
|
||||
|
||||
If the binary trees functor satisfy a quadratic, then it stands to reason that ternary trees should satisfy a cubic. And they do:
|
||||
|
||||
```{.haskell}
|
||||
data Tree3 a = Leaf | Node a (Tree3 a) (Tree3 a) (Tree3 a)
|
||||
```
|
||||
|
||||
$$
|
||||
\begin{matrix}
|
||||
\scriptsize \text{A 3-tree of X is} \\
|
||||
T_3(X) = & 1 &
|
||||
\scriptsize \text{a leaf} \\
|
||||
& + & \scriptsize \text{or} \\
|
||||
& X \times \phantom{1} &
|
||||
\scriptsize \text{a value of X and} \\
|
||||
& T_3(X) \times \phantom{1} &
|
||||
\scriptsize \text{a left subtree of X and} \\
|
||||
& T_3(X) \times \phantom{1} &
|
||||
\scriptsize \text{a center subtree of X} & \\
|
||||
& T_3(X) \phantom{\times 1} &
|
||||
\scriptsize \text{a right subtree of X} &
|
||||
\end{matrix} \\ ~ \\
|
||||
\begin{align*}
|
||||
0 &= 1 - T_3(X) + X \times T_3(X)^3
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
We can solve a cubic with the [cubic formula](https://en.wikipedia.org/wiki/Cubic_equation#Cardano's_formula), but it yields a result which is less comprehensible than the one for binary trees.
|
||||
|
||||
$$
|
||||
T_3^3 +pT_3 + q = 0 \\ p = -1/X, q =1/X
|
||||
\\ \ \\
|
||||
\begin{align*}
|
||||
T_3(X) &= \sqrt[3]{- {1 \over 2X} + \sqrt{{1 \over 4X^2} - {1\over 27X^3}}} + \sqrt[3]{- {1 \over 2X} - \sqrt{{1 \over 4X^2} - {1\over 27X^3}}} \\
|
||||
&= {1 \over X}\left( \sqrt[3]{- {X^2 \over 2} + \sqrt{{X^4 \over 4} - {X^3 \over 27}}} + \sqrt[3]{- {X^2 \over 2} - \sqrt{{X^4 \over 4} - {X^3\over 27}}} \right) \\
|
||||
\end{align*}
|
||||
\\ \ \\
|
||||
= 1 + X + 3X^2 + 12X^3 + 55X^4 + 273X^5 +...
|
||||
$$
|
||||
|
||||
The coefficients here are enumerated by [OEIS A001764](http://oeis.org/A001764), which doesn't have a name. Cube roots now feature prominently in the generating functor. This expression is much too large for me to even attempt rewriting in "functional" form. WolframAlpha refuses to calculate the series from this expression within standard computation time. Sympy gives an almost-correct series, but which has unsimplified roots of *i* and has half-integer powers of *X*.
|
||||
|
||||
A series better-suited to computation, based on angle trisection, is adapted from the OEIS here:
|
||||
|
||||
$$
|
||||
T_3(X^2) = {2 \over X \sqrt{3}} \cdot
|
||||
\sin \left( {1\over 3}\arcsin \left(3\cdot{X\sqrt {3} \over 2} \right) \right)
|
||||
$$
|
||||
|
||||
This expression invokes not only irrationals, but trigonometric functions, which are even harder, if not impossible to understand as types. The situation gets even worse for higher-order trees, where [no algebraic formula for the solutions exists](https://en.wikipedia.org/wiki/Abel%E2%80%93Ruffini_theorem). Without being sure how to interpret the simplest case with only square roots, I am unsure whether this has implications for rewrite rules.
|
||||
|
||||
|
||||
Closing
|
||||
-------
|
||||
|
||||
This hardly scratches the surface of constructible datatypes. As a final example, a list which could include pairs as well as singletons of X is counted by the Fibonacci numbers:
|
||||
|
||||
```{.haskell}
|
||||
data FibList a = NullF | Single a (List a) | Double (a,a) (List a)
|
||||
```
|
||||
|
||||
$$
|
||||
\begin{matrix}
|
||||
F(X) = 1 + X \times F(X) + X^2 \times F(X)
|
||||
\end{matrix}
|
||||
\\ \ \\
|
||||
\begin{align*}
|
||||
1 &= F(X) - X \cdot F(X) - X^2 \cdot F(X) \\
|
||||
1 &= F(X) \cdot(1 - X - X^2) \\
|
||||
F(X) &= {1 \over 1 - X - X^2} \\
|
||||
&=1 + X + 2X^2+3X^3 +5X^4 +...
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
However, I do not feel that it is useful to continue plumbing for types to express as a series. At this basic level, there are still mysteries I am not equipped to solve.
|
||||
|
||||
I am the most interested in generalizing rewrite rule-style types. The presence of the square root seems almost opaque. Even the similar Mandelbrot set rule doesn't cooperate with the "recursive" interpretation of -1. This rule is given in terms of the variable *z*, but my goal is to is to give an intermediate meaning to -1, not to introduce intermediate "dummy" variables.
|
||||
|
||||
I am also interested in extending these ideas to bifunctors (polynomials in multiple variables) and bi-fixes (fixing multiple terms at once, which admittedly the binary tree does already). Perhaps I may explore these ideas in the future.
|
||||
|
||||
Haskell code used in this post can be found [here](). Tree diagrams made with GeoGebra.
|
||||
|
||||
|
||||
### Links
|
||||
|
||||
- [Haskell/Zippers](https://en.wikibooks.org/wiki/Haskell/Zippers) (from Wikibooks)
|
||||
- Another formal manipulation: using the derivative operator on algebraic datatypes. Since the Taylor series is defined in terms of derivatives, there may be a completely type-theoretical characterization that doesn't rely on "borrowing ideas" from calculus.
|
||||
- [Generating functions for generating trees](https://www.sciencedirect.com/science/article/pii/S0012365X01002503?via%3Dihub) (paper from ScienceDirect)
|
||||
- This paper references enumerates structures by rewrite rules. It also presents a zoo of combinatorial objects and their associated generating functions.
|
||||
Loading…
x
Reference in New Issue
Block a user