2025-08-08 04:22:49 -05:00

12 lines
20 KiB
JSON

{
"hash": "b5c0c433960dde69df644a132dc2e115",
"result": {
"engine": "jupyter",
"markdown": "---\ntitle: \"Polynomial Counting 5: Pentamerous multiplication\"\ndescription: |\n Arithmetic in non-geometric integral systems and surprisingly regular errors therein.\nformat:\n html:\n html-math-method: katex\ndate: \"2021-02-12\"\ndate-modified: \"2025-02-12\"\njupyter: python3\ncategories:\n - algebra\n - python\n---\n\n<style>\n .cell-output-display .figure {\n text-align: center;\n }\n\n .figure-img {\n max-width: 512px;\n object-fit: contain;\n height: 100%;\n }\n\n .image-wide {\n width: 256px;\n image-rendering: pixelated;\n }\n</style>\n\n\nThis post assumes you have read [the first](../1), which introduces generalized polynomial counting.\n\nOne layer up from counting is arithmetic.\nWe've done a little arithmetic using irrational expansions in pursuit of counting, but maybe we should investigate it a bit.\n\n\nArithmetical Algorithms\n-----------------------\n\nAddition is a fairly simple process for positional systems: align the place values, add each term together,\n then apply the carry as many times as possible.\nWithout a carry rule, this can be approached formally by treating the place values as a sequence\n $b_n$ and gathering terms:\n\n$$\n\\begin{gather*}\n \\phantom{+~} \\overset{b_2}{1} \\overset{b_1}{2} \\overset{b_0}{3} &\n \\phantom{+~} 1b_2 + 2b_1 + 3b_0 \\\\\n \\underline{\n +\\\n \\smash{\n \\overset{\\phantom{b_0}}4\n \\overset{\\phantom{b_0}}5\n \\overset{\\phantom{b_0}}6\n }\n \\vphantom,\n } &\n \\underline{\n +\\\n 4b_2 + 5b_1 + 6b_0 \\vphantom,\n } \\\\\n \\phantom{+~}\n \\smash{\n \\overset{\\phantom{b_0}}5\n \\overset{\\phantom{b_0}}7\n \\overset{\\phantom{b_0}}9\n } &\n \\phantom{+~} 5b_2 + 7b_1 + 9b_0\n\\end{gather*}\n$$\n\nMultiplication is somewhat trickier.\nIts validity follows from the interpretation of an expansion as a polynomial.\nPolynomial multiplication itself is equivalent to\n [*discrete convolution*](https://en.wikipedia.org/wiki/Convolution#Discrete_convolution).\n\n$$\n\\begin{align*}\n &\\begin{matrix}\n \\phantom{\\cdot~}\n 111_x \\\\\n \\underline{\\cdot\\ \\phantom{1}21_x\\vphantom,} \\\\\n \\phantom{\\cdot \\ 0}\n 111_{\\phantom{x}} \\\\\n \\underline{\\phantom{\\cdot\\ } 2220_{\\phantom{x}} \\vphantom,} \\\\\n \\phantom{\\cdot\\ }\n 2331_{\\phantom{x}}\n \\end{matrix}\n &\\qquad&\n \\begin{gather*}\n (x^2 + x + 1)(2x + 1) \\\\ \\\\\n = \\phantom{2x^3 + } \\phantom{2}x^2 + \\phantom{2}x + 1 \\\\\n + \\phantom{.} 2x^3 + 2x^2 + 2x \\phantom{+ 1}\\\\\n = 2x^3 + 3x^2 + 3x + 1\n \\end{gather*}\n &\\qquad&\n \\begin{gather*}\n [1,1,1] * [2,1] \\\\\n \\begin{matrix}\n \\textcolor{blue}0 &\\textcolor{red}1 & \\textcolor{red}1 & \\textcolor{red}1 & \\textcolor{blue}0 &\\\\\n & & &1 & 2 & =~1\\\\\n & & 1 & 2 & &=~3\\\\\n & 1 & 2 & & &=~3\\\\\n 1 & 2 & & & & =~2\n \\end{matrix}\n \\end{gather*}\n\\end{align*}\n$$\n\nThe left equation shows familiar multiplication, the middle equation is the same product when expressed as polynomials,\n and the right equation shows this product obtained by convolution.\nNote that \\[2, 1\\], the second argument, is reversed when performing the convolution.\n\nWithout carrying, multiplication and addition are base-agnostic.\nWhen doing arithmetic in a particular base, we should obtain the same result even if we perform the same operations in another base.\n\n$$\n\\begin{array}{c}\n 18_{10} \\cdot 5_{10} = 10010_2 \\cdot 101_2\n \\\\ \\hline \\\\\n \\begin{gather*}\n &\n \\begin{matrix}\n \\phantom{\\cdot~}\n 18_{10} \\\\\n \\underline{\\cdot\\\n \\phantom{1}5_{10}\\vphantom,} \\\\\n \\phantom{\\cdot~}90_{10} \\\\ \\\\\n =~1011010_2\n \\end{matrix}\n &&\n \\begin{matrix}\n [1,0,0,1,0] * [1,0,1] \\\\ \\\\\n \\begin{matrix}\n \\textcolor{blue}0 & \\textcolor{blue}0 &\\textcolor{red}1 & \\textcolor{red}0 & \\textcolor{red}0\n & \\textcolor{red}1 & \\textcolor{red}0 & \\textcolor{blue}0 & \\textcolor{blue}0 &\\\\\n & & & & & &1 & 0 & 1 & =~0\\\\\n & & & & &1 & 0 & 1 & & =~1\\\\\n & & & &1 & 0 & 1 & & & =~0\\\\\n & & &1 & 0 & 1 & & & & =~1\\\\\n & &1 & 0 & 1 & & & & & =~1\\\\\n & 1 & 0 & 1 & & & & & & =~0\\\\\n 1 & 0 & 1 & & & & & & & =~1\\\\\n \\end{matrix}\n \\end{matrix}\n \\end{gather*}\n\\end{array}\n$$\n\nThis shows that regardless of whether we multiply eighteen and five together in base ten or two,\n we'll get the same string of digits if afterward everything is rendered in the same base.\nFortunately in this instance, we don't have to worry about any carries in the result.\n\n\nTwo Times Tables\n----------------\n\nThe strings \"10010\" and \"101\" do not contain adjacent \"1\"s, so they can also be interpreted as\n Zeckendorf expansions (of ten and four, respectively).\nThis destroys their correspondence to polynomials, so their product by convolution,\n as a Zeckendorf expansion (when returned to canonical form) is not the product of ten and four.\n\n$$\n\\begin{gather*}\n [1,0,0,1,0] * [1,0,1] = 1011010 \\\\\n 10\\textcolor{red}{11}010_{\\text{Zeck}}\n = 1\\textcolor{red}{1}00010_{\\text{Zeck}}\n = \\textcolor{blue}{11}00010_{\\text{Zeck}}\n = \\textcolor{blue}{1}0000010_{\\text{Zeck}} = 36_{10} \\\\\n \\text{Zeck}(10_{10}) * \\text{Zeck}(4_{10})\n = 36_{10} \\neq 40_{10} = 10_{10} \\cdot 4_{10}\n\\end{gather*}\n$$\n\nWe can express this operation for general integers *x* and *y* as\n\n$$\nx \\odot_\\text{Zeck} y = \\text{Unzeck}(\\text{Zeck}(x) * \\text{Zeck}(y))\n$$\n\nwhere \"Zeck\" expands an integer into its Zeckendorf expansion and \"Unzeck\" signifies the process of\n re-interpreting the string as an integer.\nWe can do this because for every integer, a canonical expansion exists.\nFurther, since this operation is defined via convolution (which is commutative), it is also commutative.\n\n::: {#bb7589d5 .cell execution_count=1}\n``` {.python .cell-code}\nfrom itertools import count, chain, islice, takewhile\nimport numpy as np\n\ndef fibs():\n i = 1\n j = 1\n while True:\n yield i\n t = i + j\n j = i\n i = t\n\ndef zeck(n: int) -> list[int]:\n fibs_ = list(takewhile(lambda x: x <= n, fibs()))[::-1]\n ret = []\n\n for i in fibs_:\n digit, n = divmod(n, i)\n ret.append(digit)\n\n return ret or [0]\n\ndef unzeck(ns: list[int]) -> int:\n return sum(map(lambda x, y: x * y, reversed(ns), fibs()))\n\ndef zeck_times(x: int, y: int) -> int:\n return unzeck(np.convolve(zeck(x), zeck(y), \"full\"))\n```\n:::\n\n\nWe can then build a times table by experimentally multiplying.\nThe expansions for zero and one are invariably the strings \"0\" (or the empty string!) and \"1\".\nWhen a sequence has length one, convolution degenerates to term-by-term multiplication.\nThus, convolution by \"0\" produces a sequence of \"0\"s, and convolution by \"1\" produces the same sequence.\n\nIgnoring those rows, the table is:\n\n::: {#d148b53b .cell execution_count=2}\n``` {.python .cell-code code-fold=\"true\"}\nfrom IPython.display import Markdown\nfrom tabulate import tabulate\n\nMarkdown(tabulate(\n [[zeck_times(i, j) for i in range(1, 10)] for j in range(2, 10)],\n headers=[\"$\\\\odot_\\\\text{Zeck}$\", *range(2, 10)]\n))\n```\n\n::: {.cell-output .cell-output-display .cell-output-markdown execution_count=2}\n $\\odot_\\text{Zeck}$ 2 3 4 5 6 7 8 9\n--------------------- --- --- --- --- --- --- --- ---\n 2 3 5 7 8 10 11 13 15\n 3 5 8 11 13 16 18 21 24\n 4 7 11 15 18 22 25 29 33\n 5 8 13 18 21 26 29 34 39\n 6 10 16 22 26 32 36 42 48\n 7 11 18 25 29 36 40 47 54\n 8 13 21 29 34 42 47 55 63\n 9 15 24 33 39 48 54 63 72\n:::\n:::\n\n\nThis resembles a sequence in the OEIS ([A101646](https://oeis.org/A101646)), where it mentions the defining operation\n is sometimes called the \"arroba\" product.\n\n### Correcting for Errors\n\nThe difference between the actual product and the false product can be interpreted as an error endemic to Zeckendorf expansions.\nIf assigned to the symbol $\\oplus_\\text{Zeck}$, then the normal product can be recovered as\n $mn = m \\odot_\\text{Zeck} n + m \\oplus_\\text{Zeck} n$.\n\nWe can then tabulate these errors just like we did our \"products\":\n\n::: {#2c593bb2 .cell execution_count=3}\n``` {.python .cell-code code-fold=\"true\"}\nzeck_error = lambda x, y: x*y - zeck_times(x, y)\n\nMarkdown(tabulate(\n [[j] + [zeck_error(i, j) for i in range(2, 10)] for j in range(2, 10)],\n headers=[\"$\\\\oplus_\\\\text{Zeck}$\", *range(2, 10)]\n))\n```\n\n::: {.cell-output .cell-output-display .cell-output-markdown execution_count=3}\n $\\oplus_\\text{Zeck}$ 2 3 4 5 6 7 8 9\n---------------------- --- --- --- --- --- --- --- ---\n 2 1 1 1 2 2 3 3 3\n 3 1 1 1 2 2 3 3 3\n 4 1 1 1 2 2 3 3 3\n 5 2 2 2 4 4 6 6 6\n 6 2 2 2 4 4 6 6 6\n 7 3 3 3 6 6 9 9 9\n 8 3 3 3 6 6 9 9 9\n 9 3 3 3 6 6 9 9 9\n:::\n:::\n\n\nNotice that the errors seem to be grouped together in rectangular blocks.\nBecause this operation is commutative, the shapes of the rectangles must agree along the main diagonal, where they are squares.\n\n### Fibonacci Runs\n\nThe shape of the rectangular blocks is somewhat odd.\nWe can assess the number of terms the series \"hangs\" before progressing by looking at the\n [*run lengths*](https://en.wikipedia.org/wiki/Run-length_encoding).\n\n:::: {.row layout-ncol=\"1\"}\n::: {.row}\n\n::: {#74bfd62a .cell execution_count=4}\n``` {.python .cell-code code-fold=\"true\"}\nfrom IPython.display import Math\nfrom typing import Generator\n\ndef run_lengths(xs: Generator) -> Generator:\n last = next(xs)\n run_length = 1\n for i in xs:\n if i != last:\n yield run_length\n last = i\n run_length = 1\n else:\n run_length += 1\n\nshow_trunc_list = lambda xs, n: \"[\" + \", \".join(chain(islice(map(str, xs), n), (\"...\",))) + \"]\"\n\nMath(\n \"RL([i \\\\oplus 2]_{i}) =\"\n + f\"RL({show_trunc_list((zeck_error(i, 2) for i in count(0)), 10)}) =\"\n + show_trunc_list(run_lengths(zeck_error(i, 2) for i in count(0)), 10)\n)\n```\n\n::: {.cell-output .cell-output-display .cell-output-markdown execution_count=4}\n$\\displaystyle RL([i \\oplus 2]_{i}) =RL([0, 0, 1, 1, 1, 2, 2, 3, 3, 3, ...]) =[2, 3, 2, 3, 3, 2, 3, 2, 3, 3, ...]$\n:::\n:::\n\n\n:::\n\n::: {.row}\n\n::: {#58a28b71 .cell execution_count=5}\n``` {.python .cell-code code-fold=\"true\"}\nMath(\n \"RL([i \\\\oplus 5]_{i}) =\"\n + f\"RL({show_trunc_list((zeck_error(i, 5) for i in count(0)), 10)}) =\"\n + show_trunc_list(run_lengths(zeck_error(i, 2) for i in count(0)), 10)\n)\n```\n\n::: {.cell-output .cell-output-display .cell-output-markdown execution_count=5}\n$\\displaystyle RL([i \\oplus 5]_{i}) =RL([0, 0, 2, 2, 2, 4, 4, 6, 6, 6, ...]) =[2, 3, 2, 3, 3, 2, 3, 2, 3, 3, ...]$\n:::\n:::\n\n\n:::\n\n::: {.row}\n\n::: {#5a3b08bf .cell execution_count=6}\n``` {.python .cell-code code-fold=\"true\"}\nMath(\n \"RL([i \\\\oplus i]_{i}) =\"\n + f\"RL({show_trunc_list((zeck_error(i, i) for i in count(0)), 10)}) =\"\n + show_trunc_list(run_lengths(zeck_error(i, i) for i in count(0)), 10)\n)\n```\n\n::: {.cell-output .cell-output-display .cell-output-markdown execution_count=6}\n$\\displaystyle RL([i \\oplus i]_{i}) =RL([0, 0, 1, 1, 1, 4, 4, 9, 9, 9, ...]) =[2, 3, 2, 3, 3, 2, 3, 2, 3, 3, ...]$\n:::\n:::\n\n\n:::\n::::\n\nThe initial two comes from the errors for $0 \\oplus i$ and $1 \\oplus i$.\nThese rows never have errors, so they can be ignored.\n\nSince the run lengths appear to only be occur in twos and threes, we don't lose any information by converting it\n into, say, ones and zeroes.\nArbitrarily mapping $3 \\mapsto 0,~ 2 \\mapsto 1$, the sequence becomes (truncated to 30 terms):\n\n$$\n0,1,0,0,1,0,1,0,0,1,0,0,1,0,1,0,0,1,0,1,0,0,1,0,0,...\n$$\n\nThis sequence matches the *Fibonacci word* ([OEIS A3849](http://oeis.org/A003849)).\nThe typical definition of this sequence follows from the a slight modification to that of\n the Fibonacci sequence: rather than starting with two 1s and adding, we start with\n \"0\" and \"1\" (as distinct strings), and concatenate.\n\nPerhaps this is unsurprising, since Zeckendorf expansions are literally strings in Fibonacci numbers.\nHowever, this shows that the errors can be interesting, since it is not obvious that this is the case\n from just looking at the multiplication table.\n\n\nVisualizing Tables\n------------------\n\nWhile it's great that we can build a table, just showing the numbers isn't the best we can do when it comes to visualization.\n\nFor inspiration, I remembered what WolframAlpha does when you ask it for operation tables in finite fields.\nRather than showing you the underlying field elements in a table, it renders\n [a grayscale image](https://www.wolframalpha.com/input?i=finite+field+of+order+25).\n\nI'd like produce something similar in color, but there's a slight problem: we don't have a finite number of elements.\nTo get around this, we'll have to take our tables mod an integer.\nIn the HSV color space, colors are cyclic just like the integers mod *n*.\nThis actually has an additional appealing feature: two colors with similar hues are \"near\" in a similar way to the underlying numbers.\nThen, the entry-to-color mapping can just be given as\n\n$$\nk \\mapsto HSV \\left({2\\pi k \\over n}, 100\\%, 100\\% \\right)\n$$\n\nIn this mapping, for $n = 2$, zero goes to red and one goes to cyan.\nThe following is a 100x100 image of the multiplication table of $\\oplus_\\text{Zeck}$ from zero to ninety-nine.\n\n![\n &nbsp;\n](fibonacci_deficiency_mod_2.png){.image-wide}\n\n\n### Anima Moduli\n\nThis table can by animated by gathering various n into an animation and incrementing n every frame.\nBefore applying this technique to an error table, it'd be prudent to apply it to standard addition and multiplication tables first.\n\n::: {.row layout-ncol=\"2\"}\n![\n Addition\n](canonical_tables/canonical_addition.gif){.image-wide}\n\n![\n Multiplication\n](canonical_tables/canonical_multiplication.gif){.image-wide}\n:::\n\nBoth images appear to zoom in as the animation progresses.\nUnsurprisingly, addition produces diagonal stripes, along which the sum is the same.\nMultiplication produces a squarish pattern (with somewhat appealing moirés).\n\nTables for $\\odot_\\text{Zeck}$ and $\\oplus_\\text{Zeck}$ should resemble the table to the right,\n since their definition relies on it.\n\n::: {.row layout-ncol=\"2\"}\n![\n $\\odot_\\text{Zeck}$\n](series_tables/fibonacci_odot.gif){.image-wide}\n\n![\n $\\oplus_\\text{Zeck}$\n](series_tables/fibonacci_oplus.gif){.image-wide}\n:::\n\nIndeed, the zooming is present in both tables.\nAt later frames, both tables share the same \"square rainbow\" pattern, but the error table is closer.\nThough both tables appear to have a repeating pattern, the blocks in the error table are still\n irregularly shaped, as in the mod 2 case.\n\nThe error table also demonstrates (for a given modulus) how much the series differs from a geometric series.\nWe know there will always be an error since, while Fibonacci numbers grow on the order of\n $O(\\varphi^n)$, the ratio between place values is only *φ* in the limit.\n\n\nOther Error Tables\n------------------\n\nThese operations can can also be defined in terms of any other series expansion (i.e., integral system).\nRather than showing both of the multiplication and error tables, I will elect to show only the latter.\nThe error table is more fundamental since the table for $\\odot$ can be constructed from it and the normal multiplication table.\n\nThe error tables for some of the previously-discussed generalizations of the Fibonacci numbers are presented below.\n\n\n### Pell, Jacobsthal\n\n::: {.row layout-ncol=\"2\"}\n![\n Pell ([OEIS A000129](http://oeis.org/A000129))\n $\\langle 2,1|$\n](series_tables/pell_oplus.gif){.image-wide}\n\n![\n Jacobsthal ([OEIS A001045](http://oeis.org/A001045)),\n $\\langle 1,2|$\n](series_tables/jacobsthal_oplus.gif){.image-wide}\n:::\n\nOf the two tables, the Pell table looks more similar to the Fibonacci one.\nMeanwhile, the Jacobsthal table has frames which are significantly redder than the either surrounding it.\nThese occur on frames for which *n* is a power of 2, which is a root of the recurrence polynomial.\n\n\n### *n*-nacci\n\n::: {.row layout-ncol=\"2\"}\n![\n Tribonacci,\n $\\langle 1,1,1|$\n](series_tables/tribonacci_oplus.gif){.image-wide}\n\n![\n Tetranacci,\n $\\langle 1,1,1,1|$\n](series_tables/tetranacci_oplus.gif){.image-wide}\n:::\n\nCompared to the Fibonacci image, the size of the square pattern in the Tribonacci and Tetranacci images is larger.\nHigher orders of Fibonacci recurrences approach binary, so in the limit,\n the pattern disappears, as if the tiny red corner in the upper-left is all that remains.\n\n\n### Other\n\nOf course, integral systems are not limited to linear recurrences, and tables can also be produced\n that correspond to other integer sequences.\nFor example, the factorials produce distinct patterns at certain frames.\nThe Catalan numbers (which are their own recurrence) mod two seem to make a fractal carpet.\nTo prevent the page from getting bloated, these frames are presented in isolation.\n\n\n::: {.row layout-ncol=\"2\"}\n![\n Factorial Error (mod 8)\n](series_tables/factorial_oplus_mod_8.png){.image-wide}\n\n![\n Catalan Numbers Error (mod 2)\n](series_tables/catalan_oplus_mod_2.png){.image-wide}\n:::\n\n\nAdditional tables which I find interesting are:\n\n- [\n Triangular Numbers (recurrence $\\langle 3, \\bar{3}, 1|$)\n](series_tables/triangular_oplus.gif){target=\"_blank_\"}\n- [\n Square Numbers\n](series_tables/square_oplus.gif){target=\"_blank_\"}\n- [\n Padovan Numbers (recurrence $\\langle 0,1,1|$)\n](series_tables/padovan_oplus.gif){target=\"_blank_\"}\n- [\n $\\langle 1, 1, 2|$, based on the first 3 Catalan numbers\n](series_tables/recurrence_1_1_2_oplus.gif){target=\"_blank_\"}\n\n\nClosing\n-------\n\nEven if not particularly useful, I enjoy the emergent behavior apparent in the tables.\nEven for typical multiplication, this visualization technique shows regular patterns which appear to \"grow\".\nMeanwhile, the sequence products frequently produce a pattern that either repeats, or is composed of many similar elements.\n\nThe project that I used to render the images can be found [here](https://github.com/queue-miscreant/SeriesDeficient).\nI didn't put much thought into command line arguments, nor did I build in many features.\nAs an executable, it should be completely serviceable to generate error tables based on recurrence relations;\n the GHCi REPL can be used for more sophisticated sequences.\n\n\n### Bad Visualization\n\nMy first attempt to map integers to colors was to consider its prime factorization.\nThe largest number in the factorization was related to the hue, and the number of terms in the factorization\n was related to the saturation.\nResults were not great.\n\n![&nbsp;](fibonacci_old_bad.png)\n\n\n### Bad Filesizes\n\nWhile I rendered these animations as GIFs, I also attempted to convert some of them to MP4s in hopes of shrinking the filesize.\nHowever, this is a use case where the MP4 is larger than the GIF, at least without compromising on resolution.\nThis is a case when space-compression beats ill-suited time-compression.\n\n![Produced with ffmpeg defaults](gif_mp4_size_comparison.png)\n\n",
"supporting": [
"index_files"
],
"filters": [],
"includes": {}
}
}