Public
Edited
Nov 18
1 star
Insert cell
Insert cell
Insert cell
{
let rad = 300;
let nMax = drawMax;
return svg`<svg width="${rad * 2}" height="${
rad * 2
}" viewBox="${-rad} ${-rad} ${2 * rad} ${2 * rad}">
${_.range(1, nMax).flatMap((n) => {
let nRad = (rad * n) / nMax;
return _.range(0, n).map((r) => {
let theta = (r / n) * 2 * Math.PI;
let x = nRad * Math.cos(theta - Math.PI / 2);
let y = nRad * Math.sin(theta - Math.PI / 2);
let fill = allQuadraticResidues[n].includes(r)
? "red"
: allPrimitiveRoots[n].includes(r)
? "blue"
: "none";
return svg`<circle opacity="${0.33}" r="1.5" cx="${x}" cy="${y}" fill="${fill}"/>`;
});
})}
</svg>`;
}
Insert cell
_.range(1, 10).map((exp) => {
return {
exp,
prims: primitiveRoots(3 ** exp)
};
})
Insert cell
Insert cell
{
// I want to do prime powers but I need to fix my mod pow function
let rad = 300;
let p = 7;
let nMax = 5;
return svg`<svg width="${rad * 2}" height="${
rad * 2
}" viewBox="${-rad} ${-rad} ${2 * rad} ${2 * rad}">
${_.range(1, nMax).flatMap((n) => {
let nRad = (rad * n) / nMax;
return svg`
<circle r="${nRad}" fill="none" stroke="lightgrey"/>
${primitiveRoots(p ** n).map((r) => {
let theta = (r / p ** n) * 2 * Math.PI;
let x = nRad * Math.cos(theta - Math.PI / 2);
let y = nRad * Math.sin(theta - Math.PI / 2);
return svg`<circle r="2" cx="${x}" cy="${y}"/>`;
})}`;
})}
</svg>`;
}
Insert cell
Insert cell
{
let rad = 300;
let nMax = drawMax;
return svg`<svg width="${rad * 2}" height="${
rad * 2
}" viewBox="${-rad} ${-rad} ${2 * rad} ${2 * rad}">
${_.range(1, nMax).flatMap((n) => {
let nRad = (rad * n) / nMax;
return allQuadraticResidues[n].map((r) => {
let theta = (r / n) * 2 * Math.PI;
let x = nRad * Math.cos(theta - Math.PI / 2);
let y = nRad * Math.sin(theta - Math.PI / 2);
return svg`<circle opacity="0.5" r="1" cx="${x}" cy="${y}"/>`;
});
})}
</svg>`;
}
Insert cell
Insert cell
viewof highlightRoot= Inputs.range([0, drawMax], { step: 1 })
Insert cell
{
let rad = 300;
let nMax = drawMax;
return svg`<svg width="${rad * 2}" height="${
rad * 2
}" viewBox="${-rad} ${-rad} ${2 * rad} ${2 * rad}">
${_.range(1, nMax).flatMap((n) => {
let nRad = (rad * n) / nMax;
return allPrimitiveRoots[n].map((r) => {
let theta = (r / n) * 2 * Math.PI;
let x = nRad * Math.cos(theta - Math.PI / 2);
let y = nRad * Math.sin(theta - Math.PI / 2);
let radius = highlightRoot === n ? 4 : 2;
let opacity = highlightRoot === n ? 1 : 0.5;
return svg`<circle opacity="${opacity}" r="${radius}" cx="${x}" cy="${y}" fill="${"black"}"/>`;
});
})}
</svg>`;
}
Insert cell
{
let rad = 300;
let nMax = drawMax;
let pi = 100;
return svg`<svg width="${rad * 2}" height="${
rad * 2
}" viewBox="${-rad} ${-rad} ${2 * rad} ${2 * rad}">
${_.take(allPrimes, pi).flatMap((n, i) => {
let nRad = (rad * i) / pi;
return allPrimitiveRoots[n].map((r) => {
let theta = (r / n) * 2 * Math.PI;
let x = nRad * Math.cos(theta - Math.PI / 2);
let y = nRad * Math.sin(theta - Math.PI / 2);
let radius = highlightRoot === n ? 4 : 2;
let opacity = highlightRoot === n ? 1 : 0.5;
return svg`<circle opacity="${opacity}" r="${radius}" cx="${x}" cy="${y}" fill="${"black"}"/>`;
});
})}
</svg>`;
}
Insert cell
Insert cell
{
let rad = 300;
let nMax = drawMax / 2;
return svg`<svg width="${rad * 2}" height="${
rad * 2
}" viewBox="${-rad} ${-rad} ${2 * rad} ${2 * rad}">
${_.range(1, nMax).flatMap((n) => {
let no = 2 * n - 1;
let nRad = (rad * n) / nMax;
return allPrimitiveRoots[no].map((r) => {
let theta = (r / no) * 2 * Math.PI;
let x = nRad * Math.cos(theta - Math.PI / 2);
let y = nRad * Math.sin(theta - Math.PI / 2);
let color = no % 2 === 0 ? "blue" : "red";
return svg`<circle opacity="0.5" r="1.5" cx="${x}" cy="${y}" fill="${color}"/>`;
});
})}
</svg>`;
}
Insert cell
{
let rad = 300;
let nMax = drawMax / 2;
return svg`<svg width="${rad * 2}" height="${
rad * 2
}" viewBox="${-rad} ${-rad} ${2 * rad} ${2 * rad}">
${_.range(1, nMax).flatMap((n) => {
let no = 2 * (2 * n - 1);
let nRad = (rad * n) / nMax;
return allPrimitiveRoots[no].map((r) => {
let theta = (r / no) * 2 * Math.PI;
let x = nRad * Math.cos(theta - Math.PI / 2);
let y = nRad * Math.sin(theta - Math.PI / 2);
let color = no % 2 === 0 ? "blue" : "red";
return svg`<circle opacity="0.5" r="1.5" cx="${x}" cy="${y}" fill="${color}"/>`;
});
})}
</svg>`;
}
Insert cell
Insert cell
Insert cell
viewof highlight = Inputs.range([-drawMax, drawMax], { step: 1 })
Insert cell
{
let rad = 300;
let nMax = drawMax / 4;
return svg`<svg width="${rad * 2}" height="${
rad * 2
}" viewBox="${-rad} ${-rad} ${2 * rad} ${2 * rad}">
${_.range(1, nMax).flatMap((n) => {
let nRad = (rad * n) / nMax;
return _.range(0, n)
.filter((r) => gcd(r, n) === 1)
.map((r) => {
let theta = (r / n) * 2 * Math.PI;
let x = nRad * Math.cos(theta - Math.PI / 2);
let y = nRad * Math.sin(theta - Math.PI / 2);
let fill = r === mod(highlight, n) ? "red" : "black";
return svg`<circle r="1.5" cx="${x}" cy="${y}" fill="${fill}"/>`;
});
})}
</svg>`;
}
Insert cell
Insert cell
{
let rad = 300;
let nMax = drawMax / 2;
let dots = _.range(1, nMax).flatMap((n) => {
let nRad = (rad * n) / nMax;
return _.range(0, n).map((r) => {
let theta = (r / n) * 2 * Math.PI;
let x = nRad * Math.cos(theta - Math.PI / 2);
let y = nRad * Math.sin(theta - Math.PI / 2);
return { x, y, gcd: gcd(n, r) };
});
});
return Plot.plot({
width: 600,
height: 600,
color: {
type: "sequential",
scheme: "RdYlBu",
transform: (gcd) => 1 / gcd
},
marks: [
Plot.voronoi(dots, { x: "x", y: "y", fill: "gcd" })
// Plot.dot(dots, { x: "x", y: "y" })
]
});
}
Insert cell
{
let rad = 300;
let nMax = 64;
return svg`<svg width="${rad * 2}" height="${
rad * 2
}" viewBox="${-rad} ${-rad} ${2 * rad} ${2 * rad}">
${_.range(1, nMax).flatMap((n) => {
let nRad = (rad * n) / nMax;
return _.range(0, n).map((r) => {
// TODO make the color more "meaningful" and consistent
return svg`<path fill="${d3.interpolateRdYlBu(
1 / gcd(r, n)
)}" d="${d3.arc()({
innerRadius: nRad,
outerRadius: (rad * (n + 1)) / nMax,
startAngle: ((r - 0.5) / n) * 2 * Math.PI,
endAngle: ((r + 0.5) / n) * 2 * Math.PI
})}"/>`;
});
})}
</svg>`;
}
Insert cell
{
let rad = 300;
let nMax = 256;
return svg`<svg width="${rad * 2}" height="${
rad * 2
}" viewBox="${-rad} ${-rad} ${2 * rad} ${2 * rad}">
${_.range(1, nMax).flatMap((n) => {
let nRad = (rad * n) / nMax;
return _.range(0, n).map((r) => {
return svg`<path fill="${
allQuadraticResidues[n].includes(r) ? "black" : "white"
}" d="${d3.arc()({
innerRadius: nRad,
outerRadius: (rad * (n + 1)) / nMax,
startAngle: ((r - 0.5) / n) * 2 * Math.PI,
endAngle: ((r + 0.5) / n) * 2 * Math.PI
})}"/>`;
});
})}
</svg>`;
}
Insert cell
{
let rad = 300;
let nMax = 64;
return svg`<svg width="${rad * 2}" height="${
rad * 2
}" viewBox="${-rad} ${-rad} ${2 * rad} ${2 * rad}">
${_.range(1, nMax).flatMap((n) => {
let nRad = (rad * n) / nMax;
return _.range(0, n).map((r) => {
let color = allQuadraticResidues[n].includes(r)
? "red"
: allPrimitiveRoots[n].includes(r)
? "blue"
: "white";
return svg`<path fill="${color}" stroke="${'none'}" d="${d3.arc()({
innerRadius: nRad,
outerRadius: (rad * (n + 1)) / nMax,
startAngle: ((r - 0.5) / n) * 2 * Math.PI,
endAngle: ((r + 0.5) / n) * 2 * Math.PI
})}"/>`;
});
})}
</svg>`;
}
Insert cell
{
let rad = 300;
let nMax = 64;
return svg`<svg width="${rad * 2}" height="${
rad * 2
}" viewBox="${-rad} ${-rad} ${2 * rad} ${2 * rad}">
${_.range(1, nMax).flatMap((n) => {
let nRad = (rad * n) / nMax;
let orders = elementOrders(n);
return _.range(0, n).map((r) => {
// FIXME this still returns undefined in some instances - e.g. for elements that aren't part of the multiplicative group
return svg`<path fill="${d3.interpolateInferno(
orders[r] / n
)}" d="${d3.arc()({
innerRadius: nRad,
outerRadius: (rad * (n + 1)) / nMax,
startAngle: ((r - 0.5) / n) * 2 * Math.PI,
endAngle: ((r + 0.5) / n) * 2 * Math.PI
})}"/>`;
});
})}
</svg>`;
}
Insert cell
Insert cell
{
let rad = 300;
let nMax = drawMax;
return svg`<svg width="${rad * 2}" height="${
rad * 2
}" viewBox="${-rad} ${-rad} ${2 * rad} ${2 * rad}">
${_.range(1, nMax).flatMap((n) => {
let nRad = (rad * n) / nMax;
return _.range(0, n)
.filter((r) => n % r === 0)
.map((r) => {
let theta = (r / n) * 2 * Math.PI;
let x = nRad * Math.cos(theta - Math.PI / 2);
let y = nRad * Math.sin(theta - Math.PI / 2);
let fill = r === mod(highlight, n) ? "red" : "black";
return svg`<circle opacity=".5" r="1" cx="${x}" cy="${y}" fill="${"black"}"/>`;
});
})}
</svg>`;
}
Insert cell
Insert cell
{
let rad = 300;
let nMax = drawMax / 2;
return svg`<svg width="${rad * 2}" height="${
rad * 2
}" viewBox="${-rad} ${-rad} ${2 * rad} ${2 * rad}">
${_.range(1, nMax).flatMap((n) => {
let nRad = (rad * n) / nMax;
return allCubicResidues[n].map((r) => {
let theta = (r / n) * 2 * Math.PI;
let x = nRad * Math.cos(theta - Math.PI / 2);
let y = nRad * Math.sin(theta - Math.PI / 2);
let fill = r === mod(highlight, n) ? "red" : "black";
return svg`<circle opacity=".5" r="1" cx="${x}" cy="${y}" fill="${"black"}"/>`;
});
})}
</svg>`;
}
Insert cell
drawMax = 2 ** 9
Insert cell
mod = (n, m) => ((n % m) + m) % m
Insert cell
quadraticResiduesByFreq = {
let result = _.range(drawMax).map((_) => []);
for (let [index, roots] of allQuadraticResidues.entries()) {
for (let root of roots) {
result[root].push(index);
}
}
return result;
}
Insert cell
allPrimitiveRoots = _.range(primitiveRootLimit).map((n) => primitiveRoots(n))
Insert cell
primitiveRootLimit = 2 ** 10
Insert cell
allQuadraticResidues = _.range(drawMax).map((n) => quadraticResidues(n))
Insert cell
allQuinticResidues = _.range(drawMax).map((n) => {
let residues = _.range(n).map((k) => k ** 5 % n);
let uniq = _.uniq(residues);
uniq.sort();
return uniq;
})
Insert cell
allCubicResidues = _.range(drawMax).map((n) => {
let residues = _.range(n).map((k) => k ** 3 % n);
let uniq = _.uniq(residues);
uniq.sort();
return uniq;
})
Insert cell
function quadraticResidues(n) {
let residues = _.range(n).map((k) => {
return k ** 2 % n;
});
let uniq = _.uniq(residues);
uniq.sort();
return uniq;
}
Insert cell
function elementOrders(n) {
return _.range(0, n).map((k) => {
if (k === 0) {
return 0;
}
let phi = allTotients[n];
let phiDivisors = [...allDivisors[phi], phi];
return phiDivisors.find((d) => modpow(k, d, n) === 1);
});
}
Insert cell
function primitiveRoots(n) {
let factors = _.countBy(allPrimeFactors[n]);
let rad = _.uniq(allPrimeFactors[n]);
let twoPower = factors[2] ?? 0;
let oddPrimes = rad.filter((p) => p !== 2).length;
if (n === 0) {
return [];
}
if (n < 5) {
return [n - 1];
}

// Primitive roots only exist for numbers 2, 4, p^k, 2p^k (for odd primes)
if (!(n === 2 || n === 4 || (oddPrimes <= 1 && twoPower <= 1))) {
return [];
}
let phi = allTotients[n];
let phiFactors = _.uniq(allPrimeFactors[phi]);
return _.range(2, n - 1).filter((m) => {
return (
gcd(m, n) === 1 && phiFactors.every((q) => modpow(m, phi / q, n) !== 1)
);
});
return [1];
}
Insert cell
function modpow(base, exp, mod) {
if (mod === 1) {
return 0;
}
let result = 1;
base = base % mod;
while (exp > 0) {
if (exp % 2 === 1) {
result = (result * base) % mod;
}
exp >>= 1;
base = base ** 2 % mod;
}
return result;
}
Insert cell
allDivisors = _.range(limit).map(factors)
Insert cell
allPrimeFactors = primeFactors(limit)
Insert cell
allTotients = totients(limit)
Insert cell
allPrimes = getPrimes(limit)
Insert cell
limit = 2 ** 16
Insert cell
import {
getPrimes,
gcd,
totients,
primeFactors,
factors
} from "@tesseralis/math"
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more