Hồng ngọc
Lý lịch
Có ba họ của polytope thường xuyên mở rộng thành các chiều vô hạn:
các đơn giản, trong đó tứ diện là một thành viên (tôi sẽ thường gọi chúng ở đây là hypertetrahedra, mặc dù thuật ngữ đơn giản là chính xác hơn.) Các biểu tượng schlafi của chúng có dạng {3,3,...,3,3}
các khối n, trong đó khối lập phương là thành viên. Biểu tượng schlafi của họ có dạng{4,3,...,3,3}
các orthoplexes, trong đó bát diện là một thành viên (tôi sẽ thường gọi chúng ở đây là hyperoctahedra) Các biểu tượng schlafi của chúng có dạng {3,3,...,3,4}
Có thêm một họ vô hạn của các đa giác thông thường, biểu tượng {m}
đa giác , đó là đa giác 2 chiều, có thể có bất kỳ số cạnh m nào.
Ngoài ra, chỉ có năm trường hợp đặc biệt khác của polytope thông thường: icosahedron 3 chiều {3,5}
và dodecahedron {5,3}
; 4 chiều của chúng tương tự 600 tế bào {3,3,5}
và 120 tế bào {5,3,3}
; và một đa giác 4 chiều khác, 24 ô{3,4,3}
(có các tương tự gần nhất trong 3 chiều là khối lập phương và khối kép của nó là khối mười hai hình thoi.)
Chức năng chính
Dưới đây là polytope
chức năng chính diễn giải biểu tượng schlafi. Nó mong đợi một mảng các số và trả về một mảng chứa một loạt các mảng như sau:
Một mảng gồm tất cả các đỉnh, mỗi đỉnh được biểu thị dưới dạng một mảng phần tử n (trong đó n là số lượng kích thước)
Một mảng gồm tất cả các cạnh, mỗi cạnh được biểu thị dưới dạng 2 phần tử của các chỉ số đỉnh
Một mảng gồm tất cả các mặt, mỗi mặt được biểu thị dưới dạng phần tử m của các chỉ số đỉnh (trong đó m là số đỉnh trên mỗi mặt)
và như vậy là thích hợp cho số lượng kích thước.
Nó tự tính toán các đa giác 2d, gọi các hàm cho 3 họ chiều vô hạn và sử dụng các bảng tra cứu cho năm trường hợp đặc biệt. Nó hy vọng sẽ tìm thấy các hàm và bảng được khai báo ở trên nó.
include Math
#code in subsequent sections of this answer should be inserted here
polytope=->schl{
if schl.size==1 #if a single digit calculate and return a polygon
return [(1..schl[0]).map{|i|[sin(PI*2*i/schl[0]),cos(PI*2*i/schl[0])]},(1..schl[0]).map{|i|[i%schl[0],(i+1)%schl[0]]}]
elsif i=[[3,5],[5,3]].index(schl) #if a 3d special, lookup from tables
return [[vv,ee,ff],[uu,aa,bb]][i]
elsif i=[[3,3,5],[5,3,3],[3,4,3]].index(schl) #if a 4d special. lookup fromm tables
return [[v,e,f,g],[u,x,y,z],[o,p,q,r]][i]
elsif schl.size==schl.count(3) #if all threes, call tetr for a hypertetrahedron
return tetr[schl.size+1]
elsif schl.size-1==schl.count(3) #if all except one number 3
return cube[schl.size+1] if schl[0]==4 #and the 1st digit is 4, call cube for a hypercube
return octa[schl.size+1] if schl[-1]==4 #and the last digit is 4, call octa for a hyperoctahedron
end
return "error" #in any other case return an error
}
Chức năng cho các họ tứ diện, khối lập phương và khối bát diện
https://en.wikipedia.org/wiki/Simplex
https://en.wikipedia.org/wiki/5-cell (4d đơn giản)
http://mathworld.wolfram.com/Simplex.html
Giải thích gia đình tứ diện - tọa độ
một đơn giản n / hypertetrahedron n chiều có n + 1 điểm. Rất dễ dàng để đưa ra các đỉnh của đơn giản n chiều trong n + 1 chiều.
Do đó (1,0,0),(0,1,0),(0,0,1)
mô tả một tam giác 2d được nhúng trong 3 chiều và (1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1)
mô tả một tứ diện 3d được nhúng trong 4 chiều. Điều này dễ dàng được xác minh bằng cách xác nhận rằng tất cả các khoảng cách giữa các đỉnh là sqrt (2).
Các thuật toán phức tạp khác nhau được đưa ra trên internet để tìm các đỉnh cho đơn giản n chiều trong không gian n chiều. Tôi đã tìm thấy một câu hỏi đơn giản đáng chú ý trong nhận xét của Will Jagy về câu trả lời này /mathpro//a/38725 . Điểm cuối cùng nằm trên đường p=q=...=x=y=z
ở khoảng cách sqrt (2) so với các điểm khác. Do đó, tam giác trên có thể được chuyển đổi thành một tứ diện bằng cách thêm một điểm tại một trong hai (-1/3,-1/3,-1/3)
hoặc (1,1,1)
. Hai giá trị có thể có của tọa độ cho điểm cuối cùng được cho bởi (1-(1+n)**0.5)/n
và(1+(1+n)**0.5)/n
Như câu hỏi nói rằng kích thước của n-tope không quan trọng, tôi thích nhân với n và sử dụng tọa độ (n,0,0..0)
cho đến (0..0,0,n)
điểm cuối cùng trong (t,t,..,t,t)
đó t = 1-(1+n)**0.5
cho đơn giản.
Vì tâm của khối tứ diện này không nằm ở gốc tọa độ, nên việc hiệu chỉnh tất cả các tọa độ phải được thực hiện bằng đường thẳng s.map!{|j|j-((1-(1+n)**0.5)+n)/(1+n)}
tìm trung tâm cách gốc tọa độ bao xa và trừ đi. Tôi đã giữ điều này như là một hoạt động riêng biệt. Tuy nhiên tôi đã sử dụng s[i]+=n
nơi s[i]=n
sẽ làm, để ám chỉ thực tế là khi mảng được khởi tạo bởi s=[0]*n
chúng ta có thể đặt phần bù chính xác vào đây thay vào đó và thực hiện chỉnh sửa định tâm ngay từ đầu thay vì kết thúc.
Giải thích gia đình tứ diện - cấu trúc liên kết đồ thị
Biểu đồ của đơn giản là biểu đồ hoàn chỉnh: mọi đỉnh được kết nối chính xác một lần với mọi đỉnh khác. Nếu chúng ta có một n đơn giản, chúng ta có thể loại bỏ bất kỳ đỉnh nào để tạo một đơn vị n-1, xuống đến điểm mà chúng ta có một hình tam giác hoặc thậm chí là một cạnh.
Do đó, chúng tôi có tổng cộng 2 ** (n + 1) mục để phân loại, mỗi mục được biểu thị bằng một số nhị phân. Điều này dao động từ tất cả 0
s cho hư vô, thông qua một 1
cho một đỉnh và hai 1
s cho một cạnh, lên đến tất cả1
s cho đa giác hoàn chỉnh.
Chúng tôi thiết lập một mảng các mảng trống để lưu trữ các phần tử của từng kích thước. Sau đó, chúng tôi lặp từ 0 đến (2 ** n + 1) để tạo từng tập con có thể của các đỉnh và lưu trữ chúng trong mảng theo kích thước của mỗi tập hợp con.
Chúng tôi không quan tâm đến bất cứ điều gì nhỏ hơn một cạnh (một đỉnh hoặc một số 0) cũng như trong đa giác hoàn chỉnh (vì khối hoàn chỉnh không được đưa ra trong ví dụ trong câu hỏi), vì vậy chúng tôi quay lại tg[2..n]
để loại bỏ các yếu tố không mong muốn này. Trước khi trở về, chúng tôi đã xử lý [tv] chứa tọa độ đỉnh ở đầu.
mã
tetr=->n{
#Tetrahedron Family Vertices
tv=(0..n).map{|i|
s=[0]*n
if i==n
s.map!{(1-(1+n)**0.5)}
else
s[i]+=n
end
s.map!{|j|j-((1-(1+n)**0.5)+n)/(1+n)}
s}
#Tetrahedron Family Graph
tg=(0..n+1).map{[]}
(2**(n+1)).times{|i|
s=[]
(n+1).times{|j|s<<j if i>>j&1==1}
tg[s.size]<<s
}
return [tv]+tg[2..n]}
cube=->n{
#Cube Family Vertices
cv=(0..2**n-1).map{|i|s=[];n.times{|j|s<<(i>>j&1)*2-1};s}
#Cube Family Graph
cg=(0..n+1).map{[]}
(3**n).times{|i| #for each point
s=[]
cv.size.times{|j| #and each vertex
t=true #assume vertex goes with point
n.times{|k| #and each pair of opposite sides
t&&= (i/(3**k)%3-1)*cv[j][k]!=-1 #if the vertex has kingsmove distance >1 from point it does not belong
}
s<<j if t #add the vertex if it belongs
}
cg[log2(s.size)+1]<<s if s.size > 0
}
return [cv]+cg[2..n]}
octa=->n{
#Octahedron Family Vertices
ov=(0..n*2-1).map{|i|s=[0]*n;s[i/2]=(-1)**i;s}
#Octahedron Family Graph
og=(0..n).map{[]}
(3**n).times{|i| #for each point
s=[]
ov.size.times{|j| #and each vertex
n.times{|k| #and each pair of opposite sides
s<<j if (i/(3**k)%3-1)*ov[j][k]==1 #if the vertex is located in the side corresponding to the point, add the vertex to the list
}
}
og[s.size]<<s
}
return [ov]+og[2..n]}
giải thích các khối lập phương và khối bát diện - tọa độ
Khối lập phương n có 2**n
các đỉnh, mỗi đỉnh được biểu thị bằng một mảng n 1
s và -1
s (tất cả các khả năng đều được phép.) Chúng tôi lặp qua các chỉ mục 0
đến 2**n-1
danh sách tất cả các đỉnh và xây dựng một mảng cho mỗi đỉnh bằng cách lặp qua các bit của lập chỉ mục và thêm -1
hoặc 1
vào mảng (bit quan trọng nhất đến bit quan trọng nhất.) Do đó Binary 1101
trở thành điểm 4d [1,-1,1,1]
.
N-octahedron hoặc n-orthoplex có 2n
các đỉnh, với tất cả các tọa độ bằng 0 trừ một tọa độ, 1
hoặc là một -1
. Thứ tự của các đỉnh trong mảng được tạo là [[1,0,0..],[-1,0,0..],[0,1,0..],[0,-1,0..],[0,0,1..],[0,0,-1..]...]
. Lưu ý rằng vì bát diện là kép của khối lập phương, các đỉnh của khối bát diện được xác định bởi các tâm của các mặt của khối lập phương bao quanh nó.
giải thích các họ khối và khối bát diện - cấu trúc liên kết đồ thị
Một số nguồn cảm hứng được lấy từ các phía Hypercube và thực tế rằng hyperoctahedron là kép của hypercube.
Đối với n-cube, có 3**n
các mục để phân loại. Ví dụ: khối 3 có 3**3
= 27 phần tử. Điều này có thể được nhìn thấy bằng cách nghiên cứu khối lập phương của rubik, có 1 tâm, 6 mặt, 12 cạnh và 8 đỉnh với tổng số 27. Chúng tôi lặp qua -1,0 và -1 trong tất cả các kích thước xác định một khối n có độ dài 2x2x2 .. và trả lại tất cả các đỉnh KHÔNG ở phía đối diện của khối. Do đó, tâm điểm của khối lập phương trả về tất cả các đỉnh 2 ** n và di chuyển một đơn vị ra khỏi tâm dọc theo bất kỳ trục nào làm giảm một nửa số đỉnh.
Như với họ tứ diện, chúng ta bắt đầu bằng cách tạo ra một mảng trống và điền vào nó theo số lượng đỉnh trên mỗi phần tử. Lưu ý rằng vì số lượng đỉnh thay đổi là 2 ** n khi chúng ta đi qua các cạnh, mặt, hình khối, v.v., chúng ta sử dụng log2(s.size)+1
thay vì đơn giản s.size
. Một lần nữa, chúng ta phải loại bỏ hypercube và tất cả các phần tử có ít hơn 2 đỉnh trước khi quay trở lại hàm.
Họ bát diện / orthoplex là đối ngẫu của họ khối, do đó một lần nữa có 3**n
các mục để phân loại. Ở đây chúng tôi lặp lại -1,0,1
cho tất cả các kích thước và nếu tọa độ khác không của một đỉnh bằng với tọa độ tương ứng của điểm, thì đỉnh được thêm vào danh sách tương ứng với điểm đó. Do đó, một cạnh tương ứng với một điểm có hai tọa độ khác 0, một tam giác với một điểm có 3 tọa độ khác 0 và một tứ diện với một điểm có 4 tiếp điểm khác không (trong không gian 4d.)
Các mảng kết quả của đỉnh cho mỗi điểm được lưu trữ trong một mảng lớn như đối với các trường hợp khác và chúng tôi phải xóa bất kỳ phần tử nào có ít hơn 2 đỉnh trước khi quay lại. Nhưng trong trường hợp này, chúng tôi không phải xóa n-tope cha hoàn chỉnh vì thuật toán không ghi lại nó.
Việc triển khai mã cho khối được thiết kế giống nhau nhất có thể. Trong khi điều này có một sự tao nhã nhất định, có khả năng các thuật toán hiệu quả hơn dựa trên các nguyên tắc tương tự có thể được đưa ra.
https://en.wikipedia.org/wiki/Hypercube
http://mathworld.wolfram.com/Hypercube.html
https://en.wikipedia.org/wiki/Cross-polytope
http://mathworld.wolfram.com/CrossPolytope.html
Mã để tạo bảng cho các trường hợp đặc biệt 3d
Một hướng với icosahedron / dodecahedron được định hướng với trục đối xứng năm lần song song với kích thước cuối cùng đã được sử dụng, vì nó được tạo ra để dán nhãn nhất quán cho các bộ phận. Việc đánh số các đỉnh và các mặt của icosahedron là theo sơ đồ trong các nhận xét mã, và đảo ngược cho khối mười hai mặt.
Theo https://en.wikipedia.org/wiki/Regular_icosahedron , vĩ độ của 10 đỉnh không phân cực của icosahedron là +/- arctan (1/2) Các tọa độ của 10 đỉnh đầu tiên của icosahedron được tính từ này, trên hai vòng tròn bán kính 2 ở khoảng cách +/- 2 từ mặt phẳng xy. Điều này làm cho bán kính tổng thể của chu vi hình vuông (5) vì vậy 2 đỉnh cuối cùng nằm ở (0,0, + / - sqrt (2)).
Các tọa độ của các đỉnh của khối mười hai mặt được tính bằng cách tính tổng tọa độ của ba đỉnh icosahedron bao quanh chúng.
=begin
TABLE NAMES vertices edges faces
icosahedron vv ee ff
dodecahedron uu aa bb
10
/ \ / \ / \ / \ / \
/10 \ /12 \ /14 \ /16 \ /18 \
-----1-----3-----5-----7-----9
\ 0 / \ 2 / \ 4 / \ 6 / \ 8 / \
\ / 1 \ / 3 \ / 5 \ / 7 \ / 9 \
0-----2-----4-----6-----8-----
\11 / \13 / \15 / \17 / \19 /
\ / \ / \ / \ / \ /
11
=end
vv=[];ee=[];ff=[]
10.times{|i|
vv[i]=[2*sin(PI/5*i),2*cos(PI/5*i),(-1)**i]
ee[i]=[i,(i+1)%10];ee[i+10]=[i,(i+2)%10];ee[i+20]=[i,11-i%2]
ff[i]=[(i-1)%10,i,(i+1)%10];ff[i+10]=[(i-1)%10,10+i%2,(i+1)%10]
}
vv+=[[0,0,-5**0.5],[0,0,5**0.5]]
uu=[];aa=[];bb=[]
10.times{|i|
uu[i]=(0..2).map{|j|vv[ff[i][0]][j]+vv[ff[i][1]][j]+vv[ff[i][2]][j]}
uu[i+10]=(0..2).map{|j|vv[ff[i+10][0]][j]+vv[ff[i+10][1]][j]+vv[ff[i+10][2]][j]}
aa[i]=[i,(i+1)%10];aa[i+10]=[i,(i+10)%10];aa[i+20]=[(i-1)%10+10,(i+1)%10+10]
bb[i]=[(i-1)%10+10,(i-1)%10,i,(i+1)%10,(i+1)%10+10]
}
bb+=[[10,12,14,16,18],[11,13,15,17,19]]
Mã để tạo các bảng cho trường hợp đặc biệt 4d
Đây là một chút của một hack. Mã này mất vài giây để chạy. Sẽ tốt hơn để lưu trữ đầu ra trong một tệp và tải nó theo yêu cầu.
Danh sách 120 tọa độ đỉnh cho 600cell là từ http://mathworld.wolfram.com/600-Cell.html . 24 tọa độ đỉnh không có tỷ lệ vàng tạo thành các đỉnh của 24 ô. Wikipedia có cùng sơ đồ nhưng có lỗi trong thang đo tương đối của 24 tọa độ này và 96 tọa độ khác.
#TABLE NAMES vertices edges faces cells
#600 cell (analogue of icosahedron) v e f g
#120 cell (analogue of dodecahedron) u x y z
#24 cell o p q r
#600-CELL
# 120 vertices of 600cell. First 24 are also vertices of 24-cell
v=[[2,0,0,0],[0,2,0,0],[0,0,2,0],[0,0,0,2],[-2,0,0,0],[0,-2,0,0],[0,0,-2,0],[0,0,0,-2]]+
(0..15).map{|j|[(-1)**(j/8),(-1)**(j/4),(-1)**(j/2),(-1)**j]}+
(0..95).map{|i|j=i/12
a,b,c,d=1.618*(-1)**(j/4),(-1)**(j/2),0.618*(-1)**j,0
h=[[a,b,c,d],[b,a,d,c],[c,d,a,b],[d,c,b,a]][i%12/3]
(i%3).times{h[0],h[1],h[2]=h[1],h[2],h[0]}
h}
#720 edges of 600cell. Identified by minimum distance of 2/phi between them
e=[]
120.times{|i|120.times{|j|
e<<[i,j] if i<j && ((v[i][0]-v[j][0])**2+(v[i][1]-v[j][1])**2+(v[i][2]-v[j][2])**2+(v[i][3]-v[j][3])**2)**0.5<1.3
}}
#1200 faces of 600cell.
#If 2 edges share a common vertex and the other 2 vertices form an edge in the list, it is a valid triangle.
f=[]
720.times{|i|720.times{|j|
f<< [e[i][0],e[i][1],e[j][1]] if i<j && e[i][0]==e[j][0] && e.index([e[i][1],e[j][1]])
}}
#600 cells of 600cell.
#If 2 triangles share a common edge and the other 2 vertices form an edge in the list, it is a valid tetrahedron.
g=[]
1200.times{|i|1200.times{|j|
g<< [f[i][0],f[i][1],f[i][2],f[j][2]] if i<j && f[i][0]==f[j][0] && f[i][1]==f[j][1] && e.index([f[i][2],f[j][2]])
}}
#120 CELL (dual of 600 cell)
#600 vertices of 120cell, correspond to the centres of the cells of the 600cell
u=g.map{|i|s=[0,0,0,0];i.each{|j|4.times{|k|s[k]+=v[j][k]/4.0}};s}
#1200 edges of 120cell at centres of faces of 600-cell. Search for pairs of tetrahedra with common face
x=f.map{|i|s=[];600.times{|j|s<<j if i==(i & g[j])};s}
#720 pentagonal faces, surrounding edges of 600-cell. Search for sets of 5 tetrahedra with common edge
y=e.map{|i|s=[];600.times{|j|s<<j if i==(i & g[j])};s}
#120 dodecahedral cells surrounding vertices of 600-cell. Search for sets of 20 tetrahedra with common vertex
z=(0..119).map{|i|s=[];600.times{|j|s<<j if [i]==([i] & g[j])};s}
#24-CELL
#24 vertices, a subset of the 600cell
o=v[0..23]
#96 edges, length 2, found by minimum distances between vertices
p=[]
24.times{|i|24.times{|j|
p<<[i,j] if i<j && ((v[i][0]-v[j][0])**2+(v[i][1]-v[j][1])**2+(v[i][2]-v[j][2])**2+(v[i][3]-v[j][3])**2)**0.5<2.1
}}
#96 triangles
#If 2 edges share a common vertex and the other 2 vertices form an edge in the list, it is a valid triangle.
q=[]
96.times{|i|96.times{|j|
q<< [p[i][0],p[i][1],p[j][1]] if i<j && p[i][0]==p[j][0] && p.index([p[i][1],p[j][1]])
}}
#24 cells. Calculates the centre of the cell and the 6 vertices nearest it
r=(0..23).map{|i|a,b=(-1)**i,(-1)**(i/2)
c=[[a,b,0,0],[a,0,b,0],[a,0,0,b],[0,a,b,0],[0,a,0,b],[0,0,a,b]][i/4]
s=[]
24.times{|j|t=v[j]
s<<j if (c[0]-t[0])**2+(c[1]-t[1])**2+(c[2]-t[2])**2+(c[3]-t[3])**2<=2
}
s}
https://en.wikipedia.org/wiki/600-cell
http://mathworld.wolfram.com/600-Cell.html
https://en.wikipedia.org/wiki/120-cell
http://mathworld.wolfram.com/120-Cell.html
https://en.wikipedia.org/wiki/24-cell
http://mathworld.wolfram.com/24-Cell.html
Ví dụ về sử dụng và đầu ra
cell24 = polytope[[3,4,3]]
puts "vertices"
cell24[0].each{|i|p i}
puts "edges"
cell24[1].each{|i|p i}
puts "faces"
cell24[2].each{|i|p i}
puts "cells"
cell24[3].each{|i|p i}
vertices
[2, 0, 0, 0]
[0, 2, 0, 0]
[0, 0, 2, 0]
[0, 0, 0, 2]
[-2, 0, 0, 0]
[0, -2, 0, 0]
[0, 0, -2, 0]
[0, 0, 0, -2]
[1, 1, 1, 1]
[1, 1, 1, -1]
[1, 1, -1, 1]
[1, 1, -1, -1]
[1, -1, 1, 1]
[1, -1, 1, -1]
[1, -1, -1, 1]
[1, -1, -1, -1]
[-1, 1, 1, 1]
[-1, 1, 1, -1]
[-1, 1, -1, 1]
[-1, 1, -1, -1]
[-1, -1, 1, 1]
[-1, -1, 1, -1]
[-1, -1, -1, 1]
[-1, -1, -1, -1]
edges
[0, 8]
[0, 9]
[0, 10]
[0, 11]
[0, 12]
[0, 13]
[0, 14]
[0, 15]
[1, 8]
[1, 9]
[1, 10]
[1, 11]
[1, 16]
[1, 17]
[1, 18]
[1, 19]
[2, 8]
[2, 9]
[2, 12]
[2, 13]
[2, 16]
[2, 17]
[2, 20]
[2, 21]
[3, 8]
[3, 10]
[3, 12]
[3, 14]
[3, 16]
[3, 18]
[3, 20]
[3, 22]
[4, 16]
[4, 17]
[4, 18]
[4, 19]
[4, 20]
[4, 21]
[4, 22]
[4, 23]
[5, 12]
[5, 13]
[5, 14]
[5, 15]
[5, 20]
[5, 21]
[5, 22]
[5, 23]
[6, 10]
[6, 11]
[6, 14]
[6, 15]
[6, 18]
[6, 19]
[6, 22]
[6, 23]
[7, 9]
[7, 11]
[7, 13]
[7, 15]
[7, 17]
[7, 19]
[7, 21]
[7, 23]
[8, 9]
[8, 10]
[8, 12]
[8, 16]
[9, 11]
[9, 13]
[9, 17]
[10, 11]
[10, 14]
[10, 18]
[11, 15]
[11, 19]
[12, 13]
[12, 14]
[12, 20]
[13, 15]
[13, 21]
[14, 15]
[14, 22]
[15, 23]
[16, 17]
[16, 18]
[16, 20]
[17, 19]
[17, 21]
[18, 19]
[18, 22]
[19, 23]
[20, 21]
[20, 22]
[21, 23]
[22, 23]
faces
[0, 8, 9]
[0, 8, 10]
[0, 8, 12]
[0, 9, 11]
[0, 9, 13]
[0, 10, 11]
[0, 10, 14]
[0, 11, 15]
[0, 12, 13]
[0, 12, 14]
[0, 13, 15]
[0, 14, 15]
[1, 8, 9]
[1, 8, 10]
[1, 8, 16]
[1, 9, 11]
[1, 9, 17]
[1, 10, 11]
[1, 10, 18]
[1, 11, 19]
[1, 16, 17]
[1, 16, 18]
[1, 17, 19]
[1, 18, 19]
[2, 8, 9]
[2, 8, 12]
[2, 8, 16]
[2, 9, 13]
[2, 9, 17]
[2, 12, 13]
[2, 12, 20]
[2, 13, 21]
[2, 16, 17]
[2, 16, 20]
[2, 17, 21]
[2, 20, 21]
[3, 8, 10]
[3, 8, 12]
[3, 8, 16]
[3, 10, 14]
[3, 10, 18]
[3, 12, 14]
[3, 12, 20]
[3, 14, 22]
[3, 16, 18]
[3, 16, 20]
[3, 18, 22]
[3, 20, 22]
[4, 16, 17]
[4, 16, 18]
[4, 16, 20]
[4, 17, 19]
[4, 17, 21]
[4, 18, 19]
[4, 18, 22]
[4, 19, 23]
[4, 20, 21]
[4, 20, 22]
[4, 21, 23]
[4, 22, 23]
[5, 12, 13]
[5, 12, 14]
[5, 12, 20]
[5, 13, 15]
[5, 13, 21]
[5, 14, 15]
[5, 14, 22]
[5, 15, 23]
[5, 20, 21]
[5, 20, 22]
[5, 21, 23]
[5, 22, 23]
[6, 10, 11]
[6, 10, 14]
[6, 10, 18]
[6, 11, 15]
[6, 11, 19]
[6, 14, 15]
[6, 14, 22]
[6, 15, 23]
[6, 18, 19]
[6, 18, 22]
[6, 19, 23]
[6, 22, 23]
[7, 9, 11]
[7, 9, 13]
[7, 9, 17]
[7, 11, 15]
[7, 11, 19]
[7, 13, 15]
[7, 13, 21]
[7, 15, 23]
[7, 17, 19]
[7, 17, 21]
[7, 19, 23]
[7, 21, 23]
cells
[0, 1, 8, 9, 10, 11]
[1, 4, 16, 17, 18, 19]
[0, 5, 12, 13, 14, 15]
[4, 5, 20, 21, 22, 23]
[0, 2, 8, 9, 12, 13]
[2, 4, 16, 17, 20, 21]
[0, 6, 10, 11, 14, 15]
[4, 6, 18, 19, 22, 23]
[0, 3, 8, 10, 12, 14]
[3, 4, 16, 18, 20, 22]
[0, 7, 9, 11, 13, 15]
[4, 7, 17, 19, 21, 23]
[1, 2, 8, 9, 16, 17]
[2, 5, 12, 13, 20, 21]
[1, 6, 10, 11, 18, 19]
[5, 6, 14, 15, 22, 23]
[1, 3, 8, 10, 16, 18]
[3, 5, 12, 14, 20, 22]
[1, 7, 9, 11, 17, 19]
[5, 7, 13, 15, 21, 23]
[2, 3, 8, 12, 16, 20]
[3, 6, 10, 14, 18, 22]
[2, 7, 9, 13, 17, 21]
[6, 7, 11, 15, 19, 23]