SchläfliConvex常规多面体解释器


15

背景

所述施莱夫利符号的形式为{P,Q,R,...}限定规则多面体和镶嵌的表示法。

Schläfli符号是递归描述,以p边的常规多边形为{p}开始。例如,{3}是等边三角形,{4}是正方形,依此类推。

{p,q}表示在每个顶点周围具有q个规则的p侧多边形面的规则多面体。例如,立方体在每个顶点周围有3个正方形,并用{4,3}表示。

{p,q,r}表示一个规则的4维多面体,每个边缘周围有r个{p,q}个规则多面体单元。例如,tesseract {4,3,3}在边缘周围有3个立方体,{4,3}。

通常,规则多面体{p,q,r,...,y,z}在每个峰周围都有z {p,q,r,...,y}个小面,其中一个峰是多面体中的一个顶点, 4多面体中的边,5多面体中的面,6多面体中的单元格和n多面体中的(n-3)面。

规则的多边形具有规则的顶点图形。正则多边形{p,q,r,... y,z}的顶点图为{q,r,... y,z}。

规则多面体可以具有五角星形的星形多边形元素,其符号为{5/2},由五边形的顶点表示,但交替连接。

根据构造的角度缺陷,Schläfli符号可以表示有限凸多面体,欧氏空间的无限细分或双曲空间的无限细分。正角缺陷使顶点图形可以折叠成更高的维度,并以多面体的形式循环回到自身。零角度缺陷会细分与小平面相同尺寸的空间。负角缺陷不能在普通空间中存在,而可以在双曲空间中构造。

竞争

您的目标是创建一个程序,该程序在通过SchläfliSymbol时将返回凸多面体的完整描述。这只是Schläfli符号的一个子集,但它是最简单的符号,我相信即使没有其他可能性,这也将是一项非常困难的任务,而多面体是镶嵌的起点。设计该问题的规则时要以将结果作为API的想法进行设计,而我无法在Internet上找到任何此类程序。

您的程序必须完成以下所有操作。

  • 该程序必须能够生成任何有限尺寸的规则凸多面体。在2维中,这包括n个角。在3维中是柏拉图式固体,在4维中包括tesseract,orthoplex和其他一些实体)
  • 程序必须(a)在原点上放置一个点,或(b)确保所有点的平均值是原点。方向无关紧要。整体大小无关紧要。
  • 程序必须提供完整的描述,这意味着对于4维对象,程序将返回/打印顶点,边,面和多面体。这些报告的顺序无关紧要。对于多面体,这是渲染对象所需的信息。

不会需要处理:

  • 镶嵌
  • 双曲几何
  • 分数Schläfli符号(非凸)
  • 嵌入式Schläfli符号(非均匀平铺)

如果要求您执行任何上述操作,则可能返回错误。

示例:多维数据集

输入:

4 3

输出:

Vertices
0 0 0
0 0 1
0 1 0
0 1 1
1 0 0
1 0 1
1 1 0
1 1 1    

Edges (These are the vertex pairs that make up the edges)
0 1
0 2
0 4
1 3
1 5
2 3
2 6
3 7
4 5
4 6
5 7
6 7

Faces (These are the squares which are the faces of the cube)
0 1 3 2
0 1 5 4
0 2 6 4
6 7 5 4
7 6 2 3
7 5 1 3

我对这种算法如何工作并且非常递归有一些想法,但是到目前为止,我失败了,但是如果您正在寻找灵感,请查看:https : //en.wikipedia.org/wiki/Euler_characteristic

作为计算顶点,边和面的数量的示例,请考虑为{4,3}的立方体。如果我们看一下初始的4,那么它有4个边和4个顶点。现在,如果我们看下一个3,我们知道每个顶点有3个边相交,每个边连接2个顶点,每个边有2个面相交,每个面都连接4个边(因为有正方形边),所以欧拉特征公式。

E = 3/2伏

E = 4/2 F

V-E + F = 2

得到E = 12,V = 8,F = 6。

计分

为了使问题保持​​在主题上,已将其修订为Code Golf。最短的代码获胜。

为此问题创建了一个github


1
Googling显示只有3个以上的规则多义族家族延伸到4个维度之外:类似于立方体,八面体和四面体。似乎为这些家族编写代码并对其进行硬编码(两个3d多边形,三个4d多边形以及无限的2d多边形系列)会更简单。据我所知,这符合规范,但不能推广。那是一个有效的答案吗?编写一种递归算法来生成超出规范范围的拓扑图可能是可行的,但是即使在规范内,使用这种方法的杀手也正在计算坐标。
Level River St

仅知道顶点是等边的,我们如何知道它们呢?
马修·罗

@SIGSEGV唯一指定的要求是原点应对应于中心或点之一。这就提供了足够的范围来根据需要旋转形状。en.wikipedia.org/wiki/Simplex提供了一种计算超四面体坐标的算法(也许可以扩展到二十面体及其4d类似物,但对我而言这样做实在是太多了,因此是我的问题。)超立方体和超八面体具有不错的整数坐标(实际上也包括超四面体,但是通常仅比形状本身具有更大的尺寸,这是不整洁的。)
Level River St'St

@LevelRiverSt,是的,因为您的建议中将涵盖仅存在的常规多聚体,然后您可以对它们进行硬编码。
Tony Ruth

我对这个问题投了最后一票,因为这是西方最快的枪支式挑战,第一个有效答案将获胜。通常不认为这是有效的获胜标准。我不知道它已经打开这么久了,它应该已经关闭了。
发布Rock Garf Hunter '18

Answers:


2

蟒蛇

这是一个没有特殊情况的递归程序。忽略空白行和注释,它少于100 90行,其中包括最后对欧拉公式的免费检查。不包括即席数学函数(可能由库提供)和I / O的定义,多面体生成是50行代码。甚至还有明星多角形!

输出多面体将具有边长1,并且将处于规范位置和方向,如下所示:

  • 第一个顶点是原点,
  • 第一条边沿+ x轴,
  • 第一个面在xy平面的+ y半平面中,
  • 第一个3单元格位于xyz空间的+ z半空间等。

除此之外,输出列表没有特定的顺序。(实际上,这并不完全是正确的-它们实际上会从第一个元素开始并向外扩展以大致顺序出现。)

没有检查无效的schlafli符号。如果给它一个,该程序可能会脱离轨道(无限循环,堆栈溢出或只是浪费掉)。

如果您要求进行无限的平面平铺,例如{4,4}或{3,6}或{6,3},则该程序实际上将开始生成平铺,但是它将一直持续到其空间耗尽为止,永远不会完成或不产生输出。这不会太难解决(只需限制要生成的元素数量;结果应该是无限图片的相当连贯的区域,因为元素是按大致广度优先搜索顺序生成的)。

代码

#!/usr/bin/python3
# (works with python2 or python3)

#
# schlafli_interpreter.py
# Author: Don Hatch
# For: /codegolf/114280/schl%C3%A4fli-convex-regular-polytope-interpreter
#
# Print the vertex coords and per-element (edges, faces, etc.) vertex index
# lists of a regular polytope, given by its schlafli symbol {p,q,r,...}.
# The output polytope will have edge length 1 and will be in canonical position
# and orientation, in the following sense:
#  - the first vertex is the origin,
#  - the first edge lies along the +x axis,
#  - the first face is in the +y half-plane of the xy plane,
#  - the first 3-cell is in the +z half-space of the xyz space, etc.
# Other than that, the output lists are in no particular order.
#

import sys
from math import *

# vector minus vector.
def vmv(a,b): return [x-y for x,y in zip(a,b)]
# matrix minus matrix.
def mmm(m0,m1): return [vmv(row0,row1) for row0,row1 in zip(m0,m1)]
# scalar times vector.
def sxv(s,v): return [s*x for x in v]
# scalar times matrix.
def sxm(s,m): return [sxv(s,row) for row in m]
# vector dot product.
def dot(a,b): return sum(x*y for x,y in zip(a,b))
# matrix outer product of two vectors; that is, if a,b are column vectors: a*b^T
def outer(a,b): return [sxv(x,b) for x in a]
# vector length squared.
def length2(v): return dot(v,v)
# distance between two vectors, squared.
def dist2(a,b): return length2(vmv(a,b))
# matrix times vector, homogeneous (i.e. input vector ends with an implicit 1).
def mxvhomo(m,v): return [dot(row,v+[1]) for row in m]
# Pad a square matrix (rotation/reflection) with an extra column of 0's on the
# right (translation).
def makehomo(m): return [row+[0] for row in m]
# Expand dimensionality of homogeneous transform matrix by 1.
def expandhomo(m): return ([row[:-1]+[0,row[-1]] for row in m]
                         + [[0]*len(m)+[1,0]])
# identity matrix
def identity(dim): return [[(1 if i==j else 0) for j in range(dim)]
                                               for i in range(dim)]
# https://en.wikipedia.org/wiki/Householder_transformation. v must be unit.
# Not homogeneous (makehomo the result if you want that).
def householderReflection(v): return mmm(identity(len(v)), sxm(2, outer(v,v)))

def sinAndCosHalfDihedralAngle(schlafli):
  # note, cos(pi/q)**2 generally has a nicer expression with no trig and often
  # no radicals, see http://www.maths.manchester.ac.uk/~cds/articles/trig.pdf
  ss = 0
  for q in schlafli: ss = cos(pi/q)**2 / (1 - ss)
  if abs(1-ss) < 1e-9: ss = 1  # prevent glitch in planar tiling cases
  return sqrt(ss), sqrt(1 - ss)

# Calculate a set of generators of the symmetry group of a {p,q,r,...} with
# edge length 1.
# Each generator is a dim x (dim+1) matrix where the square part is the initial
# orthogonal rotation/reflection and the final column is the final translation.
def calcSymmetryGenerators(schlafli):
  dim = len(schlafli) + 1
  if dim == 1: return [[[-1,1]]]  # one generator: reflect about x=.5
  facetGenerators = calcSymmetryGenerators(schlafli[:-1])
  # Start with facet generators, expanding each homogeneous matrix to full
  # dimensionality (i.e. from its previous size dim-1 x dim to dim x dim+1).
  generators = [expandhomo(gen) for gen in facetGenerators]
  # Final generator will reflect the first facet across the hyperplane
  # spanned by the first ridge and the entire polytope's center,
  # taking the first facet to a second facet also containing that ridge.
  # v = unit vector normal to that bisecting hyperplane
  #   = [0,...,0,-sin(dihedralAngle/2),cos(dihedralAngle/2)]
  s,c = sinAndCosHalfDihedralAngle(schlafli)
  v = [0]*(dim-2) + [-s,c]
  generators.append(makehomo(householderReflection(v)))
  return generators

# Key for comparing coords with roundoff error.  Makes sure the formatted
# numbers are not very close to 0, to avoid them coming out as "-0" or "1e-16".
# This isn't reliable in general, but it suffices for this application
# (except for very large {p}, no doubt).
def vert2key(vert): return ' '.join(['%.9g'%(x+.123) for x in vert])

# Returns a pair verts,edgesEtc where edgesEtc is [edges,faces,...]
def regular_polytope(schlafli):
  dim = len(schlafli) + 1
  if dim == 1: return [[0],[1]],[]

  gens = calcSymmetryGenerators(schlafli)

  facetVerts,facetEdgesEtc = regular_polytope(schlafli[:-1])

  # First get all the verts, and make a multiplication table.
  # Start with the verts of the first facet (padded to full dimensionality),
  # so indices will match up.
  verts = [facetVert+[0] for facetVert in facetVerts]
  vert2index = dict([[vert2key(vert),i] for i,vert in enumerate(verts)])
  multiplicationTable = []
  iVert = 0
  while iVert < len(verts):  # while verts is growing
    multiplicationTable.append([None] * len(gens))
    for iGen in range(len(gens)):
      newVert = mxvhomo(gens[iGen], verts[iVert])
      newVertKey = vert2key(newVert)
      if newVertKey not in vert2index:
        vert2index[newVertKey] = len(verts)
        verts.append(newVert)
      multiplicationTable[iVert][iGen] = vert2index[newVertKey]
    iVert += 1

  # The higher-level elements of each dimension are found by transforming
  # the facet's elements of that dimension.  Start by augmenting facetEdgesEtc
  # by adding one more list representing the entire facet.
  facetEdgesEtc.append([tuple(range(len(facetVerts)))])
  edgesEtc = []
  for facetElementsOfSomeDimension in facetEdgesEtc:
    elts = facetElementsOfSomeDimension[:]
    elt2index = dict([[elt,i] for i,elt in enumerate(elts)])
    iElt = 0
    while iElt < len(elts):  # while elts is growing
      for iGen in range(len(gens)):
        newElt = tuple(sorted([multiplicationTable[iVert][iGen]
                               for iVert in elts[iElt]]))
        if newElt not in elt2index:
          elt2index[newElt] = len(elts)
          elts.append(newElt)
      iElt += 1
    edgesEtc.append(elts)

  return verts,edgesEtc

# So input numbers can be like any of "8", "2.5", "7/3"
def parseNumberOrFraction(s):
  tokens = s.split('/')
  return float(tokens[0])/float(tokens[1]) if len(tokens)==2 else float(s)

if sys.stdin.isatty():
  sys.stderr.write("Enter schlafli symbol (space-separated numbers or fractions): ")
  sys.stderr.flush()
schlafli = [parseNumberOrFraction(token) for token in sys.stdin.readline().split()]
verts,edgesEtc = regular_polytope(schlafli)

# Hacky polishing of any integers or half-integers give or take rounding error.
def fudge(x): return round(2*x)/2 if abs(2*x-round(2*x))<1e-9 else x

print(repr(len(verts))+' Vertices:')
for v in verts: print(' '.join([repr(fudge(x)) for x in v]))
for eltDim in range(1,len(edgesEtc)+1):
  print("")
  elts = edgesEtc[eltDim-1]
  print(repr(len(elts))+' '+('Edges' if eltDim==1
                        else 'Faces' if eltDim==2
                        else repr(eltDim)+'-cells')+" ("+repr(len(elts[0]))+" vertices each):")
  for elt in elts: print(' '.join([repr(i) for i in elt]))

# Assert the generalization of Euler's formula: N0-N1+N2-... = 1+(-1)**(dim-1).
N = [len(elts) for elts in [verts]+edgesEtc]
eulerCharacteristic = sum((-1)**i * N[i] for i in range(len(N)))
print("Euler characteristic: "+repr(eulerCharacteristic))
if 2.5 not in schlafli: assert eulerCharacteristic == 1 + (-1)**len(schlafli)

在某些情况下尝试

输入(多维数据集):

4 3

输出:

8 Vertices:
0.0 0.0 0.0
1.0 0.0 0.0
0.0 1.0 0.0
1.0 1.0 0.0
0.0 0.0 1.0
1.0 0.0 1.0
0.0 1.0 1.0
1.0 1.0 1.0

12 Edges (2 vertices each):
0 1
0 2
1 3
2 3
0 4
1 5
4 5
2 6
4 6
3 7
5 7
6 7

6 Faces (4 vertices each):
0 1 2 3
0 1 4 5
0 2 4 6
1 3 5 7
2 3 6 7
4 5 6 7

来自unix命令外壳(120单元polychoron)的输入:

$ echo "5 3 3" | ./schlafli_interpreter.py | grep ":"

输出:

600 Vertices:
1200 Edges (2 vertices each):
720 Faces (5 vertices each):
120 3-cells (20 vertices each):

输入(10维交叉多边形):

$ echo "3 3 3 3 3 3 3 3 4" | ./schlafli_interpreter.py | grep ":"

输出:

20 Vertices:
180 Edges (2 vertices each):
960 Faces (3 vertices each):
3360 3-cells (4 vertices each):
8064 4-cells (5 vertices each):
13440 5-cells (6 vertices each):
15360 6-cells (7 vertices each):
11520 7-cells (8 vertices each):
5120 8-cells (9 vertices each):
1024 9-cells (10 vertices each):

输入(15维单形):

$ echo "3 3 3 3 3 3 3 3 3 3 3 3 3 3" | ./schlafli_interpreter.py | grep ":"

16 Vertices:
120 Edges (2 vertices each):
560 Faces (3 vertices each):
1820 3-cells (4 vertices each):
4368 4-cells (5 vertices each):
8008 5-cells (6 vertices each):
11440 6-cells (7 vertices each):
12870 7-cells (8 vertices each):
11440 8-cells (9 vertices each):
8008 9-cells (10 vertices each):
4368 10-cells (11 vertices each):
1820 11-cells (12 vertices each):
560 12-cells (13 vertices each):
120 13-cells (14 vertices each):
16 14-cells (15 vertices each):

星多角形

哈,它也很自然地也有星形多聚体!我什至不需要尝试:-)除非最后关于Euler公式的内容失败了,因为该公式不适用于星形多面体。

输入(小型星状十二面体):

5/2 5

输出:

12 Vertices:
0.0 0.0 0.0
1.0 0.0 0.0
0.8090169943749473 0.5877852522924732 0.0
0.19098300562505266 0.5877852522924732 0.0
0.5 -0.36327126400268034 0.0
0.8090169943749473 -0.2628655560595667 0.5257311121191336
0.19098300562505266 -0.2628655560595667 0.5257311121191336
0.5 0.162459848116453 -0.3249196962329062
0.5 0.6881909602355867 0.5257311121191336
0.0 0.32491969623290623 0.5257311121191336
0.5 0.1624598481164533 0.8506508083520398
1.0 0.32491969623290623 0.5257311121191336

30 Edges (2 vertices each):
0 1
0 2
1 3
2 4
3 4
0 5
1 6
5 7
6 7
0 8
2 9
7 8
7 9
1 8
0 10
3 11
5 9
4 10
7 11
4 9
2 5
1 10
4 11
6 11
6 8
3 10
3 6
2 10
9 11
5 8

12 Faces (5 vertices each):
0 1 2 3 4
0 1 5 6 7
0 2 7 8 9
1 3 7 8 11
0 4 5 9 10
2 4 5 7 11
1 4 6 10 11
0 3 6 8 10
3 4 6 7 9
2 3 9 10 11
1 2 5 8 10
5 6 8 9 11
Traceback (most recent call last):
  File "./schlafli_interpreter.py", line 185, in <module>
    assert sum((-1)**i * N[i] for i in range(len(N))) == 1 + (-1)**len(schlafli)
AssertionError

输入(星状120单元):

$ echo "5/2 3 5" | ./schlafli_interpreter.py | grep ":"

输出:

120 Vertices:
720 Edges (2 vertices each):
720 Faces (5 vertices each):
120 3-cells (20 vertices each):

感谢您恢复该问题,您的回答看起来非常令人印象深刻。我喜欢递归的性质和明星人物。我将您的代码连接到一些opengl来绘制多面体(请参见上面的github链接)。
托尼·露丝

14

红宝石

背景

规则多态性分为无限大的三个家族:

  • 单面体,四面体是其中的一员(尽管术语“单面体”更正确,但在这里我经常将它们称为hypertetrahedra。)它们的schlafi符号具有以下形式: {3,3,...,3,3}

  • n个多维数据集,其中多维数据集是成员。他们的schlafi符号具有以下形式{4,3,...,3,3}

  • 八面体为其成员的正交体(我在这里经常将它们称为超八面体)。其schlafi符号的形式为 {3,3,...,3,4}

规则{m}多边形的另一个无限家族,symbol ,是二维多边形的多边形,可以具有任意数量的边m。

除此之外,还有其他五种特殊的规则多义位例:3维二十面体{3,5}和十二面体{5,3};他们的4维类似物600细胞{3,3,5}和120细胞{5,3,3};另一个是4维多面体,即24细胞{3,4,3}(在3维中最接近的类似物是立方八面体,以及其双重菱形十二面体。)

主功能

以下是polytope解释schlafi符号的主要功能。它期望一个数字数组,并返回一个包含一堆数组的数组,如下所示:

  • 所有顶点的数组,每个顶点均表示为n个元素的坐标数组(其中n是维数)

  • 所有边的数组,每个边表示为顶点索引的2个元素

  • 所有面的数组,每个面表示为顶点索引的m个元素(其中m是每个面的顶点数)

依尺寸数量而定。

它自己计算2d多边形,为3个无限维族调用函数,并为5种特殊情况使用查找表。它期望找到上面声明的函数和表。

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
}

四面体,立方和八面体族的功能

https://zh.wikipedia.org/wiki/Simplex

https://zh.wikipedia.org/wiki/5-cell(4d单工)

http://mathworld.wolfram.com/Simplex.html

四面体家族解释-坐标

n维单形/超四面体具有n + 1个点。在n + 1维中给出n维单形的顶点非常容易。

因此,(1,0,0),(0,1,0),(0,0,1)描述了嵌入3维的2d三角形,并(1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1)描述了嵌入4维的3d四面体。确认顶点之间的所有距离均为sqrt(2)即可轻松验证这一点。

互联网上给出了各种复杂的算法,用于找到n维空间中n维单纯形的顶点。我在Will Jagy对这个答案的评论/mathpro//a/38725中找到了一个非常简单的例子。最后一点位于直线上p=q=...=x=y=z,彼此相距sqrt(2)。因此,可以通过在(-1/3,-1/3,-1/3)或处加上一个点,将上述三角形转换为四面体(1,1,1)。最后一个点的这两个可能的坐标值由(1-(1+n)**0.5)/n和给出(1+(1+n)**0.5)/n

正如该问题所说的,n顶点的大小无关紧要,为了简化起见,我更喜欢乘以n并使用(n,0,0..0)直到t = (0..0,0,n)的最终点的坐标。(t,t,..,t,t)1-(1+n)**0.5

由于此四面体的中心不在原点,因此必须通过s.map!{|j|j-((1-(1+n)**0.5)+n)/(1+n)}找到中心距原点有多远并减去的线对所有坐标进行校正。我将其保留为单独的操作。但是,我使用了s[i]+=nwhere s[i]=n可以做的事情,以暗示这样一个事实,即当数组初始化时,s=[0]*n我们可以在此处放置正确的偏移量,并在开始时而不是在末尾进行居中校正。

四面体系列说明-图拓扑

单纯形的图是完整的图:每个顶点与每个其他顶点精确连接一次。如果我们有n个单纯形,则可以删除任何顶点以给出n-1个单纯形,直到具有三角形甚至边缘的点。

因此,我们总共有2 **(n + 1)个要分类的商品,每个商品都用一个二进制数字表示。范围从0无为所有的s,到1顶点1为一个,边缘的两个,再到1完整多面体的所有s。

我们设置了一个空数组来存储每种大小的元素。然后,我们从零循环到(2 ** n + 1)以生成每个可能的顶点子集,并根据每个子集的大小将它们存储在数组中。

我们对小于边(顶点或零)的东西或对完整的多面体(因为问题的示例中未给出完整的立方体)不感兴趣,因此我们返回tg[2..n]删除这些不需要的元素。返回之前,我们将包含顶点坐标的[tv]粘贴到起点。

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]}

立方和八面体族的解释-坐标

在n立方体具有2**n顶点,每一个由n的阵列表示1S和-1S(所有的可能性是允许的。)我们迭代通过索引02**n-1所有顶点的列表,并通过的比特通过迭代建立为每个顶点的阵列索引并向数组中添加-11(从最低有效位到最高有效位。)因此,二进制1101成为4d点[1,-1,1,1]

n八面体或n正交体具有2n顶点,所有坐标均为零(一个除外),一个为be 1-1。生成的数组中顶点的顺序为[[1,0,0..],[-1,0,0..],[0,1,0..],[0,-1,0..],[0,0,1..],[0,0,-1..]...]。请注意,由于八面体是立方体的对偶,因此八面体的顶点由围绕它的立方体的面的中心定义。

立方体和八面体族的解释-图拓扑

超立方体的侧面以及超八面体是超立方体的对偶这一事实获得了一些启发。

对于n多维数据集,有一些3**n要分类的项目。例如,3个多维数据集具有3**3= 27个元素。这可以通过研究一个具有1个中心,6个面,12个边缘和8个顶点(共27个)的魔方来看到。我们在各个维度上迭代-1,0和-1,定义了一个边长为2x2x2的n立方体。 ..并返回不在立方体另一侧的所有顶点。因此,立方体的中心点将返回所有2 ** n个顶点,并且沿任意轴从中心移开一个单位会使顶点数量减少一半。

与四面体族一样,我们首先生成一个空数组,然后根据每个元素的顶点数填充它。请注意,由于在通过边,面,立方体等时,顶点的数量变化为2 ** n,因此我们使用log2(s.size)+1而不是s.size。同样,在从函数返回之前,我们必须删除超立方体本身以及所有顶点少于2个的元素。

八面体/正交体家族是立方体家族的对偶体,因此再次有3**n要分类的项目。在这里,我们遍历-1,0,1所有维度,如果顶点的非零坐标等于该点的相应坐标,则将该顶点添加到与该点相对应的列表中。因此,边线对应于具有两个非零坐标的点,三角形对应于具有3个非零坐标的点,四面体对应于具有4个非零接触的点(在4d空间中)。

与其他情况一样,每个点生成的顶点数组都存储在一个大数组中,在返回之前,我们必须删除任何顶点少于2个的元素。但是在这种情况下,我们不必删除完整的父n-tope,因为该算法不会将其记录下来。

多维数据集的代码实现被设计为尽可能相似。尽管这具有一定的优雅性,但很可能可以设计出基于相同原理的更有效的算法。

https://zh.wikipedia.org/wiki/超立方体

http://mathworld.wolfram.com/Hypercube.html

https://zh.wikipedia.org/wiki/跨多边形

http://mathworld.wolfram.com/CrossPolytope.html

用于为3D特殊情况生成表格的代码

使用二十面体/十二面体的取向,该对称面的对称轴平行于最后一个尺寸,是为了使零件的标签最一致。二十面体的顶点和面的编号是根据代码注释中的图进行的,而十二面体的编号则相反。

根据https://en.wikipedia.org/wiki/Regular_icosahedron,二十面体的10个非极性顶点的纬度为+/- arctan(1/2)二十面体的前10个顶点的坐标是从这是在距xy平面+/- 2的半径2的两个圆上。这使得外球面的总半径为sqrt(5),因此最后两个顶点为(0,0,+ /-sqrt(2))。

十二面体顶点的坐标是通过将围绕它们的三个二十面体顶点的坐标相加得出的。

=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]]

生成4d特殊情况表的代码

这有点hack。此代码需要几秒钟才能运行。最好将输出存储在文件中,然后根据需要将其加载。

600单元的120个顶点坐标列表来自http://mathworld.wolfram.com/600-Cell.html。不具有黄金分割比例的24个顶点坐标形成24单元的顶点。维基百科具有相同的方案,但是在这24个坐标和其他96个坐标的相对比例上有错误。

#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://zh.wikipedia.org/wiki/600-cell

http://mathworld.wolfram.com/600-Cell.html

https://zh.wikipedia.org/wiki/120单元

http://mathworld.wolfram.com/120-Cell.html

https://zh.wikipedia.org/wiki/24-cell

http://mathworld.wolfram.com/24-Cell.html

使用和输出示例

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]

1
哇,这是一个了不起的答案!我很惊讶您能够在大约200行中完成此操作。我运行了立方体,四面体,600单元电池和其他一些立方体,它们看起来不错。由于输出太多,因此很难验证。输出比程序长很容易,但是我会同意的。我将尝试将其加载到openGL中并查看柏拉图式固体,因为列出了所有面孔,因此它们应该很简单。我认为在平面空间中添加曲面细分很容易,我也可以尝试。
Tony Ruth

@TonyRuth的关键是找到最佳算法。更少的行=更少的错误空间。我做的第一件事是检查3个无穷维族之外还存在什么,那是我决定回答的时候。威尔·杰吉(Will Jagy)的评论真是天赐之物(我在考虑那种解决方案,因为维基百科的方法看起来很难),因此将非整数坐标保持在最低水平。我想在赏金到期之前完成这项工作,因此检查尚未彻底完成,因此我没有作图。让我知道任何错误-几个小时前我更正了24cell。
Level River St

@TonyRuth的面顶点没有特定的顺序(它们不会以顺时针方向或其他任何方式围绕面旋转)。对于更大的尺寸,没有标准订单。超立方体具有按数字顺序列出的面,因此第二个和第三个顶点是对角线相反的(如果要按顺时针/逆时针方向交换它们,则需要交换第一个和第二个或第三个和第四个顶点。)十二面体的面应为顺时针/逆时针顺序,但120cell将以任何和所有顺序具有面顶点。
水平河圣
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.