From 9786cb0f614c5dded71ea1ee3b9b55367c9bcb1a Mon Sep 17 00:00:00 2001 From: queue-miscreant Date: Sun, 2 Mar 2025 01:08:50 -0600 Subject: [PATCH] add interactive page for p-adics --- interactive/p-adics/index.qmd | 56 ++++++++++++++++++++++++++ interactive/p-adics/showAdic.ojs | 69 ++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 interactive/p-adics/index.qmd create mode 100644 interactive/p-adics/showAdic.ojs diff --git a/interactive/p-adics/index.qmd b/interactive/p-adics/index.qmd new file mode 100644 index 0000000..0b378fd --- /dev/null +++ b/interactive/p-adics/index.qmd @@ -0,0 +1,56 @@ +--- + +--- + +```{ojs} +{{< include ./showAdic.ojs >}} +``` + +About +----- + +The Wikipedia article on the [*p*-adic valuation](https://en.wikipedia.org/wiki/P-adic_valuation) + contains [a figure](https://commons.wikimedia.org/wiki/File:2adic12480.svg) whose description + provides a way to map *p*-adics into the complex numbers[^1]. +The gist is to construct a Fourier series over truncations of numbers. +Each term of the series is weighted by a geometrically decreasing coefficient *c*. + +$$ +[...d_2 d_1 d_0]_p \mapsto e^{2\pi i [d_0] / p} + + c e^{2\pi i [d_1 d_0] / p^2} + + c^2 e^{2\pi i [d_2 d_1 d_0] / p^2} + + ... \\ + f_N(d; p) = \sum_{n = 0}^N c^n e^{2\pi i \cdot [d_{n:0}]_p / p^{n + 1}} +$$ + +Assuming the first term dominates, one way of interpreting this is that we place numbers + around the unit circle according to their one's place. +Then, we offset each by smaller circles, each centered on the last, using more and more digits. +This produces a fractal pattern that looks like a wheel with *p* spokes. +At each point on where the spokes meet the rim, there is another smaller wheel with *p* spokes, + ad infinitum. + +This is somewhat visible in the Wikimedia diagram. +All of the odd numbers are at the left side of the diagram, since the leading term of the series for them + is negative one. +The odd numbers are further split into those of the forms $4k + 1$ and $4k + 3$. + +Each of the inputs corresponds to something from the above formula. + +- *b*, the base of the expansions. + - Integers are converted to their representations in base *b*, + which are sequences of digits (*d* in the above formula). +- *p*, the base used in the embedding. + - The same *p* that appears in the above formula. + - Truncations of digit sequences ($d_{n:0}$) are interpreted as strings in base *p*, + then divided by $p^{n+1}$. +- *c*, the geometric constant. + - Smaller *c* means more tightly packed points. + +Additionally, instead of using an integer base, you can also use either *κ*-adic representation. +If using an integer base, only one thousand twenty four (1024) points will be calculated. +Note also that only fifteen terms of the series are used ($N = 15$). + + +[^1]: Taken from the paper "Fractal geometry for images of continuous embeddings of p-adic + numbers and solenoids into Euclidean spaces" (DOI: 10.1007/BF02073866). diff --git a/interactive/p-adics/showAdic.ojs b/interactive/p-adics/showAdic.ojs new file mode 100644 index 0000000..5fd545c --- /dev/null +++ b/interactive/p-adics/showAdic.ojs @@ -0,0 +1,69 @@ +// Convert x into its expansion in base b, as an array of integers (ascending powers of b) +toBase = (b, x) => { + if (x == 0) { return [0] } + + let ret = []; + while (x > 0) { + ret.push(x % b); + x = Math.floor(x / b); + } + return ret; +}; + +// Interpret xs as an expansion in base b (ascending powers of b) +fromBase = (b, xs) => { + let plval = 1 + return xs.reduce((a, x) => { + let new_a = x * plval; + plval *= b; + return a + new_a; + }, 0) +} + +// Map an expansion to a complex number (as a 2-array). +// The expansion is interpreted in base `b`. +// `n` terms of the series are used, with geometric ratio `c` +adicAngles = (expansion, b, n, c) => { + return d3.range(n).reduce((a, i) => { + let angle = fromBase(b, expansion.slice(null, i + 1)) / (b ** (i + 1)); + let x = c**i * Math.cos(2 * Math.PI * angle); + let y = c**i * Math.sin(2 * Math.PI * angle); + return [a[0] + x, a[1] + y]; + }, [0, 0]); +} + +pointCount = 1024; +expansions = d3.range(pointCount).map((x) => toBase(base, x)); + +embedding = expansions.map( + (x) => adicAngles(x, embedBase, 15, geometric) +); + +viewof base = Inputs.range([2, 10], { + value: 2, + step: 1, + label: "Base of expansions (b)", +}); + +viewof embedBase = Inputs.range([2, 10], { + value: 2, + step: 0.2, + label: "Embedding base (p)", +}); + +viewof geometric = Inputs.range([0.005, 0.995], { + value: 0.9, + step: 0.005, + label: "Geometric ratio (c)", +}); + +plot = Plot.plot({ + grid: true, + inset: 10, + // aspectRatio: 1, + width: 640, + height: 480, + marks: [ + Plot.dot(embedding, { r: 1 }) + ] +});