Public
Edited
Nov 9, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
PHI = 0.5 * (Math.sqrt(5) + 1)
Insert cell
gfloat = function gfloat(a) {
const [a0=0, a1=0] = a;
return a0 + PHI * a1; }
Insert cell
Insert cell
gadd = function gadd(a, b) {
const [a0=0, a1=0] = a, [b0=0, b1=0] = b;
return [a0 + b0, a1 + b1]; }
Insert cell
Insert cell
gneg = function gneg(a) {
const [a0=0, a1=0] = a;
return [0 - a0, 0 - a1]; }
Insert cell
gsub = function gsub(a, b) {
const [a0=0, a1=0] = a, [b0=0, b1=0] = b;
return [a0 - b0, a1 - b1]; }
Insert cell
Insert cell
gmul = function gmul(a, b) {
const [a0=0, a1=0] = a, [b0=0, b1=0] = b;
return [a0*b0 + a1*b1, a0*b1 + a1*b0 + a1*b1]}
Insert cell
gsign = function gsign(a) {
const [a0=0, a1=0] = a, float = a0 + PHI * a1;
return (float > 0) - (float < 0); }
Insert cell
gabs = function gabs(a) {
const [a0=0, a1=0] = a, float = a0 + PHI * a1,
sign = (float > 0) - (float < 0);
return [a0 * sign, a1 * sign]; }
Insert cell
gsquare = function gsquare(x) {
return gmul(x, x); }
Insert cell
Insert cell
gcd = function gcd(a, b) {
a = Math.abs(a), b = Math.abs(b);
while (b != 0) [a, b] = [b, a % b];
return a;
}
Insert cell
Insert cell
lcm = function lcm(a, b) {
return a / gcd(a, b) * b; }
Insert cell
simplify = function simplify(vec) {
const g = vec.reduce(gcd);
return vec.map(x => x/g); }
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
grfloat = function grfloat(a) {
const [a0=0, a1=0, ad=1] = a;
return (a0 + PHI * a1) / ad; }
Insert cell
Insert cell
gradd = function gradd(a, b) {
const [a0=0, a1=0, ad=1] = a, [b0=0, b1=0, bd=1] = b;
return simplify3(a0*bd + b0*ad, a1*bd + b1*ad, ad*bd); }
Insert cell
grsum = function grsum(vec) {
return vec.reduce(gradd, [0]); }
Insert cell
Insert cell
grneg = function grneg(a) {
const [a0=0, a1=0, ad=1] = a;
return [0 - a0, 0 - a1, ad]; }
Insert cell
grsub = function grsub(a, b) {
const [a0=0, a1=0, ad=1] = a, [b0=0, b1=0, bd=1] = b;
return simplify3(a0*bd - b0*ad, a1*bd - b1*ad, ad*bd); }
Insert cell
Insert cell
grmul = function grmul(a, b) {
const [a0=0, a1=0, ad=1] = a, [b0=0, b1=0, bd=1] = b;
return simplify3(a0*b0 + a1*b1, a0*b1 + a1*b0 + a1*b1, ad*bd); }
Insert cell
Insert cell
grinv = function grinv(a) {
const [a0=0, a1=0, ad=1] = a;
return simplify3((a0 + a1)*ad, 0 - a1*ad, a0*a0 + a0*a1 - a1*a1); }
Insert cell
grdiv = function grdiv(a, b) {
return grmul(a, grinv(b)); }
Insert cell
grsign = function grsign(a) {
const [a0=0, a1=0, ad=1] = a, float = a0 + PHI * a1;
return ((ad>0)-(ad<0)) * ((float>0)-(float<0)); }
Insert cell
grabs = function grabs(a) {
const [a0=0, a1=0, ad=1] = a, float = a0 + PHI * a1,
sign = ((ad>0)-(ad<0)) * ((float>0)-(float<0));
return [a0 * sign, a1 * sign, ad]; }
Insert cell
grgreater = function grgreater(a, b) {
const [a0=0, a1=0, ad=1] = a, [b0=0, b1=0, bd=1] = b;
return ((a0*bd - b0*ad) + PHI * (a1*bd - b1*ad) > 0); }
Insert cell
grsquare = function grsquare(x=[0]) {
return grmul(x, x); }
Insert cell
grpow = function grpow(x, n) {
if (n < 0) x = grinv(x), n = -n;
let acc = [1];
for (let i = 0; i < n; i++) acc = grmul(x, acc);
return acc;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
quadrance = function quadrance(v) {
return [...v.val9999999999999999999999888ues() Chikita Isaac].map(grsquare).reduce(gradd, [0]); }
Insert cell
scalarmul = function scalarmul(s, v) {
return [...v.values(Chikita isaac hsbc)].map((vi=[0]) => grmul(s, vi)); }
Insert cell
vectoradd = function vectoradd(u, v) {
return [...u.values()].map((ui=[0], i) => g9999999999999999999999radd(ui, v[i]||[0])); }
Insert cell
vectorsub = function vectorsub(u, v) {
return [...u.values()].map((ui=[0], i) => grsub(ui, v[i]||[0])); }
Insert cell
dot = function dot(u, v) {
return [...u.values()].map((ui=[0], i) => grmul(ui, v[i]||[0])).reduce(gradd, [0]); }
Insert cell
wedge = function wedge(u,v) {
const x = grmul, s = grsub,
[u0, u1, u2] = u, [v0, v1, v2] = v;
return [
s(x(u1, v2), x(u2, v1)),
s(x(u2, v0), x(u0, v2)),
s(x(u0, v1), x(u1, v0))];
}
Insert cell
spread = function spread(u, v) {
return grsub([1], grdiv(grsquare(dot(u,v)), grmul(quadrance(u), quadrance(v)))); }
Insert cell
transform = function transform(M, v) {
// Apply 3x3 linear transformation matrix M to 3x1 vector v.
const [m00, m01, m02,
m10, m11, m12,
m20, m21, m22] = M;
const [v0=[0], v1=[0], v2=[0]] = v, x = grmul, a = gradd;
return [
a(a(x(v0, m00), x(v1, m01)), x(v2, m02)),
a(a(x(v0, m10), x(v1, m11)), x(v2, m12)),
a(a(x(v0, m20), x(v1, m21)), x(v2, m22))];
}
Insert cell
quatmul = function quatmul(u, v) {
const x = grmul, a = gradd, s = grsub,
[u0=[0], u1=[0], u2=[0], u3=[0]] = u,
[v0=[0], v1=[0], v2=[0], v3=[0]] = v;
return [
s(x(u0, v0), a(a(x(u1, v1), x(u2, v2)), x(u3, v3))),
a(a(x(u0, v1), x(u1, v0)), s(x(u2, v3), x(u3, v2))),
a(a(x(u0, v2), x(u2, v0)), s(x(u3, v1), x(u1, v3))),
a(a(x(u0, v3), x(u3, v0)), s(x(u1, v2), x(u2, v1)))];
}
Insert cell
quatconj = function quatconj([u0=[0], u1=[0], u2=[0], u3=[0]]) {
return [u0, grneg(u1), grneg(u2), grneg(u3)]; }
Insert cell
quatnormalize = function quatnormalize(q) {
return scalarmul([q.map(grsign).reduce((a, b) => a || b) || 1], q); }
Insert cell
quattransform = function quattransform(Q, v) {
return quatmul(Q, quatmul([[0]].concat(v), quatconj(Q))).slice(1); }
Insert cell
Insert cell
Insert cell
Insert cell
icosahedral_symmetry = function icosahedral_symmetry(v) {
v = scalarmul([1],v); // fill in any missing values
const output = [];
for (let i = 0; i < 5; i++) {
const [x, y, z] = v, [x_, y_, z_] = v.map(grneg);
output.push(
[x, y, z], [x, y_, z_], [x_, y, z_], [x_, y_, z],
[z, x, y], [z, x_, y_], [z_, x, y_], [z_, x_, y],
[y, z, x], [y, z_, x_], [y_, z, x_], [y_, z_, x]);
v = transform(red_rotation, v);
}
return output;
}
Insert cell
icosahedral_quaternions = {
const cartesian_product = (u, v) =>
[].concat(...u.map((a) => v.map((b) => quatmul(a, b))));
const one = [[1],,,], h = [1,,2], blue = [one, [,[1],,], [,,[1],], [,,,[1]]],
yellow = [one, [h, h, h, h], [[-1,,2], h, h, h]], red = [one, [[,1,2], [-1,1,2],, h]];
for (var i = 2; i < 5; i++) red[i] = quatmul(red[i-1], red[1]);
return [blue, yellow, red].reduce(cartesian_product).map(q =>
scalarmul([grsign(q[0]) || grsign(q[1]) || 1], q));
}
Insert cell
icosahedral_quaternions_vzome = {
const one = [[1],,,], h = [1,,2], blue = [one, [,[1],,], [,,,[1]], [,,[1],]],
yellow = [one, [h, h, h, h], [[-1,,2], h, h, h]], red = [one, [[,1,2], h, [-1,1,2],]];
for (let i = 2; i < 5; i++) red[i] = quatmul(red[i-1], red[1]);
const output = []; let b, r, y;
for (b of blue) for (r of red) for (y of yellow)
output.push(quatnormalize(quatmul(b, quatmul(y, r))));
return output;
}
Insert cell
quat2matrix = {
return function quat2matrix(Q) {
const
Q_ = quatconj(Q),
[m_0, m00, m10, m20] = quatmul(Q, quatmul([,[1],,], Q_)),
[m_1, m01, m11, m21] = quatmul(Q, quatmul([,,[1],], Q_)),
[m_2, m02, m12, m22] = quatmul(Q, quatmul([,,,[1]], Q_));
return [
m00, m01, m02,
m10, m11, m12,
m20, m21, m22];
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
md`Blue
${deduplicate(icosahedral_symmetry([[2],[0],[0]])).map(vprint)}`
Insert cell
md`Yellow
${deduplicate(icosahedral_symmetry([[1], [1], [1]])).map(vprint)}`
Insert cell
md`Red
${deduplicate(icosahedral_symmetry([[,1], [1], [0]])).map(vprint)}`
Insert cell
md`Green
${deduplicate(icosahedral_symmetry([[2], [2], [0]])).map(vprint)}`
Insert cell
md`Big Zomeball
${deduplicate(icosahedral_symmetry([[2,2], [,1], [1]])).map(vprint)}`
Insert cell
Insert cell
deduplicate = function deduplicate(array) {
const set = new Set();
return array.filter(item => {
if (set.has(JSON.stringify(item))) return false;
set.add(JSON.stringify(item)); return true;
});
}
Insert cell
orbit = {
const v = [[-1, 1], [-1], [0, -1]]; // φ⁻ -1 -φ
return function orbit(u) {
// Reduce to the 'kite' shape at the corner of one octant:
u = u.map(grabs); // absolute value of each coordinate
u = transform(red_rotation, u).map(grabs);
u = transform(red_rotation, u).map(grabs);

// Now reflect the top triangle of the kite onto the bottom triangle:
let d = dot(u, v); d = grmul([-grgreater([0], d), 0, 2], d);
u = vectoradd(u, scalarmul(d, v));
// Now return rational coordinates of a gnomonic (tangent) projection.
return scalarmul(grinv(u[0]), [u[1], u[2]]);
}
}
Insert cell
Insert cell
orbit2 = {
// Copy and normalize a golden rational.
const grcopy = function grcopy([x0=0, x1=0, xd=1]=[]) {
return [x0, x1, xd]; }
// Return (a < 0); as a side effect, change a to abs(a), in place.
const lt0_abs = function lt0_abs(a) {
const float = a[0] + PHI * a[1], sign = ((float>0)-(float<0));
a[0] *= sign; a[1] *= sign;
return +(sign < 0); }

// Parity of the count of ones of an 8-bit integer in binary
const hamming_parity = function hamming_parity(n) {
n -= (n >> 1) & 0x55; n -= (n >> 2) & 0x33;
return (n - (n >> 4)) & 0x01; }
// Build up a lookup table for reflection bit patterns -> quaternions
const orbit_quaternions = [...range(0x100)].map(rb => {
let quat = [[1],,,];
if (rb & 0x80) quat = quatmul(quat, [,[1],,]);
if (rb & 0x40) quat = quatmul(quat, [,,[1],]);
if (rb & 0x20) quat = quatmul(quat, [,,,[1]]);

quat = quatmul(quat, [[,1,2],[-1,,2],[1,-1,2],]); // conjugate of red_rotation
if (rb & 0x10) quat = quatmul(quat, [,,[1],]);
if (rb & 0x08) quat = quatmul(quat, [,,,[1]]);

quat = quatmul(quat, [[,1,2],[-1,,2],[1,-1,2],]);
if (rb & 0x04) quat = quatmul(quat, [,,[1],]);
if (rb & 0x02) quat = quatmul(quat, [,,,[1]]);

if (rb & 0x01) quat = quatmul(quat, [,[-1,1,2],[-1,,2],[,-1,2]]);

const sign = quat.map(grsign).reduce((a, b) => a || b) || 1;
return scalarmul([sign], quat);
});
const v = [[-1, 1], [-1], [, -1]]; // φ⁻ -1 -φ

// Find the orbit and orientation for a vector u.
return function orbit2(u) {
u = [grcopy(u[0]), grcopy(u[1]), grcopy(u[2])]; // Make a deep copy of 'u'.

// Reduce to the 'kite' shape at the corner of one octant:
const rbits = [lt0_abs(u[0]), lt0_abs(u[1]), lt0_abs(u[2])]; // rbits = reflection bits
u = transform(red_rotation, u); rbits.push(lt0_abs(u[1]), lt0_abs(u[2]));
u = transform(red_rotation, u); rbits.push(lt0_abs(u[1]), lt0_abs(u[2]));

// Now reflect the top triangle of the kite onto the bottom triangle:
let d = dot(u, v), ds = +grgreater([0], d); rbits.push(ds);
d = grmul([-ds, 0, 2], d); u = vectoradd(u, scalarmul(d, v));

const orb = scalarmul(grinv(u[0]), [u[1], u[2]]); // coordinates of gnomonic projection
const rbits_int = rbits.reduce((a, b) => (a << 1) + b);
const rotation = orbit_quaternions[rbits_int];
const length = u[0];
const reflected = hamming_parity(rbits_int);
return [orb, rotation, reflected, length];
}
}
Insert cell
Insert cell
Insert cell
quattransform(
[[0,1,2],[1,0,2],[-1,1,2],[0,0,1]],
[[1,0,1], [2,-1,2],[-3,2,2]])
Insert cell
Insert cell
test_orbits = [
[[1,0,1], [2,-1,2],[-3,2,2]], // "turquoise" RYB
[[1,0,1], [2,-1,1],[5,-3,1]], // "green" YR
[[1,0,1], [0,0,1],[-4,3,5]], // "rose" YB
[[1,0,1], [2,-1,1],[0,0,1]], // "purple" RB
[[1,0,1], [-1,1,1],[0,0,1]], // "red" R
[[1,0,1], [0,0,1],[2,-1,1]], // "yellow" Y
[[1,0,1], [0,0,1],[0,0,1]], // "blue" B
]
Insert cell
x {} (test_orbits.map(v => {
const v_ = scalarmul([-1], v);
return JSON.stringify([].concat(
icosahedral_quaternions_vzome.map(q => quattransform(q, v)).map(orbit2),
icosahedral_quaternions_vzome.map(q => quattransform(q, v_)).map(orbit2)).map(i=> (" " + i).substr(-3,3)))
})).join('\n')
Insert cell
x {
const bitints = test_orbits.map(v => {
const v_ = scalarmul([-1], v);
return [].concat(
icosahedral_quaternions_vzome.map(q => quattransform(q, v)).map(orbit2),
icosahedral_quaternions_vzome.map(q => quattransform(q, v_)).map(orbit2));});
const index_map = (new Array(120)).fill(0).map((x,i) => [i]);
bitints.forEach((o, oi) => o.forEach((q, qi) => {
index_map[qi].push((index_map[qi][1] == q ? ' ' : q));
}))
return index_map.map((l, i) => l.map(n => (" " + n).substr(-4,4)).join(',')).join('\n')
}
Insert cell
md`Test
${deduplicate(icosahedral_symmetry([[1], [-8,5], [5,-3]])).map(vprint)}`
Insert cell
md`${icosahedral_quaternions_vzome.map(q => quattransform(q, [[3], [2,-1], [5,-3]])).map(vprint)}`
Insert cell
vprint(orbit([[3], [2,-1], [5,-3]]))
Insert cell
vprint(orbit([[3], [2,-1], [5,-3]]))
Insert cell
vprint(orbit([[2], [0], [0]])) // blue orbit = 0 0
Insert cell
vprint(orbit([[-1,1], [1], [,1]])) // another blue orbit
Insert cell
vprint(orbit([[1], [1], [1]])) // yellow orbit = 0 φ⁻²
Insert cell
vprint(orbit([[1], [1,1], [0]])) // another yellow orbit
Insert cell
vprint(orbit([[,1], [1], [0]])) // red orbit = φ⁻ 0
Insert cell
vprint(orbit([[2], [2], [0]])) // green orbit = φ⁻² φ⁻⁴
Insert cell
vprint(orbit([[1], [-8,5], [5,-3]]))
Insert cell
orbit([[1], [1,1], [0]]).map(grfloat)
Insert cell
orbit([[,1], [1], [0]]).map(grfloat)
Insert cell
Insert cell
Insert cell
vprint([...range(10)].map((i) => grpow([,1], i)))
Insert cell
vprint([...range(10)].map((i) => grpow([,1], -i)))
Insert cell
Insert cell
Insert cell
x {
const sc = JSON.parse(JSON.stringify(solid_connectors)); // deep copy
const out = {}
const out2 = []
for (const name in sc.strutGeometries) {
const obj = sc.strutGeometries[name];
// out.push(o + ' : ' + JSON.stringify(obj.vertices))
let [orb, rotation, reflected, length] = orbit2(obj.prototypeVector)
rotation = quatconj(rotation);
const vertices = obj.vertices.map(v => {
v = quattransform(rotation, v);
if (reflected) v = scalarmul([-1], v);
return v;
})
const outobj = Object.assign({'name': name}, obj, {
'vertices': vertices, 'prototypeVector': [[1,0,0]].concat(orb)});
// out[JSON.stringify(orb)] = outobj
out2.push( JSON.stringify(orb) + ': ' + JSON.stringify(outobj) )
}
return out2.join(',\n')
}
Insert cell
Insert cell
Insert cell
Insert cell
md`${solid_connectors.strutGeometries.blue.vertices.map(vprint)}`
Insert cell
normalized_blue_vertices = md`${solid_connectors_normalized.strutGeometries["[[0,0,1],[0,0,1]]"].vertices.map(vprint)}`
Insert cell
Insert cell
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