Intérprete regular de polítipos convexos da Schläfli


15

fundo

O símbolo Schläfli é uma notação da forma {p, q, r, ...} que define polítopos e pavimentações regulares.

O símbolo Schläfli é uma descrição recursiva, começando com um polígono regular com lados p como {p}. Por exemplo, {3} é um triângulo equilátero, {4} é um quadrado e assim por diante.

Um poliedro regular que possui q faces poligonais regulares ao lado de cada vértice é representado por {p, q}. Por exemplo, o cubo tem 3 quadrados ao redor de cada vértice e é representado por {4,3}.

Um polítopo quadridimensional regular, com células poliédricas regulares r {p, q} ao redor de cada borda, é representado por {p, q, r}. Por exemplo, um tesseract, {4,3,3}, tem 3 cubos {4,3}, em torno de uma aresta.

Em geral, um polítopo regular {p, q, r, ..., y, z} tem z {p, q, r, ..., y} facetas em torno de cada pico, em que um pico é um vértice em um poliedro, uma aresta em um polítopo 4, uma face em um polítopo 5, uma célula em um polítopo 6 e uma face (n-3) em um polítopo n.

Um politopo regular tem uma figura regular de vértice. A figura do vértice de um politopo regular {p, q, r, ... y, z} é {q, r, ... y, z}.

Os politopos regulares podem ter elementos poligonais em estrela, como o pentagrama, com o símbolo {5/2}, representado pelos vértices de um pentágono, mas conectados alternadamente.

O símbolo de Schläfli pode representar um poliedro convexo finito, um mosaico infinito de espaço euclidiano ou um mosaico infinito de espaço hiperbólico, dependendo do defeito angular da construção. Um defeito de ângulo positivo permite que a figura do vértice se dobre em uma dimensão mais alta e retorne a si mesma como um politopo. Um defeito no ângulo zero reduz o espaço da mesma dimensão que as facetas. Um defeito de ângulo negativo não pode existir no espaço comum, mas pode ser construído no espaço hiperbólico.

Concorrência

Seu objetivo é criar um programa que, quando passado um símbolo da Schläfli, retornará uma descrição completa de um polítopo convexo. Este é apenas um subconjunto dos Símbolos Schläfli, mas é o mais simples, acredito que mesmo sem as outras possibilidades, essa será uma tarefa muito difícil, e os politopos são o ponto de partida para os mosaicos. As regras desta pergunta foram criadas com a idéia de que esse resultado seja uma API e não consegui localizar nenhum programa na Internet.

Seu programa deve realizar todos os seguintes.

  • O programa deve ser capaz de gerar qualquer politopo convexo regular de dimensão finita. Em 2 dimensões, isso inclui n-gons. Em 3 dimensões, esses são os sólidos platônicos; em 4 dimensões, inclui o tesserato, o orthoplex e alguns outros)
  • O programa deve (a) colocar um ponto na origem ou (b) garantir que a média de todos os pontos seja a origem. Orientação não importa. O tamanho geral não importa.
  • O programa deve fornecer uma descrição completa, o que significa que, para um objeto quadridimensional, o programa retornará / imprimirá os vértices, arestas, faces e poliedros. A ordem em que são relatadas não importa. Para os poliedros, essas são as informações necessárias para renderizar o objeto.

Você não precisa lidar com:

  • Tesselações
  • Geometria hiperbólica
  • Símbolos fracionários de Schläfli (não convexos)
  • Símbolos Schläfli incorporados (inclinações não uniformes)

Se solicitado a fazer alguma dessas coisas, você pode retornar um erro.

Exemplo: Cubo

Entrada:

4 3

Resultado:

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

Eu tive algumas idéias de como esse algoritmo poderia funcionar e ser muito recursivo, mas até agora eu falhei, mas se você estiver procurando por inspiração, confira: https://en.wikipedia.org/wiki/Euler_characteristic

Como um exemplo de cálculo do número de vértices, arestas e faces, considere o cubo que é {4,3}. Se olharmos para o 4 inicial, ele tem 4 arestas e 4 vértices. Agora, se olharmos para os próximos 3, sabemos que 3 arestas se encontram em cada vértice, cada aresta se conecta a 2 vértices, 2 faces se encontram em cada aresta, cada face se conecta a 4 arestas (por causa dos lados quadrados), e temos a fórmula característica de Euler.

E = 3/2 V

E = 4/2 F

V - E + F = 2

O que dá E = 12, V = 8, F = 6.

Pontuação

Para manter a pergunta sobre o assunto, isso foi revisado para o Code Golf. O menor código vence.

Um github foi criado para esta pergunta


1
A pesquisa no Google mostra que existem apenas três famílias de pólipos regulares que se estendem além de quatro dimensões: análoga a cubo, octaedro e tetraedro. Parece que seria mais simples escrever para essas famílias e codificar o restante (dois polítopos 3d, três polópticos 4d e a família infinita de polítopos 2D). Tanto quanto posso ver, isso atende às especificações, mas não seria generalizável. Essa seria uma resposta válida? Pode ser possível escrever um algoritmo recursivo para gerar gráficos topológicos além do escopo da especificação, mas o assassino com essa abordagem, mesmo dentro da especificação, está calculando as coordenadas.
Level River St

Como sabemos os vértices reais, sabendo apenas que são equilaterais?
Matthew Roh

@SIGSEGV, o único requisito especificado é que a origem corresponda ao centro ou a um dos pontos. Isso dá muito espaço para girar a forma como você quiser. O en.wikipedia.org/wiki/Simplex fornece um algoritmo para o cálculo das coordenadas dos hipertetraedros (que talvez possam ser estendidos ao icosaedro e ao seu análogo 4d, mas fazer isso é demais para mim, daí a minha pergunta). coordenadas agradável inteiros (e os hypertetrahedrons muito na verdade, mas muitas vezes apenas em mais dimensões do que a própria forma, que é desarrumado.)
Nível do rio St

@LevelRiverSt, sim, uma vez que os únicos pólipos regulares existentes existiriam dentro de suas sugestões, então sim, você poderia codificá-las.
Tony Ruth

Fiz a votação final nesta questão porque é um desafio ao estilo das armas mais rápidas do oeste , onde a primeira resposta válida vence. Isso geralmente não é considerado um critério de vitória válido. Não sei como isso está aberto há tanto tempo, deveria ter sido fechado.
Post Rock Garf Hunter

Respostas:


2

Pitão

Aqui está um programa recursivo sem casos especiais. Ignorando linhas e comentários em branco, são menos de 100 90 linhas, incluindo uma verificação gratuita da fórmula de Euler no final. Excluindo as definições das funções matemáticas ad-hoc (que provavelmente poderiam ser fornecidas por uma biblioteca) e a E / S, a geração do politopo é de 50 linhas de código. E até os pólipos das estrelas!

O pólipo de saída terá o comprimento da aresta 1 e estará na posição e orientação canônicas, no seguinte sentido:

  • o primeiro vértice é a origem,
  • a primeira aresta fica ao longo do eixo + x,
  • a primeira face está no semiplano + y do plano xy,
  • a primeira célula 3 está no meio espaço + z do espaço xyz, etc.

Fora isso, as listas de saída não estão em uma ordem específica. (Bem, na verdade, isso não é inteiramente verdade - eles serão publicados aproximadamente desde o primeiro elemento e se expandindo para fora.)

Não há verificação de símbolo inválido de schlafli; se você der um, o programa provavelmente sairá dos trilhos (loop infinito, estouro de pilha ou apenas lixo).

Se você solicitar um mosaico planar infinito, como {4,4} ou {3,6} ou {6,3}, o programa realmente começará a gerar o mosaico, mas continuará para sempre até ficar sem espaço, nunca acabamento nem produção de saída. Isso não seria muito difícil de corrigir (basta colocar um limite no número de elementos a serem gerados; o resultado deve ser uma região bastante coerente da imagem infinita, pois os elementos são gerados aproximadamente na ordem da primeira pesquisa).

O código

#!/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)

Experimentando em alguns casos

Entrada ( cubo ):

4 3

Resultado:

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

Entrada de um shell de comando unix ( policoron de 120 células ):

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

Resultado:

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

Entrada ( pólipo cruzado de 10 dimensões ):

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

Resultado:

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):

Entrada ( simplex de 15 dimensões ):

$ 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):

Polytopes estrelas

Ha, e isso naturalmente também faz pólipos estrelas! Eu nem precisei tentar :-) Exceto que o pouco sobre a fórmula de Euler no final falha, pois essa fórmula não é válida para polítopos estelares.

Entrada ( pequeno dodecaedro estrelado ):

5/2 5

Resultado:

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

Entrada ( grande estrelada de 120 células ):

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

Resultado:

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

Obrigado por reviver esta pergunta, e sua resposta parece bastante impressionante. Gosto da natureza recursiva e das figuras estelares. Conectei seu código a algum opengl para desenhar polytopes (veja o link do github acima).
21418 Tony Ruth

14

Rubi

fundo

Existem três famílias de pólipos regulares que se estendem em dimensões infinitas:

  • os simplexos, dos quais o tetraedro é um membro (frequentemente, vou me referir a eles aqui como hipertetraedro, embora o termo simplex seja mais correto.) Seus símbolos schlafi têm a forma {3,3,...,3,3}

  • os n-cubos, dos quais o cubo é um membro. Seus símbolos schlafi são da forma{4,3,...,3,3}

  • os ortoplexos, dos quais o octaedro é um membro (muitas vezes vou me referir a eles aqui como hiperoctahedra). Seus símbolos schlafi são da forma {3,3,...,3,4}

Existe uma outra família infinita de pólipos regulares, símbolo {m}, a dos polígonos bidimensionais, que pode ter qualquer número de arestas m.

Além disso, existem apenas cinco outros casos especiais de politopo regular: o icosaedro tridimensional {3,5}e o dodecaedro {5,3}; seus análogos tridimensionais de 600 {3,3,5}e 120 células {5,3,3}; e um outro polítopo tridimensional, a célula de 24 {3,4,3}(cujos análogos mais próximos em três dimensões são o cuboctaedro e seu dodecaedro rômbico duplo).

Função principal

Abaixo está a principal polytopefunção que interpreta o símbolo schlafi. Ele espera uma matriz de números e retorna uma matriz contendo várias matrizes da seguinte maneira:

  • Uma matriz de todos os vértices, cada uma expressa como uma matriz de coordenadas com n elementos (onde n é o número de dimensões)

  • Uma matriz de todas as arestas, cada uma expressa como um elemento 2 de índices de vértices

  • Uma matriz de todas as faces, cada uma expressa como um elemento m de índices de vértices (em que m é o número de vértices por face)

e assim por diante, conforme apropriado para o número de dimensões.

Ele calcula os próprios polítopos 2D, chama funções para as três famílias dimensionais infinitas e usa tabelas de pesquisa para os cinco casos especiais. Ele espera encontrar as funções e tabelas declaradas acima dele.

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
}

Funções para as famílias de tetraedro, cubo e octaedro

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

https://en.wikipedia.org/wiki/5-cell (4d simplex)

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

Explicação da família tetraedro - coordenadas

um simplex / hipertetraedro n-dimensional tem n + 1 pontos. É muito fácil fornecer os vértices do simplex n-dimensional em n + 1 dimensões.

Assim, (1,0,0),(0,1,0),(0,0,1)descreve um triângulo 2D incorporado em 3 dimensões e (1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1)descreve um tetraedro 3d incorporado em 4 dimensões. Isso é facilmente verificado confirmando que todas as distâncias entre os vértices são sqrt (2).

Vários algoritmos complicados são dados na internet para encontrar os vértices para o simplex n-dimensional no espaço n-dimensional. Eu encontrei um notavelmente simples nos comentários de Will Jagy sobre esta resposta /mathpro//a/38725 . O último ponto está na linha p=q=...=x=y=za uma distância de sqrt (2) dos outros. Assim, o triângulo acima pode ser convertido em um tetraedro pela adição de um ponto em um (-1/3,-1/3,-1/3)ou em (1,1,1). Estes 2 valores possíveis das coordenadas para o último ponto são dados por (1-(1+n)**0.5)/ne(1+(1+n)**0.5)/n

Como a pergunta diz que o tamanho do n-tope não importa, prefiro multiplicar por n e usar as coordenadas (n,0,0..0)até (0..0,0,n)o ponto final em (t,t,..,t,t)que t = 1-(1+n)**0.5por simplicidade.

Como o centro deste tetraedro não está na origem, uma correção para todas as coordenadas deve ser feita pela linha s.map!{|j|j-((1-(1+n)**0.5)+n)/(1+n)}que descobre a que distância o centro está da origem e o subtrai. Eu mantive isso como uma operação separada. No entanto, usei s[i]+=nonde s[i]=nfaria, para aludir ao fato de que, quando a matriz é inicializada s=[0]*n, poderíamos colocar o deslocamento correto aqui e fazer a correção de centralização no início e não no final.

Explicação da família tetraedro - topologia gráfica

O gráfico do simplex é o gráfico completo: todos os vértices são conectados exatamente uma vez a todos os outros vértices. Se temos um n simplex, podemos remover qualquer vértice para dar um n-1 simplex, até o ponto em que temos um triângulo ou mesmo uma aresta.

Portanto, temos um total de 2 ** (n + 1) itens para catalogar, cada um representado por um número binário. Isso varia de todos os 0s para nada, até um 1para um vértice e dois 1s para uma aresta, até todos os 1s para o polítopo completo.

Montamos uma matriz de matrizes vazias para armazenar os elementos de cada tamanho. Em seguida, fazemos um loop de zero a (2 ** n + 1) para gerar cada um dos possíveis subconjuntos de vértices e os armazenamos na matriz de acordo com o tamanho de cada subconjunto.

Não estamos interessados ​​em nada menor que uma aresta (um vértice ou um zero) nem no polítopo completo (como o cubo completo não é dado no exemplo da pergunta), então voltamos tg[2..n]a remover esses elementos indesejados. Antes de retornar, aderimos [tv] contendo as coordenadas dos vértices no início.

código

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

explicação das famílias cubo e octaedro - coordenadas

O n-cubo tem 2**nvértices, cada um representado por um conjunto de n 1s e -1s (são permitidos todas as possibilidades.) Iteramos através de índices 0a 2**n-1da lista de todos os vértices, e construir uma matriz para cada vértice por iteração através dos bits do indexe e adicione -1ou 1à matriz (bit menos significativo para o bit mais significativo.) Assim, Binário 1101se torna o ponto 4d [1,-1,1,1].

O n-octaedro ou n-ortoplex possui 2nvértices, com todas as coordenadas zero, exceto uma, que é ser 1ou -1. A ordem dos vértices na matriz gerada é [[1,0,0..],[-1,0,0..],[0,1,0..],[0,-1,0..],[0,0,1..],[0,0,-1..]...]. Observe que, como o octaedro é o dual do cubo, os vértices do octaedro são definidos pelos centros das faces do cubo que o rodeia.

explicação das famílias cubo e octaedro - topologia gráfica

Alguma inspiração foi tirada dos lados do Hipercubo e o fato de o hiperoctaedro ser o dual do hipercubo.

Para o cubo n, existem 3**nitens para catalogar. Por exemplo, o cubo 3 possui 3**3= 27 elementos. Isso pode ser observado no estudo de um cubo de rubik, que possui 1 centro, 6 faces, 12 arestas e 8 vértices, para um total de 27. Nós iteramos através de -1,0 e -1 em todas as dimensões que definem um n-cubo de comprimento lateral 2x2x2 .. e retorne todos os vértices que NÃO estão no lado oposto do cubo. Assim, o ponto central do cubo retorna todos os 2 ** n vértices, e afastar uma unidade do centro ao longo de qualquer eixo reduz o número de vértices pela metade.

Como na família dos tetraedros, começamos gerando uma matriz vazia de matrizes e a preenchemos de acordo com o número de vértices por elemento. Observe que, como o número de vértices varia em 2 ** n à medida que avançamos pelas arestas, faces, cubos, etc., usamos em log2(s.size)+1vez de simplesmente s.size. Novamente, temos que remover o próprio hipercubo e todos os elementos com menos de 2 vértices antes de retornar da função.

A família octaedro / orthoplex é o dual da família de cubos; portanto, novamente existem 3**nitens para catalogar. Aqui, percorreremos -1,0,1todas as dimensões e, se a coordenada diferente de zero de um vértice for igual à coordenada correspondente do ponto, o vértice será adicionado à lista correspondente a esse ponto. Assim, uma aresta corresponde a um ponto com duas coordenadas diferentes de zero, um triângulo a um ponto com três coordenadas diferentes de zero e um tetraedro a um ponto com 4 contatos diferentes de zero (no espaço 4d).

As matrizes resultantes de vértice para cada ponto são armazenadas em uma matriz grande, como nos outros casos, e precisamos remover quaisquer elementos com menos de 2 vértices antes de retornar. Mas, neste caso, não precisamos remover o pai completo n-tope porque o algoritmo não o grava.

As implementações do código para o cubo foram projetadas para serem as mais semelhantes possíveis. Embora isso tenha certa elegância, é provável que algoritmos mais eficientes baseados nos mesmos princípios possam ser criados.

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

Código para gerar tabelas para casos especiais 3d

Utilizou-se uma orientação com o icosaedro / dodecaedro, com o eixo de simetria quíntuplo paralelo à última dimensão, para garantir a etiquetagem mais consistente das peças. A numeração de vértices e faces do icosaedro é de acordo com o diagrama nos comentários do código e invertida para o dodecaedro.

De acordo com https://en.wikipedia.org/wiki/Regular_icosahedron, a latitude dos 10 vértices não polares do icosaedro é +/- arctan (1/2) As coordenadas dos 10 primeiros vértices do icosaedro são calculadas a partir de isto, em dois círculos de raio 2 a distância +/- 2 do plano xy. Isso faz com que o raio geral da circunsfera sqrt (5) faça com que os 2 últimos vértices estejam em (0,0, + / - sqrt (2)).

As coordenadas dos vértices do dodecaedro são calculadas somando as coordenadas dos três vértices do icosaedro que os cercam.

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

Código para gerar as tabelas para os casos especiais 4d

Isso é meio que um hack. Esse código leva alguns segundos para ser executado. Seria melhor armazenar a saída em um arquivo e carregá-la conforme necessário.

A lista de 120 coordenadas de vértice para a célula 600 é de http://mathworld.wolfram.com/600-Cell.html . As 24 coordenadas do vértice que não apresentam uma proporção áurea formam os vértices de uma célula de 24. A Wikipedia tem o mesmo esquema, mas há um erro na escala relativa dessas 24 coordenadas e das outras 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://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

Exemplo de uso e saída

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
Uau, isso é uma resposta incrível! Estou muito surpreso que você foi capaz de fazer isso em ~ 200 linhas. Corri o cubo, o tetraedro, as células de 600 e alguns outros, e eles pareciam bons. É difícil verificar a saída, pois existe muito; é muito fácil que a saída seja mais longa que o programa, mas aceitarei sua palavra. Vou tentar carregar isso no openGL e visualizar os sólidos platônicos que devem ser diretos, pois todas as faces estão listadas. Eu acho que adicionar tesselações em espaço plano seria fácil, e eu poderia tentar isso também.
Tony Ruth

@ TonyRuth a chave foi encontrar o melhor algoritmo. Menos linhas = menos espaço para erro. A primeira coisa que fiz foi verificar o que existia além das três famílias dimensionais infinitas e foi então que decidi responder. Os comentários de Will Jagy foram uma dádiva de Deus (eu estava pensando nesse tipo de solução, pois o método da wikipedia parecia difícil), de modo que as coordenadas não inteiras são reduzidas ao mínimo. Eu queria fazê-lo antes que a recompensa expirasse, para que a verificação não tenha sido maciçamente minuciosa e não os plotei. Avise-me de algum erro - eu corrigi a célula 24 algumas horas atrás.
Level River St

Os vértices de face do @TonyRuth não estão em uma ordem específica (eles não circulam no sentido horário ou algo assim). Para dimensões mais altas, não há pedido padrão. Os hipercubos têm faces listadas em ordem numérica; portanto, o segundo e o terceiro vértices são diagonalmente opostos (você precisará trocar o primeiro e o segundo ou o terceiro e o quarto vértices, se desejar em sentido horário / anti-horário.) O dodecaedro deve ter faces em no sentido horário / anti-horário, mas a célula de 120 terá os vértices de face em todo e qualquer pedido.
Level River St
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.