From f70438f8c5ed08c3844f28565ae5e2f4062b17d7 Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Sat, 22 Nov 2025 17:40:15 +1100 Subject: [PATCH 01/19] Made a pleasanter and more 4d default rotation --- gui.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui.js b/gui.js index 8e4755a..1c79172 100644 --- a/gui.js +++ b/gui.js @@ -15,8 +15,8 @@ const DEFAULTS = { background: 0xd4d4d4, hyperplane: 0.93, zoom: 1, - xRotate: 'YW', - yRotate: 'XW', + xRotate: 'YZ', + yRotate: 'XZ', dtheta: 0, damping: false, captions: true, From 6ae5c7938fd0c8ece162218c40ab16bce35e19ab Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Sun, 23 Nov 2025 10:26:02 +1100 Subject: [PATCH 02/19] First glimpse of the inscribed 5-cells in the 120-cell --- cellindex.js | 6 ++++++ main.js | 1 - polytopes.js | 30 +++++++++++++++++++++++++++++- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/cellindex.js b/cellindex.js index 366673a..d2d1ca6 100644 --- a/cellindex.js +++ b/cellindex.js @@ -104,6 +104,12 @@ export const LAYERS120 = { 163,219,271,223,167] }; +// just one for now + +export const CELL120_CELL5 = { + "1": [ 1, 258, 304, 431, 510 ] +}; + // Schoute's partition via https://arxiv.org/abs/1010.4353 export const PARTITION600 = { diff --git a/main.js b/main.js index 16591e6..01e544c 100644 --- a/main.js +++ b/main.js @@ -69,7 +69,6 @@ link_ms.map((m) => { ); - const face_ms = [ new THREE.MeshStandardMaterial( { color: 0x44ff44 } ) ]; diff --git a/polytopes.js b/polytopes.js index d39a834..ba89d76 100644 --- a/polytopes.js +++ b/polytopes.js @@ -461,6 +461,34 @@ export const cell120_inscribed = () => { +export const cell120_inscribed_cell5 = () => { + const nodes = make_120cell_vertices(); + const links = auto_detect_edges(nodes, 4); + + for( const cstr in CELLINDEX.INDEX120 ) { + label_nodes(nodes, CELLINDEX.INDEX120[cstr], Number(cstr)); + } + + links.map((l) => l.label = 0); + + const nodes5 = nodes.filter((n) => CELLINDEX.CELL120_CELL5["1"].includes(n.id)); + console.log(`node5 = ${nodes5}`); + const links5 = auto_detect_edges(nodes5, 4); + links5.map((l) => l.label = 1); + links.push(...links5); + + return { + name: '120-cell-5-cell', + nodes: nodes, + links: links, + options: [ + { name: "none", links: [ 0 ]}, + { name: "one inscribed 5-cell", links: [ 0, 1 ] }, + ], + description: `The 120-cell with one of its 5-cells.`, + } +} + function partition_coord(i, coords, invert) { @@ -864,7 +892,6 @@ export const icosahedron = () => { export const build_all = () => { return [ - linkTest(), tetrahedron(), octahedron(), cube(), @@ -878,6 +905,7 @@ export const build_all = () => { cell600(), cell600_layered(), cell120_inscribed(), + cell120_inscribed_cell5(), cell120_layered() ]; } From 94568470caafb831457494018f0ff8987e4fb59b Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Sun, 23 Nov 2025 17:44:50 +1100 Subject: [PATCH 03/19] Links and inscribed links have separate opacity controls again --- gui.js | 7 ++++++- main.js | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/gui.js b/gui.js index 8e4755a..d779fc6 100644 --- a/gui.js +++ b/gui.js @@ -7,6 +7,7 @@ const DEFAULTS = { linksize: 1.0, linkopacity: 0.75, shape: '120-cell', + link2opacity: 0.75, option: 'none', visibility: 5, inscribed: false, @@ -40,6 +41,7 @@ class FourDGUI { inscribe_all: this.link['inscribe_all'], linksize: this.link['linksize'], linkopacity: this.link['linkopacity'], + link2opacity: this.link['link2opacity'], nodesize: this.link['nodesize'], nodeopacity: this.link['nodeopacity'], depth: this.link['depth'], @@ -73,7 +75,9 @@ class FourDGUI { this.gui.add(this.params, 'nodesize', 0, 1.5); this.gui.add(this.params, 'nodeopacity', 0, 1).onChange(setNodeOpacity); this.gui.add(this.params, 'linksize', 0, 2); - this.gui.add(this.params, 'linkopacity', 0, 1).onChange(setLinkOpacity); + console.log(setLinkOpacity); + this.gui.add(this.params, 'linkopacity', 0, 1).onChange((v) => setLinkOpacity(v, true)); + this.gui.add(this.params, 'link2opacity', 0, 1).onChange((v) => setLinkOpacity(v, false)); this.gui.addColor(this.params, 'color').onChange(setColor); this.gui.addColor(this.params, 'background').onChange(setBackground); this.gui.add(this.params, 'xRotate', [ 'YW', 'YZ', 'ZW' ]); @@ -136,6 +140,7 @@ class FourDGUI { this.link['zoom'] = this.numParam('zoom', parseFloat); this.link['linksize'] = this.numParam('linksize', parseFloat); this.link['linkopacity'] = this.numParam('linkopacity', parseFloat); + this.link['link2opacity'] = this.numParam('link2opacity', parseFloat); this.link['nodesize'] = this.numParam('nodesize', parseFloat); this.link['nodeopacity'] = this.numParam('nodeopacity', parseFloat); this.link['color'] = this.numParam('color', (s) => guiObj.stringToHex(s)); diff --git a/main.js b/main.js index 01e544c..aa8cab4 100644 --- a/main.js +++ b/main.js @@ -167,7 +167,11 @@ function setBackground(c) { function setLinkOpacity(o, primary) { link_ms.map((lm) => lm.opacity = o); if( shape ) { - shape.links.map((l) => l.object.material.opacity = o); + shape.links.map((l) => { + if( (primary && l.label == 0) || (!primary && l.label !== 0) ) { + l.object.material.opacity = o + } + }); } } From 5922a5df601c12786a571b7aaba2985961aa3078 Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Wed, 26 Nov 2025 19:02:09 +1100 Subject: [PATCH 04/19] New idea doesn't quite work --- explore_120cell.js | 172 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 explore_120cell.js diff --git a/explore_120cell.js b/explore_120cell.js new file mode 100644 index 0000000..056556b --- /dev/null +++ b/explore_120cell.js @@ -0,0 +1,172 @@ + + +import * as POLYTOPES from './polytopes.js'; + +// exploring more inscriptions of the 120-cell + + +export function nodes_links(links, nodeid) { + return links.filter((l) => l.source === nodeid || l.target === nodeid); +} + + +export function linked(links, n1, n2) { + const ls = nodes_links(nodes_links(links, n1), n2); + if( ls.length ) { + return ls[0] + } else { + return false; + } +} + + +function fingerprint(ids) { + const sids = [...ids]; + sids.sort(); + return sids.join(','); +} + +export function dist(n1, n2) { + return Math.sqrt((n1.x - n2.x) ** 2 + (n1.y - n2.y) ** 2 + (n1.z - n2.z) ** 2 + (n1.w - n2.w) ** 2); +} + + +export function make_120cell() { + const nodes = POLYTOPES.make_120cell_vertices(); + const links = POLYTOPES.auto_detect_edges(nodes, 4); + return { + nodes: nodes, + links: links + } +} + +function round_dist(raw) { + return Math.floor(raw * 100000) / 100000; +} + +export function distance_groups(cell120) { + // get list of other nodes by distance + // sort them and dump them out + const dists = {}; + + cell120.nodes.map((n) => { + const draw = dist(cell120.nodes[0], n); + const dtrunc = round_dist(draw); + if( !(dtrunc in dists) ) { + dists[dtrunc] = []; + } + dists[dtrunc].push(n); + }); + return dists; +} + +function distance_group(cell120, n0, chord) { + const nodes = [] + cell120.nodes.map((n) => { + const d = round_dist(dist(n0, n)); + if( d == chord ) { + nodes.push(n); + } + }); + // filter and return those whose chord is also the same + const equidistant = []; + for( const n1 of nodes ) { + for( const n2 of nodes ) { + if( n2.id > n1.id ) { + if( round_dist(dist(n1, n2)) == chord ) { + equidistant.push([n1, n2]); + } + } + } + } + return equidistant; +} + + +export function chord_survey() { + const cell120 = POLYTOPES.cell120_inscribed(); + + const dgroups = distance_groups(cell120); + + const dists = Object.keys(dgroups); + + dists.sort(); + + for( const d of dists ) { + const g0 = dgroups[d][0]; + dgroups[d].map((g) => { + console.log(`${g0.id}-${g.id}: ${round_dist(dist(g0, g))}`); + }); + } +} + +// how to proceed: start with the 120-cell that has five 600-cells inscribed +// in it. Take the 120 nodes from one of those 600-cells, and construct a 5-cell +// from each such that all of the vertices are on different 600-cells. + +// collect them and output by label because every 5-cell is on all 5 600-cells + +function overlap(c1, c2) { + for( const l in c1 ) { + if( c1[l] === c2[l] ) { + return true; + } + } + return overlap; +} + + +export function gather_5cells() { + const cell120 = POLYTOPES.cell120_inscribed(); + const CHORD5 = round_dist(Math.sqrt(2.5)); + const bins = []; + cell120.nodes.filter((n) => n.label === 1).map((n) => { + const g = distance_group(cell120, n, CHORD5); + const cells = [ ]; + for( const pair of g ) { + let seen = false; + for( const cell of cells ) { + const c = Object.values(cell); + if( c.includes(pair[0].id) || c.includes(pair[1].id) ) { + if( !c.includes(pair[0].id) ) { + cell[pair[0].label] = pair[0].id; + } + if( !c.includes(pair[1].id) ) { + cell[pair[1].label] = pair[1].id; + } + seen = true; + break; + } + } + if( !seen ) { + const cell = {}; + cell[1]= n.id; + cell[pair[0].label] = pair[0].id; + cell[pair[1].label] = pair[1].id; + cells.push(cell); + } + } + for( const cell of cells ) { + let binned = false; + for( const bin of bins ) { + const overlaps = bin.filter((b) => overlap(b, cell)); + console.log(bin); + console.log(cell); + console.log(overlaps); + if( overlaps.length === 0 ) { + bin.push(cell); + binned = true; + console.log("binned"); + break; + } + } + if( !binned ) { + bins.push([ cell ]); + } + } + //console.log(bins); + }) + ; +} + +gather_5cells(); From 303a2971fea9a244c8130a8867f2302338e22bb4 Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Wed, 26 Nov 2025 19:05:46 +1100 Subject: [PATCH 05/19] Brought some ui stuff from another branch --- cellindex.js | 7 ++++++- polytopes.js | 12 +++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/cellindex.js b/cellindex.js index d2d1ca6..520118f 100644 --- a/cellindex.js +++ b/cellindex.js @@ -107,7 +107,12 @@ export const LAYERS120 = { // just one for now export const CELL120_CELL5 = { - "1": [ 1, 258, 304, 431, 510 ] + "1": [ 1, 258, 304, 431, 510 ], + "2": [ 1, 260, 302, 427, 506 ], + "3": [1, 264, 298, 435, 514], + "4": [1, 330, 334, 387, 391], + "5": [1, 491, 503, 574, 578], + "6": [1, 495, 499, 570, 582 ], }; // Schoute's partition via https://arxiv.org/abs/1010.4353 diff --git a/polytopes.js b/polytopes.js index ba89d76..89d7e2f 100644 --- a/polytopes.js +++ b/polytopes.js @@ -471,11 +471,13 @@ export const cell120_inscribed_cell5 = () => { links.map((l) => l.label = 0); - const nodes5 = nodes.filter((n) => CELLINDEX.CELL120_CELL5["1"].includes(n.id)); - console.log(`node5 = ${nodes5}`); - const links5 = auto_detect_edges(nodes5, 4); - links5.map((l) => l.label = 1); - links.push(...links5); + for( const c5 in CELLINDEX.CELL120_CELL5 ) { + const nodes5 = nodes.filter((n) => CELLINDEX.CELL120_CELL5[c5].includes(n.id)); + console.log(`node5 = ${nodes5}`); + const links5 = auto_detect_edges(nodes5, 4); + links5.map((l) => l.label = c5); + links.push(...links5); + } return { name: '120-cell-5-cell', From 1e59b55f5e7a4257e4fb71a966d264efcac9294b Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Wed, 26 Nov 2025 19:13:57 +1100 Subject: [PATCH 06/19] Works "better" now but it's finding 17 wacky disjoint sets instead of 7 --- explore_120cell.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/explore_120cell.js b/explore_120cell.js index 056556b..e2a508f 100644 --- a/explore_120cell.js +++ b/explore_120cell.js @@ -112,7 +112,7 @@ function overlap(c1, c2) { return true; } } - return overlap; + return false; } @@ -146,21 +146,20 @@ export function gather_5cells() { cells.push(cell); } } + //console.log(`From ${n.id}`); + //console.log(cells); for( const cell of cells ) { let binned = false; for( const bin of bins ) { const overlaps = bin.filter((b) => overlap(b, cell)); - console.log(bin); - console.log(cell); - console.log(overlaps); - if( overlaps.length === 0 ) { + if( overlaps.length === 0 ) { bin.push(cell); binned = true; - console.log("binned"); - break; + break; } } if( !binned ) { + console.log(`new bin for ${JSON.stringify(cell)}`); bins.push([ cell ]); } } From 0ae1d66669bc5d907ef5f450c4f12713bb3102c2 Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Sat, 6 Dec 2025 15:11:32 +1100 Subject: [PATCH 07/19] Still trying to get this working and understand why it maxes out at 81 --- explore_120cell.js | 96 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 67 insertions(+), 29 deletions(-) diff --git a/explore_120cell.js b/explore_120cell.js index e2a508f..c30253d 100644 --- a/explore_120cell.js +++ b/explore_120cell.js @@ -100,11 +100,6 @@ export function chord_survey() { } } -// how to proceed: start with the 120-cell that has five 600-cells inscribed -// in it. Take the 120 nodes from one of those 600-cells, and construct a 5-cell -// from each such that all of the vertices are on different 600-cells. - -// collect them and output by label because every 5-cell is on all 5 600-cells function overlap(c1, c2) { for( const l in c1 ) { @@ -116,13 +111,13 @@ function overlap(c1, c2) { } -export function gather_5cells() { - const cell120 = POLYTOPES.cell120_inscribed(); +export function gather_5cells(cell120) { const CHORD5 = round_dist(Math.sqrt(2.5)); const bins = []; + const all = []; cell120.nodes.filter((n) => n.label === 1).map((n) => { - const g = distance_group(cell120, n, CHORD5); const cells = [ ]; + const g = distance_group(cell120, n, CHORD5); for( const pair of g ) { let seen = false; for( const cell of cells ) { @@ -146,26 +141,69 @@ export function gather_5cells() { cells.push(cell); } } - //console.log(`From ${n.id}`); - //console.log(cells); - for( const cell of cells ) { - let binned = false; - for( const bin of bins ) { - const overlaps = bin.filter((b) => overlap(b, cell)); - if( overlaps.length === 0 ) { - bin.push(cell); - binned = true; - break; - } - } - if( !binned ) { - console.log(`new bin for ${JSON.stringify(cell)}`); - bins.push([ cell ]); - } - } - //console.log(bins); - }) - ; + all.push(...cells); + }); + return all; } -gather_5cells(); +function audit_5cells(cells) { + // this verifies that for each label (a 600-cell set), each of its + // vertices is in exactly 7 5-cells. It checks out. + + ['1','2','3','4','5'].map((l) => { + const sets = {}; + for( const cell of cells ) { + const lv = cell[l]; + if( !(lv in sets) ) { + sets[lv] = []; + } + sets[lv].push(cell); + } + for( const lv in sets ) { + const ok = ( sets[lv].length === 7 ) ? 'ok' : 'miss'; + console.log(`${l},${lv},${sets[lv].length},${ok}`); + } + }); +} + +function try_120_5_cells(cell120, cells, l) { + // iterate over every vertex in the 600-cell defined by label l, + // get all 7 5-cells including that vertex, and add them if they are + // disjoint with what we already have + + const vertices = cell120.nodes.filter((n) => n.label === l); + + const cellset = []; + for( const v of vertices ) { + console.log(`Vertex ${v.id}`); + const vcells = cells.filter((c) => c[l] === v.id); + const overlap_any = (cs, c) => { + for( const seen of cs ) { + console.log(c); + if( overlap(seen, c) ) { + console.log("overlap"); + console.log(seen); + console.log(c); + return true; + + } + } + return false; + } + const disjoint = vcells.filter((c) => ! overlap_any(cellset, c)); + console.log(`Found ${disjoint.length} disjoint cells`); + if( disjoint.length > 0 ) { + cellset.push(disjoint[0]); + } + } + console.log(`Found total of ${cellset.length} disjoint cells`); + //console.log(cellset); +} + + + + +const cell120 = POLYTOPES.cell120_inscribed(); +const all5 = gather_5cells(cell120); + +try_120_5_cells(cell120, all5, 1); From 137f7db0662f3e663e5e75d4b6b9ba12d0045be2 Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Sun, 28 Dec 2025 18:05:42 +1100 Subject: [PATCH 08/19] Getting closer to an algorithm for the 120 5-cell inscription --- explore_120cell.js | 120 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 114 insertions(+), 6 deletions(-) diff --git a/explore_120cell.js b/explore_120cell.js index c30253d..474d301 100644 --- a/explore_120cell.js +++ b/explore_120cell.js @@ -1,10 +1,24 @@ +// TODO - try visualising the 5-cells from a vertex on the layered 120-cell +// and get an intuition for how they work with the dodecahedral structure + +// Another new approach - +// pick a starting 5-cell +// look at the neighbours on the 120-cell of all of its 5 vertices +// try to find abother 5-cell with these neighbours +// if there's only one, add that to the list and keep going +// if there's more than one, are they disjoint with each other? import * as POLYTOPES from './polytopes.js'; // exploring more inscriptions of the 120-cell +function choice(a) { + const r = Math.floor(Math.random() * a.length); + return a[r]; +} + export function nodes_links(links, nodeid) { return links.filter((l) => l.source === nodeid || l.target === nodeid); } @@ -166,11 +180,13 @@ function audit_5cells(cells) { }); } -function try_120_5_cells(cell120, cells, l) { +function try_120_5_cells_fails(cell120, cells, l) { // iterate over every vertex in the 600-cell defined by label l, // get all 7 5-cells including that vertex, and add them if they are // disjoint with what we already have + // this always runs out of disjoint nodes early + const vertices = cell120.nodes.filter((n) => n.label === l); const cellset = []; @@ -179,13 +195,11 @@ function try_120_5_cells(cell120, cells, l) { const vcells = cells.filter((c) => c[l] === v.id); const overlap_any = (cs, c) => { for( const seen of cs ) { - console.log(c); if( overlap(seen, c) ) { console.log("overlap"); - console.log(seen); console.log(c); return true; - + } } return false; @@ -193,17 +207,111 @@ function try_120_5_cells(cell120, cells, l) { const disjoint = vcells.filter((c) => ! overlap_any(cellset, c)); console.log(`Found ${disjoint.length} disjoint cells`); if( disjoint.length > 0 ) { - cellset.push(disjoint[0]); + cellset.push(choice(disjoint)); } } console.log(`Found total of ${cellset.length} disjoint cells`); //console.log(cellset); } +function overlap_any(cs, c) { + for( const seen of cs ) { + if( overlap(seen, c) ) { + return true; + } + } + return false; +} + + +function explore_disjoint(cell120, all5, l) { + const a = all5[0]; + + const overlaps = all5.filter((c) => overlap(c, a)); + + console.log(a); + + console.log(overlaps.length); + console.log(overlaps); +} + +// select a five-cell from a starting vertex v +// find a neighbor of v vn on its 600 cell, find all of the 5-cells which include +// vn. Then see if we can find any from that set which are similiar neighbours to +// the other four vertices in the first 5-cell + +// the idea is that the 600-cells are a guide to finding the right subset of +// 5-cells + +function neighbours600(cell120, vid) { + const v = cell120.nodes.filter((node) => node.id === vid)[0]; + const label = v.label; + const links = cell120.links.filter((l) => { + return l.label === v.label && (l.source === v.id || l.target == v.id ); + }); + const nodes = links.map((l) => { + if( l.source === v.id ) { + return l.target; + } else { + return l.source; + } + }); + return nodes; +} + +function cell120node(cell120, nid) { + return cell120.nodes.filter((n) => n.id === nid)[0]; +} + +function node_dist(cell120, aid, bid) { + const a = cell120node(cell120, aid); + const b = cell120node(cell120, bid); + return dist(a, b); +} + + + +function follow_600(cell120, all5) { + const v = cell120.nodes[0]; + console.log("Start vertex:"); + console.log(v); + const v5s = all5.filter((c5) => c5[v.label] === v.id); + console.log(`Vertex ${v.id} belongs to these 5-cells:`); + console.log(v5s); + const n600s = neighbours600(cell120, v.id); + const n600id = n600s[0]; + const n600 = cell120node(cell120, n600id); + console.log("One 600-cell neighbour:"); + console.log(n600); + const DIST600 = round_dist(node_dist(cell120, v.id, n600id)); + const nv5s = all5.filter((c5) => c5[v.label] === n600id); + console.log(`Vertex ${n600id} belongs to these 5-cells:`); + console.log(nv5s); + console.log("Distances for each pair of 5-cells from the two sets:"); + for( const v5a of v5s ) { + for( const v5b of nv5s ) { + let match = true; + const d = {}; + for( const label in v5a ) { + d[label] = round_dist(node_dist(cell120, v5a[label], v5b[label])); + if( d[label] != DIST600 ) { + match = false; + } + } + if( match ) { + console.log("--- pair ---"); + console.log(v5a); + console.log(v5b); + } + } + } +} + const cell120 = POLYTOPES.cell120_inscribed(); const all5 = gather_5cells(cell120); -try_120_5_cells(cell120, all5, 1); +follow_600(cell120, all5); + From 0e1d8df7b5a7c02a8061ea5b3caa4902fcf9a498 Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Sun, 28 Dec 2025 18:09:54 +1100 Subject: [PATCH 09/19] Refactored a bit so that I can inject functions into the gui --- cellindex.js | 14 +++++--------- gui.js | 48 ++++++++++++++++++++++++++++++------------------ main.js | 22 ++++++++++++++-------- polytopes.js | 19 +++++++++++++------ 4 files changed, 62 insertions(+), 41 deletions(-) diff --git a/cellindex.js b/cellindex.js index 520118f..48838bb 100644 --- a/cellindex.js +++ b/cellindex.js @@ -104,16 +104,12 @@ export const LAYERS120 = { 163,219,271,223,167] }; -// just one for now - export const CELL120_CELL5 = { - "1": [ 1, 258, 304, 431, 510 ], - "2": [ 1, 260, 302, 427, 506 ], - "3": [1, 264, 298, 435, 514], - "4": [1, 330, 334, 387, 391], - "5": [1, 491, 503, 574, 578], - "6": [1, 495, 499, 570, 582 ], -}; + "1": [ 258, 1, 510, 304, 431 ], + "2": [ 185, 93, 222, 295, 372 ], +} + + // Schoute's partition via https://arxiv.org/abs/1010.4353 diff --git a/gui.js b/gui.js index 5a747ee..646dd5a 100644 --- a/gui.js +++ b/gui.js @@ -28,9 +28,10 @@ const DEFAULTS = { class FourDGUI { - constructor(shapes, changeShape, setColor, setBackground, setNodeOpacity,setLinkOpacity, setVisibility, showDocs) { + constructor(funcs) { + this.shapes = funcs.shapes; this.gui = new GUI(); - const SHAPE_NAMES = shapes.map((s) => s.name); + const SHAPE_NAMES = this.shapes.map((s) => s.name); this.parseLinkParams(); const guiObj = this; @@ -55,41 +56,52 @@ class FourDGUI { captions: true, dtheta: this.link['dtheta'], dpsi: this.link['dpsi'], - "copy link": function () { guiObj.copyUrl() } + "copy link": function () { guiObj.copyUrl() }, }; + if( funcs.extras ) { + for( const label in funcs.extras ) { + console.log(label); + console.log(funcs.extras[label]); + this.params[label] = funcs.extras[label]; + } + } let options_ctrl; this.gui.add(this.params, 'shape', SHAPE_NAMES).onChange((shape) => { - const options = this.getShapeOptions(shapes, shape); + const options = this.getShapeOptions(shape); options_ctrl = options_ctrl.options(options).onChange((option) => { - setVisibility(option) + funcs.setVisibility(option) }); options_ctrl.setValue(options[0]) - changeShape(shape) + funcs.changeShape(shape) }); - const options = this.getShapeOptions(shapes, this.params['shape']); + const options = this.getShapeOptions(this.params['shape']); options_ctrl = this.gui.add(this.params, 'option').options(options).onChange((option) => { - setVisibility(option) + funcs.setVisibility(option) }); this.gui.add(this.params, 'hyperplane', 0.5, 1 / 0.8); this.gui.add(this.params, 'zoom', 0.1, 2.0); this.gui.add(this.params, 'nodesize', 0, 1.5); - this.gui.add(this.params, 'nodeopacity', 0, 1).onChange(setNodeOpacity); + this.gui.add(this.params, 'nodeopacity', 0, 1).onChange(funcs.setNodeOpacity); this.gui.add(this.params, 'linksize', 0, 2); - console.log(setLinkOpacity); - this.gui.add(this.params, 'linkopacity', 0, 1).onChange((v) => setLinkOpacity(v, true)); - this.gui.add(this.params, 'link2opacity', 0, 1).onChange((v) => setLinkOpacity(v, false)); - this.gui.addColor(this.params, 'color').onChange(setColor); - this.gui.addColor(this.params, 'background').onChange(setBackground); + console.log(funcs.setLinkOpacity); + this.gui.add(this.params, 'linkopacity', 0, 1).onChange((v) => funcs.setLinkOpacity(v, true)); + this.gui.add(this.params, 'link2opacity', 0, 1).onChange((v) => funcs.setLinkOpacity(v, false)); + this.gui.addColor(this.params, 'color').onChange(funcs.setColor); + this.gui.addColor(this.params, 'background').onChange(funcs.setBackground); this.gui.add(this.params, 'xRotate', [ 'YW', 'YZ', 'ZW' ]); this.gui.add(this.params, 'yRotate', [ 'XZ', 'XY', 'XW' ]); - this.gui.add(this.params, 'captions').onChange(showDocs); + this.gui.add(this.params, 'captions').onChange(this.showDocs); this.gui.add(this.params, 'damping'); this.gui.add(this.params, 'copy link'); - + if( funcs.extras ) { + for( const label in funcs.extras ) { + this.gui.add(this.params, label); + } + } } - getShapeOptions(shapes, shape) { - const spec = shapes.filter((s) => s.name === shape); + getShapeOptions(shape) { + const spec = this.shapes.filter((s) => s.name === shape); if( spec && spec[0].options ) { return spec[0].options.map((o) => o.name); } else { diff --git a/main.js b/main.js index aa8cab4..40eff35 100644 --- a/main.js +++ b/main.js @@ -200,14 +200,20 @@ function setVisibility(option_name) { gui = new FourDGUI( - STRUCTURES, - changeShape, - setColors, - setBackground, - setNodeOpacity, - setLinkOpacity, - setVisibility, - showDocs + { + shapes: STRUCTURES, + changeShape: changeShape, + setColors: setColors, + setBackground: setBackground, + setNodeOpacity: setNodeOpacity, + setLinkOpacity: setLinkOpacity, + setVisibility: setVisibility, + showDocs: showDocs, + extras: { + "Show an alert": function() { alert("hi there") }, + "Show a different one": function() { alert('yowza')}, + }, + } ); // these are here to pick up colour settings from the URL params diff --git a/polytopes.js b/polytopes.js index 89d7e2f..dd1f328 100644 --- a/polytopes.js +++ b/polytopes.js @@ -463,19 +463,27 @@ export const cell120_inscribed = () => { export const cell120_inscribed_cell5 = () => { const nodes = make_120cell_vertices(); - const links = auto_detect_edges(nodes, 4); + //const links = auto_detect_edges(nodes, 4); for( const cstr in CELLINDEX.INDEX120 ) { label_nodes(nodes, CELLINDEX.INDEX120[cstr], Number(cstr)); } - links.map((l) => l.label = 0); + //links.map((l) => l.label = 0); + + const links = []; + + for( const p of [ 1, 2 ]) { + const nodes600 = nodes.filter((n) => n.label === p); + const links600 = auto_detect_edges(nodes600, 12); + links600.map((l) => l.label = p); + links.push(...links600); + } for( const c5 in CELLINDEX.CELL120_CELL5 ) { const nodes5 = nodes.filter((n) => CELLINDEX.CELL120_CELL5[c5].includes(n.id)); - console.log(`node5 = ${nodes5}`); const links5 = auto_detect_edges(nodes5, 4); - links5.map((l) => l.label = c5); + links5.map((l) => l.label = 0); links.push(...links5); } @@ -484,8 +492,7 @@ export const cell120_inscribed_cell5 = () => { nodes: nodes, links: links, options: [ - { name: "none", links: [ 0 ]}, - { name: "one inscribed 5-cell", links: [ 0, 1 ] }, + { name: "5-cells", links: [ 0, 1, 2, 3, 4, 5, 6 ] }, ], description: `The 120-cell with one of its 5-cells.`, } From 10de708c19aab5996f8c5fc953ffd6ec6b8816f0 Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Tue, 30 Dec 2025 08:43:04 +1100 Subject: [PATCH 10/19] Added NOTES.md --- NOTES.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 NOTES.md diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000..5e59b50 --- /dev/null +++ b/NOTES.md @@ -0,0 +1,26 @@ +# NOTES + + +New approach for the 5-cells: + +Pick a tetrahedron of an inscribed 600-cell with vertices A, B, C, D + +This gives pairs of vertices: + +AB +AC +AD +BC +BD +CD + +Each of these gives rise to seven pairs of 5-cells which are on neighboring vertices +of the 5 600-cells. + +Try enumerating these and inspecting them to find one or more coherent sets of four +5-cells which lie on one tetrahedron from each of the 600-cells. + +(I expect there to be more than one, like how there are two ways to partition the +120-cell vertices into 600-cells) + + From f5afdff3bbef0d70b1970a40da5baf5a49267fd7 Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Tue, 30 Dec 2025 08:43:34 +1100 Subject: [PATCH 11/19] More exploration of the 5-cell inscriptions --- cellindex.js | 4 +-- explore_120cell.js | 64 +++++++++++++++++++++++++++------------------- polytopes.js | 4 +-- 3 files changed, 41 insertions(+), 31 deletions(-) diff --git a/cellindex.js b/cellindex.js index 48838bb..4a07c10 100644 --- a/cellindex.js +++ b/cellindex.js @@ -105,12 +105,10 @@ export const LAYERS120 = { }; export const CELL120_CELL5 = { - "1": [ 258, 1, 510, 304, 431 ], - "2": [ 185, 93, 222, 295, 372 ], + "1": [ 1, 93, 37, 157 ], } - // Schoute's partition via https://arxiv.org/abs/1010.4353 export const PARTITION600 = { diff --git a/explore_120cell.js b/explore_120cell.js index 474d301..af0a0de 100644 --- a/explore_120cell.js +++ b/explore_120cell.js @@ -161,9 +161,9 @@ export function gather_5cells(cell120) { } function audit_5cells(cells) { - // this verifies that for each label (a 600-cell set), each of its + // this verifies that for each label (a 600-cell set), each of its // vertices is in exactly 7 5-cells. It checks out. - + ['1','2','3','4','5'].map((l) => { const sets = {}; for( const cell of cells ) { @@ -199,7 +199,6 @@ function try_120_5_cells_fails(cell120, cells, l) { console.log("overlap"); console.log(c); return true; - } } return false; @@ -269,27 +268,24 @@ function node_dist(cell120, aid, bid) { return dist(a, b); } +function print_row(v1, v2, p, v5) { + console.log(`${v1.id},${v2.id},${p},${v5[1]},${v5[2]},${v5[3]},${v5[4]},${v5[5]}`); +} + +// for a pair of vertices which are on the same inscribed 600 cell, +// this returns all 7 pairs of 5-cells which contain v1 and v2 and +// which are also evenly spaced (ie every pair of vertices on the +// same 600-cell is one edge apart) -function follow_600(cell120, all5) { - const v = cell120.nodes[0]; - console.log("Start vertex:"); - console.log(v); - const v5s = all5.filter((c5) => c5[v.label] === v.id); - console.log(`Vertex ${v.id} belongs to these 5-cells:`); - console.log(v5s); - const n600s = neighbours600(cell120, v.id); - const n600id = n600s[0]; - const n600 = cell120node(cell120, n600id); - console.log("One 600-cell neighbour:"); - console.log(n600); - const DIST600 = round_dist(node_dist(cell120, v.id, n600id)); - const nv5s = all5.filter((c5) => c5[v.label] === n600id); - console.log(`Vertex ${n600id} belongs to these 5-cells:`); - console.log(nv5s); - console.log("Distances for each pair of 5-cells from the two sets:"); - for( const v5a of v5s ) { - for( const v5b of nv5s ) { +function find_adjoining_5cells(cell120, all5, v1, v2) { + const DIST600 = round_dist(node_dist(cell120, v1.id, v2.id)); + const v15s = all5.filter((c5) => c5[v1.label] === v1.id); + const v25s = all5.filter((c5) => c5[v2.label] === v2.id); + let p = 0; + const c5pairs = []; + for( const v5a of v15s ) { + for( const v5b of v25s ) { let match = true; const d = {}; for( const label in v5a ) { @@ -299,19 +295,35 @@ function follow_600(cell120, all5) { } } if( match ) { - console.log("--- pair ---"); - console.log(v5a); - console.log(v5b); + c5pairs.push([ v5a, v5b ]); } } } + return c5pairs; } +function tetra_600_cell(cell120, all5) { + // find a tetrahedron on one of the 600-cells + + const v1 = cell120.nodes[0]; + const n600s = neighbours600(cell120, v1.id); + const v2id = n600s[0]; + const n2600s = neighbours600(cell120, v2id); + // find mutual neighbours of the first two + const mutuals = n2600s.filter((nid) => nid != v2id && nid != v1.id && n600s.includes(nid)); + // find mutuals which are mutuals of each other + const m0 = mutuals[0]; + const mn = neighbours600(cell120, m0); + const mm = mn.filter((nid) => nid != m0 && mutuals.includes(nid)); + return [ v1.id, v2id, m0, mm[0] ]; +} const cell120 = POLYTOPES.cell120_inscribed(); const all5 = gather_5cells(cell120); -follow_600(cell120, all5); +const tetra = tetra_600_cell(cell120, all5); + +console.log(tetra); diff --git a/polytopes.js b/polytopes.js index dd1f328..cc26b25 100644 --- a/polytopes.js +++ b/polytopes.js @@ -473,14 +473,14 @@ export const cell120_inscribed_cell5 = () => { const links = []; - for( const p of [ 1, 2 ]) { +/* for( const p of [ 1, 2 ]) { const nodes600 = nodes.filter((n) => n.label === p); const links600 = auto_detect_edges(nodes600, 12); links600.map((l) => l.label = p); links.push(...links600); } - for( const c5 in CELLINDEX.CELL120_CELL5 ) { +*/ for( const c5 in CELLINDEX.CELL120_CELL5 ) { const nodes5 = nodes.filter((n) => CELLINDEX.CELL120_CELL5[c5].includes(n.id)); const links5 = auto_detect_edges(nodes5, 4); links5.map((l) => l.label = 0); From 506bf1cdfe03fa2e3a8446334522707778f4f995 Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Tue, 30 Dec 2025 16:04:41 +1100 Subject: [PATCH 12/19] Added a function which finds all of the 600-tetras from a vertex of the 120-cell --- explore_120 | 1 + explore_120cell.js | 63 ++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 53 insertions(+), 11 deletions(-) create mode 100644 explore_120 diff --git a/explore_120 b/explore_120 new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/explore_120 @@ -0,0 +1 @@ + diff --git a/explore_120cell.js b/explore_120cell.js index af0a0de..975287a 100644 --- a/explore_120cell.js +++ b/explore_120cell.js @@ -304,26 +304,67 @@ function find_adjoining_5cells(cell120, all5, v1, v2) { -function tetra_600_cell(cell120, all5) { - // find a tetrahedron on one of the 600-cells +function tetras(cell120, all5, v) { + // given a vertex v, find all of the 600-cell tetras it's on - const v1 = cell120.nodes[0]; - const n600s = neighbours600(cell120, v1.id); - const v2id = n600s[0]; - const n2600s = neighbours600(cell120, v2id); - // find mutual neighbours of the first two - const mutuals = n2600s.filter((nid) => nid != v2id && nid != v1.id && n600s.includes(nid)); - // find mutuals which are mutuals of each other + const n600s = neighbours600(cell120, v.id); + // need to find all sets of three neighbours which are neighbours: there + // should be 20 of these because they're faces of an icosahedron + console.log(v.id); + const tetras = new Set; + for( const v2id of n600s ) { + // find mutual neighbours of the first two + const n2600s = neighbours600(cell120, v2id); + const mutuals = n2600s.filter((nid) => { + return nid != v2id && nid != v.id && n600s.includes(nid) + }); + for( const nm of mutuals ) { + const nnms = neighbours600(cell120, nm); + const mutuals2 = nnms.filter((nid) => { + return nid != nm && nid != v2id && nid != v.id && mutuals.includes(nid) + }); + for( const m2 of mutuals2 ) { + const t = [ v.id, v2id, nm, m2 ]; + t.sort((a, b) => a - b); + const tstr = t.join(','); + tetras.add(tstr); + } + } + } + console.log(tetras); +} +/* // find mutuals which are mutuals of each other const m0 = mutuals[0]; const mn = neighbours600(cell120, m0); const mm = mn.filter((nid) => nid != m0 && mutuals.includes(nid)); return [ v1.id, v2id, m0, mm[0] ]; +}*/ + +function str5cell(c5) { + return ["1","2","3","4","5"].map((l) => String(c5[l]).padStart(3, '0')).join('-'); +} + +function tetra_sets(cell120, all5, tetra) { + // given a tetrahedron on a 600-cell, find the sets of adjacent 5-cells on + // all of the pairs + // this is ass-backwards. Need to find tetras on the other 4 vertices of a 5-cell + + const vs = tetra.map((tid) => cell120node(cell120, tid)); + const pairs = [[0,1], [0,2], [0, 3], [1, 2], [1, 3], [2, 3]]; + for( const p of pairs ) { + const v1 = vs[p[0]]; + const v2 = vs[p[1]]; + const c5pairs = find_adjoining_5cells(cell120, all5, v1, v2); + console.log(v1.id, v2.id); + console.log(c5pairs.map((p) => str5cell(p[0]) + " " + str5cell(p[1]))); + } } const cell120 = POLYTOPES.cell120_inscribed(); const all5 = gather_5cells(cell120); -const tetra = tetra_600_cell(cell120, all5); +const v1 = cell120.nodes[0]; -console.log(tetra); +tetras(cell120, all5, v1); +tetras(cell120, all5, cell120.nodes[1]); From 878209ab41f241807d376f05e42e4079d687e34c Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Thu, 1 Jan 2026 08:46:03 +1100 Subject: [PATCH 13/19] Exploratory code which seems to be able to pick out four 5-cells which connect five tetrahedra on the 600-cells --- explore_120cell.js | 95 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 71 insertions(+), 24 deletions(-) diff --git a/explore_120cell.js b/explore_120cell.js index 975287a..f544cff 100644 --- a/explore_120cell.js +++ b/explore_120cell.js @@ -1,14 +1,4 @@ -// TODO - try visualising the 5-cells from a vertex on the layered 120-cell -// and get an intuition for how they work with the dodecahedral structure - -// Another new approach - -// pick a starting 5-cell -// look at the neighbours on the 120-cell of all of its 5 vertices -// try to find abother 5-cell with these neighbours -// if there's only one, add that to the list and keep going -// if there's more than one, are they disjoint with each other? - import * as POLYTOPES from './polytopes.js'; // exploring more inscriptions of the 120-cell @@ -302,15 +292,12 @@ function find_adjoining_5cells(cell120, all5, v1, v2) { return c5pairs; } - - -function tetras(cell120, all5, v) { +function tetras(cell120, v) { // given a vertex v, find all of the 600-cell tetras it's on const n600s = neighbours600(cell120, v.id); // need to find all sets of three neighbours which are neighbours: there // should be 20 of these because they're faces of an icosahedron - console.log(v.id); const tetras = new Set; for( const v2id of n600s ) { // find mutual neighbours of the first two @@ -331,14 +318,23 @@ function tetras(cell120, all5, v) { } } } - console.log(tetras); + const tarray = []; + for( const t of tetras ) { + const ta = t.split(',').map((v) => Number(v)); + tarray.push(ta); + } + return tarray; +} + +function vertices(hedra) { + const v = new Set; + for ( const h of hedra) { + for( const p of h ) { + v.add(p); + } + } + return Array.from(v); } -/* // find mutuals which are mutuals of each other - const m0 = mutuals[0]; - const mn = neighbours600(cell120, m0); - const mm = mn.filter((nid) => nid != m0 && mutuals.includes(nid)); - return [ v1.id, v2id, m0, mm[0] ]; -}*/ function str5cell(c5) { return ["1","2","3","4","5"].map((l) => String(c5[l]).padStart(3, '0')).join('-'); @@ -360,11 +356,62 @@ function tetra_sets(cell120, all5, tetra) { } } +function cell5_neighbourhoods(cell120, all5, c5) { + const neighbours = {} + + for( const l in c5 ) { + const v = cell120node(cell120, c5[l]); + neighbours[l] = vertices(tetras(cell120, v)); + } + + // now take the set of all 5-cells and filter it to only those whose vertices + // are in the neighour sets. On first inspection there are 13? + + const n5cells = all5.filter((c5) => { + for( const l in c5 ) { + if( ! neighbours[l].includes(c5[l]) ) { + return false; + } + } + return true; + }); + return n5cells; +} + + +// pick a 5-cell, and then pick the nearest neighbours to its vertices on their +// respective inscribed 600-cells + const cell120 = POLYTOPES.cell120_inscribed(); const all5 = gather_5cells(cell120); -const v1 = cell120.nodes[0]; +const c5 = all5[0] -tetras(cell120, all5, v1); -tetras(cell120, all5, cell120.nodes[1]); +const nb = cell5_neighbourhoods(cell120, all5, c5); +// trying for all tetras + +// let's pick a tetra on the first vertex of the primary 5cell... + +const v1 = cell120node(cell120, c5["1"]); +const ts = tetras(cell120, v1); + +for( const t of ts ) { + + console.log(t); + + // ... and then see which of the neighbourhood 5-cells have at least one + // of its vertices + + const nt = nb.filter((n) => { + for( const l in n ) { + if( t.includes(n[l]) ) { + return true; + } + } + return false + }); + + console.log(nt); + console.log("\n"); +} From 264aa5e497c1502fea584368810795fbad18a0cd Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Thu, 1 Jan 2026 15:55:15 +1100 Subject: [PATCH 14/19] Interim visualisation of 4 5-cells and their tetrahedra --- cellindex.js | 16 ++++++++++++++-- polytopes.js | 16 ++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/cellindex.js b/cellindex.js index 4a07c10..14bc29c 100644 --- a/cellindex.js +++ b/cellindex.js @@ -105,8 +105,20 @@ export const LAYERS120 = { }; export const CELL120_CELL5 = { - "1": [ 1, 93, 37, 157 ], -} + "tetras": { + "1": [ 27, 223, 253, 419 ], + "2": [ 28, 76, 44, 112 ], + "3": [ 264, 238, 283, 197 ], + "4": [ 309, 84, 304, 578 ], + "5": [ 275, 225, 42, 521 ], + }, + "cell5s": { + "1": [ 27, 28, 264, 309, 275 ], + "2": [ 223, 76, 238, 84, 225 ], + "3": [ 253, 44, 283, 304, 42 ], + "4": [ 419, 112, 197, 578, 521 ], + }, +}; // Schoute's partition via https://arxiv.org/abs/1010.4353 diff --git a/polytopes.js b/polytopes.js index cc26b25..deb632d 100644 --- a/polytopes.js +++ b/polytopes.js @@ -480,13 +480,25 @@ export const cell120_inscribed_cell5 = () => { links.push(...links600); } -*/ for( const c5 in CELLINDEX.CELL120_CELL5 ) { - const nodes5 = nodes.filter((n) => CELLINDEX.CELL120_CELL5[c5].includes(n.id)); +*/ const TETRAS = CELLINDEX.CELL120_CELL5.tetras; + const CELL5S = CELLINDEX.CELL120_CELL5.cell5s; + + for( const t in TETRAS ) { + const nodes5 = nodes.filter((n) => TETRAS[t].includes(n.id)); const links5 = auto_detect_edges(nodes5, 4); + links5.map((l) => l.label = t); + links.push(...links5); + } + + for( const c5 in CELL5S ) { + const nodes5 = nodes.filter((n) => CELL5S[c5].includes(n.id)); + const links5 = auto_detect_edges(nodes5, 5); links5.map((l) => l.label = 0); links.push(...links5); } + + return { name: '120-cell-5-cell', nodes: nodes, From a395006523559b0c69fb355f250fcbaf4d4dbffb Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Thu, 1 Jan 2026 17:05:23 +1100 Subject: [PATCH 15/19] Refactoring the code which finds coherent sets of 5-cells --- cellindex.js | 10 +++++++ explore_120cell.js | 74 ++++++++++++++++++++++++++++------------------ 2 files changed, 56 insertions(+), 28 deletions(-) diff --git a/cellindex.js b/cellindex.js index 14bc29c..b493671 100644 --- a/cellindex.js +++ b/cellindex.js @@ -117,7 +117,17 @@ export const CELL120_CELL5 = { "2": [ 223, 76, 238, 84, 225 ], "3": [ 253, 44, 283, 304, 42 ], "4": [ 419, 112, 197, 578, 521 ], + "5": [ 339, 14, 384, 382, 337 ], + "6": [ 331, 4, 335, 390, 386 ], + "7": [ 427, 160, 551, 146, 557 ], + "8": [ 265, 60, 64, 295, 246 ], + "9": [ 473, 100, 495, 213, 462 ], + "10": [ 393, 6, 328, 397, 326 ], + "11": [ 539, 164, 439, 561, 142 ], + "12": [ 511, 122, 456, 595, 181 ], + "13": [ 555, 154, 152, 545, 429 ] }, + }; diff --git a/explore_120cell.js b/explore_120cell.js index f544cff..134652c 100644 --- a/explore_120cell.js +++ b/explore_120cell.js @@ -114,6 +114,15 @@ function overlap(c1, c2) { return false; } +function c5match(c1, c2) { + for( const l in c1 ) { + if( c1[l] != c2[l] ) { + return false; + } + } + return true; +} + export function gather_5cells(cell120) { const CHORD5 = round_dist(Math.sqrt(2.5)); @@ -379,39 +388,48 @@ function cell5_neighbourhoods(cell120, all5, c5) { } -// pick a 5-cell, and then pick the nearest neighbours to its vertices on their -// respective inscribed 600-cells +function cell5_tetras(cell120, all5, c5) { + const nb = cell5_neighbourhoods(cell120, all5, c5); + const v1 = cell120node(cell120, c5["1"]); + const ts = tetras(cell120, v1); + + const c5s = []; + for( const t of ts ) { + const nt = nb.filter((n) => { + for( const l in n ) { + if( t.includes(n[l]) ) { + return true; + } + } + return false + }); + for( const nc5 of nt ) { + const exact = c5s.filter((c) => c5match(c, nc5)); + if( exact.length === 0 ) { + const o = c5s.filter((c) => overlap(c, nc5)); + if( o.length > 0 ) { + console.log("Overlap", c5, o); + } else { + c5s.push(nc5); + } + } + } + } + return c5s; +} + + + + + const cell120 = POLYTOPES.cell120_inscribed(); const all5 = gather_5cells(cell120); const c5 = all5[0] -const nb = cell5_neighbourhoods(cell120, all5, c5); +const c5s = cell5_tetras(cell120, all5, c5); -// trying for all tetras +const celli = c5s.map((c5) => [ "1", "2", "3", "4", "5" ].map((l) => c5[l])); -// let's pick a tetra on the first vertex of the primary 5cell... - -const v1 = cell120node(cell120, c5["1"]); -const ts = tetras(cell120, v1); - -for( const t of ts ) { - - console.log(t); - - // ... and then see which of the neighbourhood 5-cells have at least one - // of its vertices - - const nt = nb.filter((n) => { - for( const l in n ) { - if( t.includes(n[l]) ) { - return true; - } - } - return false - }); - - console.log(nt); - console.log("\n"); -} +console.log(celli); From fa93a605629a851c571c94831b4a55e8da2365e8 Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Thu, 1 Jan 2026 17:29:55 +1100 Subject: [PATCH 16/19] I think this is working for all 120 5-cells --- explore_120cell.js | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/explore_120cell.js b/explore_120cell.js index 134652c..6ea33b7 100644 --- a/explore_120cell.js +++ b/explore_120cell.js @@ -419,6 +419,38 @@ function cell5_tetras(cell120, all5, c5) { } +function coherent_5cells_r(cell120, all5, c5s, c50) { + // Find next set of c5s, see if there are any we haven't seen, + // recurse into those ones + const c5ns = cell5_tetras(cell120, all5, c50); + const c5unseen = c5ns.filter((c5) => { + const matched = c5s.filter((c5b) => c5match(c5b, c5)); + return matched.length === 0; + }); + for( const c5u of c5unseen ) { + c5s.push(c5u); + } + for( const c5u of c5unseen ) { + coherent_5cells_r(cell120, all5, c5s, c5u); + } +} + + + +function coherent_5cells(cell120, all5) { + // pick a starting point, collect coherent 5_cells, continue till + // there aren't any new ones + + const c5set = []; + let c5 = all5[0]; + + const c5s = []; + coherent_5cells_r(cell120, all5, c5s, c5); + return c5s; +} + + + @@ -426,10 +458,9 @@ function cell5_tetras(cell120, all5, c5) { const cell120 = POLYTOPES.cell120_inscribed(); const all5 = gather_5cells(cell120); -const c5 = all5[0] - -const c5s = cell5_tetras(cell120, all5, c5); +const c5s = coherent_5cells(cell120, all5); const celli = c5s.map((c5) => [ "1", "2", "3", "4", "5" ].map((l) => c5[l])); console.log(celli); +console.log(celli.length); From 3df850dfa9710bbed8d52a862b58b0211b7a0cd0 Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Thu, 1 Jan 2026 17:38:43 +1100 Subject: [PATCH 17/19] OK I added some checks and I think it's actually correct! --- explore_120cell.js | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/explore_120cell.js b/explore_120cell.js index 6ea33b7..b88a891 100644 --- a/explore_120cell.js +++ b/explore_120cell.js @@ -462,5 +462,31 @@ const c5s = coherent_5cells(cell120, all5); const celli = c5s.map((c5) => [ "1", "2", "3", "4", "5" ].map((l) => c5[l])); -console.log(celli); -console.log(celli.length); + +// check it because I don't believe it yet + +const vertex_check = {}; + +for( const c5 of celli ) { + for( const l in c5 ) { + const v = c5[l]; + if( v in vertex_check ) { + console.log(`Double count vertex ${v}`); + } + vertex_check[v] = 1; + } +} + +for( let i = 1; i < 601; i++ ) { + if( !vertex_check[i] ) { + console.log(`v ${i} missing`); + } +} + + +const idict = {}; +for( let i = 1; i < 121; i++ ) { + idict[i] = celli[i - 1]; +} + +console.log(JSON.stringify(idict, null, 2)); From 2d63efec7c5a315179e7844b76f97024a5d8c648 Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Thu, 1 Jan 2026 18:20:21 +1100 Subject: [PATCH 18/19] Merged the 5-cell inscription into the main 120-cell options, fixed a bug where showing nodes and links was inadvertently broken --- cellindex.js | 862 ++++++++++++++++++++++++++++++++++++++++++++++++-- fourDShape.js | 4 +- main.js | 8 +- polytopes.js | 47 +-- 4 files changed, 864 insertions(+), 57 deletions(-) diff --git a/cellindex.js b/cellindex.js index b493671..0640a7a 100644 --- a/cellindex.js +++ b/cellindex.js @@ -106,27 +106,849 @@ export const LAYERS120 = { export const CELL120_CELL5 = { "tetras": { - "1": [ 27, 223, 253, 419 ], - "2": [ 28, 76, 44, 112 ], - "3": [ 264, 238, 283, 197 ], - "4": [ 309, 84, 304, 578 ], - "5": [ 275, 225, 42, 521 ], - }, + }, "cell5s": { - "1": [ 27, 28, 264, 309, 275 ], - "2": [ 223, 76, 238, 84, 225 ], - "3": [ 253, 44, 283, 304, 42 ], - "4": [ 419, 112, 197, 578, 521 ], - "5": [ 339, 14, 384, 382, 337 ], - "6": [ 331, 4, 335, 390, 386 ], - "7": [ 427, 160, 551, 146, 557 ], - "8": [ 265, 60, 64, 295, 246 ], - "9": [ 473, 100, 495, 213, 462 ], - "10": [ 393, 6, 328, 397, 326 ], - "11": [ 539, 164, 439, 561, 142 ], - "12": [ 511, 122, 456, 595, 181 ], - "13": [ 555, 154, 152, 545, 429 ] - }, + "1": [ + 27, + 28, + 264, + 309, + 275 + ], + "2": [ + 223, + 76, + 238, + 84, + 225 + ], + "3": [ + 253, + 44, + 283, + 304, + 42 + ], + "4": [ + 419, + 112, + 197, + 578, + 521 + ], + "5": [ + 339, + 14, + 384, + 382, + 337 + ], + "6": [ + 331, + 4, + 335, + 390, + 386 + ], + "7": [ + 427, + 160, + 551, + 146, + 557 + ], + "8": [ + 265, + 60, + 64, + 295, + 246 + ], + "9": [ + 473, + 100, + 495, + 213, + 462 + ], + "10": [ + 393, + 6, + 328, + 397, + 326 + ], + "11": [ + 539, + 164, + 439, + 561, + 142 + ], + "12": [ + 511, + 122, + 456, + 595, + 181 + ], + "13": [ + 555, + 154, + 152, + 545, + 429 + ], + "14": [ + 95, + 202, + 486, + 500, + 465 + ], + "15": [ + 471, + 208, + 502, + 484, + 89 + ], + "16": [ + 347, + 21, + 348, + 374, + 373 + ], + "17": [ + 487, + 203, + 94, + 468, + 497 + ], + "18": [ + 165, + 139, + 542, + 568, + 434 + ], + "19": [ + 367, + 18, + 355, + 368, + 353 + ], + "20": [ + 231, + 78, + 86, + 236, + 217 + ], + "21": [ + 356, + 17, + 366, + 354, + 365 + ], + "22": [ + 503, + 205, + 470, + 92, + 481 + ], + "23": [ + 527, + 106, + 584, + 195, + 421 + ], + "24": [ + 239, + 73, + 222, + 228, + 81 + ], + "25": [ + 543, + 138, + 168, + 435, + 565 + ], + "26": [ + 48, + 46, + 302, + 281, + 255 + ], + "27": [ + 248, + 62, + 293, + 58, + 267 + ], + "28": [ + 440, + 141, + 562, + 540, + 163 + ], + "29": [ + 274, + 30, + 312, + 261, + 29 + ], + "30": [ + 179, + 128, + 597, + 450, + 505 + ], + "31": [ + 376, + 22, + 375, + 346, + 345 + ], + "32": [ + 320, + 11, + 405, + 401, + 319 + ], + "33": [ + 448, + 173, + 130, + 587, + 519 + ], + "34": [ + 460, + 102, + 211, + 489, + 479 + ], + "35": [ + 388, + 2, + 392, + 329, + 333 + ], + "36": [ + 512, + 182, + 596, + 121, + 455 + ], + "37": [ + 592, + 170, + 516, + 443, + 133 + ], + "38": [ + 120, + 189, + 529, + 570, + 411 + ], + "39": [ + 420, + 198, + 577, + 522, + 111 + ], + "40": [ + 272, + 69, + 65, + 290, + 243 + ], + "41": [ + 488, + 93, + 498, + 204, + 467 + ], + "42": [ + 156, + 150, + 547, + 553, + 431 + ], + "43": [ + 252, + 53, + 286, + 297, + 55 + ], + "44": [ + 532, + 192, + 117, + 410, + 571 + ], + "45": [ + 400, + 7, + 321, + 396, + 323 + ], + "46": [ + 580, + 199, + 417, + 110, + 523 + ], + "47": [ + 296, + 63, + 245, + 266, + 59 + ], + "48": [ + 600, + 125, + 178, + 508, + 451 + ], + "49": [ + 68, + 72, + 269, + 242, + 291 + ], + "50": [ + 324, + 8, + 399, + 322, + 395 + ], + "51": [ + 499, + 96, + 485, + 466, + 201 + ], + "52": [ + 406, + 12, + 317, + 318, + 402 + ], + "53": [ + 563, + 144, + 437, + 162, + 537 + ], + "54": [ + 234, + 88, + 219, + 229, + 80 + ], + "55": [ + 350, + 24, + 349, + 371, + 372 + ], + "56": [ + 444, + 134, + 515, + 169, + 591 + ], + "57": [ + 258, + 40, + 39, + 277, + 307 + ], + "58": [ + 285, + 56, + 251, + 54, + 298 + ], + "59": [ + 546, + 151, + 153, + 430, + 556 + ], + "60": [ + 98, + 215, + 475, + 493, + 464 + ], + "61": [ + 474, + 214, + 99, + 461, + 496 + ], + "62": [ + 357, + 20, + 363, + 359, + 364 + ], + "63": [ + 490, + 212, + 459, + 101, + 480 + ], + "64": [ + 185, + 116, + 415, + 533, + 574 + ], + "65": [ + 378, + 16, + 343, + 341, + 380 + ], + "66": [ + 218, + 85, + 235, + 77, + 232 + ], + "67": [ + 342, + 15, + 377, + 379, + 344 + ], + "68": [ + 458, + 209, + 491, + 477, + 104 + ], + "69": [ + 514, + 135, + 441, + 590, + 172 + ], + "70": [ + 226, + 83, + 75, + 237, + 224 + ], + "71": [ + 530, + 119, + 569, + 190, + 412 + ], + "72": [ + 38, + 37, + 257, + 308, + 278 + ], + "73": [ + 414, + 113, + 188, + 575, + 536 + ], + "74": [ + 362, + 19, + 358, + 361, + 360 + ], + "75": [ + 334, + 1, + 330, + 387, + 391 + ], + "76": [ + 438, + 161, + 538, + 143, + 564 + ], + "77": [ + 550, + 157, + 426, + 560, + 147 + ], + "78": [ + 566, + 167, + 137, + 544, + 436 + ], + "79": [ + 126, + 177, + 452, + 507, + 599 + ], + "80": [ + 284, + 41, + 254, + 43, + 303 + ], + "81": [ + 494, + 97, + 476, + 463, + 216 + ], + "82": [ + 200, + 109, + 418, + 524, + 579 + ], + "83": [ + 263, + 25, + 26, + 276, + 310 + ], + "84": [ + 300, + 50, + 52, + 249, + 287 + ], + "85": [ + 558, + 145, + 428, + 159, + 552 + ], + "86": [ + 403, + 9, + 316, + 315, + 407 + ], + "87": [ + 74, + 82, + 227, + 221, + 240 + ], + "88": [ + 289, + 66, + 244, + 271, + 70 + ], + "89": [ + 306, + 34, + 280, + 33, + 259 + ], + "90": [ + 572, + 118, + 531, + 409, + 191 + ], + "91": [ + 207, + 90, + 472, + 483, + 501 + ], + "92": [ + 369, + 23, + 370, + 351, + 352 + ], + "93": [ + 585, + 132, + 175, + 517, + 446 + ], + "94": [ + 105, + 196, + 528, + 583, + 422 + ], + "95": [ + 241, + 67, + 292, + 71, + 270 + ], + "96": [ + 425, + 148, + 559, + 549, + 158 + ], + "97": [ + 525, + 193, + 108, + 423, + 582 + ], + "98": [ + 174, + 129, + 588, + 447, + 520 + ], + "99": [ + 313, + 10, + 404, + 408, + 314 + ], + "100": [ + 413, + 187, + 576, + 535, + 114 + ], + "101": [ + 573, + 186, + 416, + 115, + 534 + ], + "102": [ + 49, + 51, + 299, + 288, + 250 + ], + "103": [ + 449, + 180, + 127, + 598, + 506 + ], + "104": [ + 469, + 91, + 206, + 504, + 482 + ], + "105": [ + 513, + 171, + 589, + 136, + 442 + ], + "106": [ + 279, + 35, + 305, + 260, + 36 + ], + "107": [ + 389, + 3, + 385, + 336, + 332 + ], + "108": [ + 593, + 183, + 509, + 454, + 124 + ], + "109": [ + 149, + 155, + 554, + 432, + 548 + ], + "110": [ + 325, + 5, + 394, + 327, + 398 + ], + "111": [ + 453, + 123, + 510, + 184, + 594 + ], + "112": [ + 383, + 13, + 338, + 340, + 381 + ], + "113": [ + 311, + 31, + 273, + 32, + 262 + ], + "114": [ + 581, + 107, + 526, + 424, + 194 + ], + "115": [ + 61, + 57, + 268, + 247, + 294 + ], + "116": [ + 87, + 79, + 230, + 220, + 233 + ], + "117": [ + 301, + 47, + 45, + 256, + 282 + ], + "118": [ + 131, + 176, + 445, + 518, + 586 + ], + "119": [ + 210, + 103, + 457, + 478, + 492 + ], + "120": [ + 140, + 166, + 567, + 433, + 541 + ] + }, }; diff --git a/fourDShape.js b/fourDShape.js index 7cd76db..7aaeca4 100644 --- a/fourDShape.js +++ b/fourDShape.js @@ -62,7 +62,7 @@ class FourDShape extends THREE.Group { const s1 = this.link_scale * n1.scale; const s2 = this.link_scale * n2.scale; link.object.update(n1, n2, s1, s2); - link.object.visible = (!links_show || link.label in links_show); + link.object.visible = (!links_show || links_show.includes(link.label)); } @@ -143,7 +143,7 @@ class FourDShape extends THREE.Group { this.nodes3[n.id].scale = k * this.foreshortening; this.nodes3[n.id].object.position.copy(v3); this.nodes3[n.id].object.scale.copy(s3); - this.nodes3[n.id].object.visible = ( !nodes_show || n.label in nodes_show ); + this.nodes3[n.id].object.visible = ( !nodes_show || nodes_show.includes(n.label) ); } for( const l of this.links ) { this.updateLink(l, links_show); diff --git a/main.js b/main.js index 40eff35..ee7b5c9 100644 --- a/main.js +++ b/main.js @@ -68,6 +68,8 @@ link_ms.map((m) => { } ); +console.log("link_ms", link_ms); + const face_ms = [ new THREE.MeshStandardMaterial( { color: 0x44ff44 } ) @@ -189,6 +191,8 @@ function changeShape() { } function setVisibility(option_name) { + console.log("setVisibility", option_name); + console.log(structure.options); const option = structure.options.filter((o) => o.name === option_name); if( option.length ) { node_show = option[0].nodes; @@ -209,10 +213,6 @@ gui = new FourDGUI( setLinkOpacity: setLinkOpacity, setVisibility: setVisibility, showDocs: showDocs, - extras: { - "Show an alert": function() { alert("hi there") }, - "Show a different one": function() { alert('yowza')}, - }, } ); diff --git a/polytopes.js b/polytopes.js index deb632d..9871375 100644 --- a/polytopes.js +++ b/polytopes.js @@ -443,6 +443,15 @@ export const cell120_inscribed = () => { links.push(...links600); } + const CELL5S = CELLINDEX.CELL120_CELL5.cell5s; + + for( const c5 in CELL5S ) { + const nodes5 = nodes.filter((n) => CELL5S[c5].includes(n.id)); + const links5 = auto_detect_edges(nodes5, 5); + links5.map((l) => l.label = 8); + links.push(...links5); + } + return { name: '120-cell', nodes: nodes, @@ -450,12 +459,14 @@ export const cell120_inscribed = () => { options: [ { name: "none", links: [ 0 ]}, { name: "one inscribed 600-cell", links: [ 0, 1 ] }, - { name: "five inscribed 600-cells", links: [ 0, 1, 2, 3, 4, 5 ] } + { name: "five inscribed 600-cells", links: [ 0, 1, 2, 3, 4, 5 ] }, + { name: "120 inscribed 5-cells", links: [ 0, 8 ] }, ], description: `The 120-cell is the four-dimensional analogue of the dodecahedron, and consists of 120 dodecahedra joined at 720 faces, with three dodecahedra around each edge. It is dual to the 600-cell, - and five 600-cells can be inscribed in its vertices.`, + and five 600-cells can be inscribed in its vertices. Its 600 vertices + can also be divided between 120 5-cells.`, } } @@ -463,39 +474,14 @@ export const cell120_inscribed = () => { export const cell120_inscribed_cell5 = () => { const nodes = make_120cell_vertices(); - //const links = auto_detect_edges(nodes, 4); + const links = auto_detect_edges(nodes, 4); for( const cstr in CELLINDEX.INDEX120 ) { label_nodes(nodes, CELLINDEX.INDEX120[cstr], Number(cstr)); } - //links.map((l) => l.label = 0); + links.map((l) => l.label = 0); - const links = []; - -/* for( const p of [ 1, 2 ]) { - const nodes600 = nodes.filter((n) => n.label === p); - const links600 = auto_detect_edges(nodes600, 12); - links600.map((l) => l.label = p); - links.push(...links600); - } - -*/ const TETRAS = CELLINDEX.CELL120_CELL5.tetras; - const CELL5S = CELLINDEX.CELL120_CELL5.cell5s; - - for( const t in TETRAS ) { - const nodes5 = nodes.filter((n) => TETRAS[t].includes(n.id)); - const links5 = auto_detect_edges(nodes5, 4); - links5.map((l) => l.label = t); - links.push(...links5); - } - - for( const c5 in CELL5S ) { - const nodes5 = nodes.filter((n) => CELL5S[c5].includes(n.id)); - const links5 = auto_detect_edges(nodes5, 5); - links5.map((l) => l.label = 0); - links.push(...links5); - } @@ -504,7 +490,7 @@ export const cell120_inscribed_cell5 = () => { nodes: nodes, links: links, options: [ - { name: "5-cells", links: [ 0, 1, 2, 3, 4, 5, 6 ] }, + { name: "5-cells", links: [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ] }, ], description: `The 120-cell with one of its 5-cells.`, } @@ -926,7 +912,6 @@ export const build_all = () => { cell600(), cell600_layered(), cell120_inscribed(), - cell120_inscribed_cell5(), cell120_layered() ]; } From c1137f4da2e5df574273e2076e9379b4f2983948 Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Thu, 1 Jan 2026 18:24:55 +1100 Subject: [PATCH 19/19] Updated the CHANGELOG and release number --- CHANGELOG.md | 5 +++++ colours.js | 3 ++- index.html | 2 +- polytopes.js | 5 +++-- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1336378..da9dd9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +## v1.1 - 1/1/2025 + +The 120-cell now includes a visualisation of its inscribed 5-cells, which honestly +looks like less of a mess than I expected it to. + ## v1.0 - 16/11/2025 It's been [two years](https://mikelynch.org/2023/Sep/02/120-cell/) since diff --git a/colours.js b/colours.js index f85f25a..7fda5d8 100644 --- a/colours.js +++ b/colours.js @@ -14,6 +14,7 @@ export const get_colours = (basis) => { const hsl = colours.map((c) => Color("#" + c).hsl()); const resaturated = hsl.map((hslc) => hslc.saturationl(saturation).rgbNumber()); resaturated.unshift(basis); + console.log(resaturated); return resaturated; } @@ -34,4 +35,4 @@ export const get_plain_colours = (basis) => { 0xff9900, 0x000000, ] -} \ No newline at end of file +} diff --git a/index.html b/index.html index 03e203b..0460f61 100644 --- a/index.html +++ b/index.html @@ -36,7 +36,7 @@
-
release 1.0 | + diff --git a/polytopes.js b/polytopes.js index 9871375..8fc7669 100644 --- a/polytopes.js +++ b/polytopes.js @@ -465,8 +465,9 @@ export const cell120_inscribed = () => { description: `The 120-cell is the four-dimensional analogue of the dodecahedron, and consists of 120 dodecahedra joined at 720 faces, with three dodecahedra around each edge. It is dual to the 600-cell, - and five 600-cells can be inscribed in its vertices. Its 600 vertices - can also be divided between 120 5-cells.`, + and five 600-cells can be inscribed in its vertices. The converse + of this allows 120 5-cells (each of which has one vertex in each + of the 5 600-cells) to be inscribed in the 120-cell.`, } }