Schläfli Convex Regular Polytope Intérprete


15

Antecedentes

El símbolo Schläfli es una notación de la forma {p, q, r, ...} que define politopos y teselaciones regulares.

El símbolo Schläfli es una descripción recursiva, que comienza con un polígono regular p-sided como {p}. Por ejemplo, {3} es un triángulo equilátero, {4} es un cuadrado y así sucesivamente.

{P, q} representa un poliedro regular que tiene q caras de polígono con lados p regulares alrededor de cada vértice. Por ejemplo, el cubo tiene 3 cuadrados alrededor de cada vértice y está representado por {4,3}.

Un politopo regular de 4 dimensiones, con r {p, q} celdas poliédricas regulares alrededor de cada borde está representado por {p, q, r}. Por ejemplo, un tesseract, {4,3,3}, tiene 3 cubos, {4,3}, alrededor de un borde.

En general, un politopo regular {p, q, r, ..., y, z} tiene facetas z {p, q, r, ..., y} alrededor de cada pico, donde un pico es un vértice en un poliedro, un borde en un 4-politopo, una cara en un 5-politopo, una celda en un 6-politopo y una cara (n-3) en un n-politopo.

Un politopo regular tiene una figura de vértice regular. La figura del vértice de un politopo regular {p, q, r, ... y, z} es {q, r, ... y, z}.

Los politopos regulares pueden tener elementos poligonales en estrella, como el pentagrama, con el símbolo {5/2}, representado por los vértices de un pentágono pero conectados alternativamente.

El símbolo Schläfli puede representar un poliedro convexo finito, una teselación infinita del espacio euclidiano o una teselación infinita del espacio hiperbólico, dependiendo del defecto angular de la construcción. Un defecto de ángulo positivo permite que la figura del vértice se pliegue en una dimensión más alta y se repita en sí misma como un politopo. Un defecto de ángulo cero tesela el espacio de la misma dimensión que las facetas. Un defecto de ángulo negativo no puede existir en el espacio ordinario, pero puede construirse en espacio hiperbólico.

Competencia

Su objetivo es crear un programa que al pasar un símbolo Schläfli devolverá una descripción completa de un politopo convexo. Este es solo un subconjunto de los Símbolos Schläfli, pero es el más simple, creo que incluso sin las otras posibilidades, esta será una tarea muy difícil, y los politopos son el punto de partida para las teselaciones. Las reglas de esta pregunta se diseñaron con la idea de que este resultado sea una API, y no he podido localizar ningún programa de este tipo en Internet.

Su programa debe cumplir con todo lo siguiente.

  • El programa debe ser capaz de generar cualquier politopo convexo regular de dimensiones finitas. En 2 dimensiones, esto incluye n-gons. En 3 dimensiones, estos son los sólidos platónicos, en 4 dimensiones esto incluye el tesseract, el orthoplex y algunos otros)
  • El programa debe (a) colocar un punto en el origen o (b) asegurarse de que el promedio de todos los puntos sea el origen. La orientación no importa. El tamaño general no importa.
  • El programa debe proporcionar una descripción completa, lo que significa que para un objeto de 4 dimensiones, el programa devolverá / imprimirá los vértices, bordes, caras y poliedros. El orden en que se informan no importa. Para los poliedros, esta es la información que necesitaría para representar el objeto.

Usted no necesita mango:

  • Tesselaciones
  • Geometría hiperbólica
  • Símbolos Schläfli fraccionales (no convexos)
  • Símbolos Schläfli integrados (inclinaciones no uniformes)

Si se le pide que haga alguna de estas cosas, puede devolver un error.

Ejemplo: Cubo

Entrada:

4 3

Salida:

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

Tenía algunas ideas sobre cómo este algoritmo podría funcionar y ser muy recursivo, pero hasta ahora he fallado, pero si está buscando inspiración, consulte: https://en.wikipedia.org/wiki/Euler_characteristic

Como ejemplo de cálculo del número de vértices, aristas y caras, considere el cubo que es {4,3}. Si miramos los 4 iniciales, entonces tiene 4 aristas y 4 vértices. Ahora, si miramos los siguientes 3, sabemos que 3 bordes se encuentran en cada vértice, cada borde se conecta a 2 vértices, 2 caras se encuentran en cada borde, cada cara se conecta a 4 bordes (debido a los lados cuadrados), y tenemos La fórmula característica de Euler.

E = 3/2 V

E = 4/2 F

V - E + F = 2

Lo que da E = 12, V = 8, F = 6.

Puntuación

Para mantener la pregunta sobre el tema, esto se ha revisado a Code Golf. El código más corto gana.

Se ha creado un github para esta pregunta.


1
Google muestra que solo hay 3 familias de politopes regulares que se extienden más allá de 4 dimensiones: análoga a cubo, octaedro y tetraedro. Parece que sería más simple escribir para estas familias y codificar el resto (dos politopos 3d, tres politopos 4d y la familia infinita de politopos 2D). Hasta donde puedo ver, eso cumple con las especificaciones pero no sería generalizable. ¿Sería una respuesta válida? Puede ser factible escribir un algoritmo recursivo para generar gráficos topológicos más allá del alcance de la especificación, pero el asesino con ese enfoque incluso dentro de la especificación está calculando las coordenadas.
Level River St

¿Cómo sabemos los vértices reales, solo sabiendo que son equiláteros?
Matthew Roh

@SIGSEGV, el único requisito especificado es que el origen debe corresponder al centro o a uno de los puntos. Eso le da un amplio margen para rotar la forma como desee. en.wikipedia.org/wiki/Simplex ofrece un algoritmo para calcular las coordenadas de los hipertetraedros (que tal vez podría extenderse al icosaedro y su análogo 4d, pero hacerlo es demasiado para mí, de ahí mi pregunta). Los hipercubos e hiperoctaedros tienen coordenadas enteras agradables (y los hipertetraedros también en realidad, pero a menudo solo en más dimensiones que la forma en sí, que es desordenada)
Nivel River St

@LevelRiverSt, sí, ya que los únicos politopos regulares que existen estarían cubiertos en sus sugerencias, entonces sí, podría codificarlos.
Tony Ruth

He emitido el voto final sobre esta pregunta porque es un desafío al estilo de las armas más rápidas en el oeste , donde gana la primera respuesta válida. Esto generalmente no se considera un criterio ganador válido. No sé cómo ha estado abierto durante tanto tiempo, debería haberse cerrado.
Post Rock Garf Hunter

Respuestas:


2

Pitón

Aquí hay un programa recursivo sin ningún caso especial. Ignorando las líneas en blanco y los comentarios, son menos de 100 90 líneas, incluida una verificación gratuita de la fórmula de Euler al final. Excluyendo las definiciones de funciones matemáticas ad-hoc (que probablemente podrían ser proporcionadas por una biblioteca) y la E / S, la generación de politopos es de 50 líneas de código. ¡Y hasta hace los politopos estrella!

El politopo de salida tendrá una longitud de borde 1 y estará en posición y orientación canónicas, en el siguiente sentido:

  • el primer vértice es el origen
  • el primer borde se encuentra a lo largo del eje + x,
  • la primera cara está en el semiplano + y del plano xy,
  • la primera de 3 celdas está en el medio espacio + z del espacio xyz, etc.

Aparte de eso, las listas de salida no están en un orden particular. (Bueno, en realidad, eso no es del todo cierto, en realidad saldrán más o menos en orden comenzando desde el primer elemento y expandiéndose hacia afuera).

No hay comprobación de símbolos schlafli no válidos; si le das uno, el programa probablemente se saldrá de los rieles (bucle sin fin, desbordamiento de pila o simplemente basura).

Si solicita un mosaico plano infinito como {4,4} o {3,6} o {6,3}, el programa realmente comenzará a generar el mosaico, pero durará para siempre hasta que se quede sin espacio, nunca acabado ni producción de salida. Esto no sería demasiado difícil de solucionar (solo ponga un límite en el número de elementos a generar; el resultado debería ser una región bastante coherente de la imagen infinita, ya que los elementos se generan en un orden de búsqueda más o menos amplio).

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

Probándolo en algunos casos

Entrada ( cubo ):

4 3

Salida:

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 desde un shell de comandos de Unix ( policlorón de 120 celdas ):

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

Salida:

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

Entrada ( politopo cruzado de 10 dimensiones ):

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

Salida:

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

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

Politopos estrella

Ja, y naturalmente también hace politopos estrella! Ni siquiera tuve que intentar :-) Excepto que falla un poco sobre la fórmula de Euler al final, ya que esa fórmula no es válida para los politopos de estrella.

Entrada ( pequeño dodecaedro estrellado ):

5/2 5

Salida:

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 ( gran 120 celdas estrelladas ):

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

Salida:

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

Gracias por revivir esta pregunta, y su respuesta se ve bastante impresionante. Me gusta la naturaleza recursiva y las figuras de estrellas. Conecté su código a algún opengl para dibujar politopos (vea el enlace de github arriba).
Tony Ruth

14

Rubí

Antecedentes

Hay tres familias de politopo regular que se extienden a dimensiones infinitas:

  • los símplex, de los cuales el tetraedro es miembro (a menudo me referiré a ellos aquí como hipertetraedros, aunque el término simplex es más correcto). Sus símbolos schlafi son de la forma {3,3,...,3,3}

  • los n-cubos, de los cuales el cubo es miembro. Sus símbolos schlafi son de la forma{4,3,...,3,3}

  • los ortoplexes, de los cuales es miembro el octaedro (a menudo me referiré a ellos aquí como hiperoctaedros) Sus símbolos schlafi son de la forma {3,3,...,3,4}

Hay otra familia infinita de politopes regulares, símbolo {m}, el de los polígonos bidimensionales, que pueden tener cualquier número de aristas m.

Además de esto, solo hay otros cinco casos especiales de politopo regular: el icosaedro tridimensional {3,5}y el dodecaedro {5,3}; sus análogos de 4 dimensiones, de 600 celdas {3,3,5}y 120 celdas {5,3,3}; y otro politopo de 4 dimensiones, el de 24 celdas {3,4,3}(cuyos análogos más cercanos en 3 dimensiones son el cuboctaedro y su doble el dodecaedro rómbico).

Función principal

A continuación se muestra la polytopefunción principal que interpreta el símbolo schlafi. Espera una matriz de números y devuelve una matriz que contiene un montón de matrices de la siguiente manera:

  • Una matriz de todos los vértices, cada uno expresado como una matriz de coordenadas de n elementos (donde n es el número de dimensiones)

  • Una matriz de todos los bordes, cada uno expresado como un índice de vértices de 2 elementos

  • Una matriz de todas las caras, cada una expresada como un elemento m de índices de vértices (donde m es el número de vértices por cara)

y así sucesivamente según el número de dimensiones.

Calcula los politopos 2D en sí, llama a funciones para las 3 familias de dimensiones infinitas y utiliza tablas de búsqueda para los cinco casos especiales. Espera encontrar las funciones y tablas declaradas arriba.

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
}

Funciones para las familias de tetraedro, cubo y octaedro

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

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

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

Explicación de la familia del tetraedro - coordenadas

un simplex / hipertetraedro n-dimensional tiene n + 1 puntos. Es muy fácil dar los vértices del simplex n-dimensional en n + 1 dimensiones.

Por lo tanto, (1,0,0),(0,1,0),(0,0,1)describe un triángulo 2D incrustado en 3 dimensiones y (1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1)describe un tetraedro 3d incrustado en 4 dimensiones. Esto se verifica fácilmente al confirmar que todas las distancias entre vértices son sqrt (2).

Se proporcionan varios algoritmos complicados en Internet para encontrar los vértices para el símplex n-dimensional en el espacio n-dimensional. Encontré una notablemente simple en los comentarios de Will Jagy sobre esta respuesta /mathpro//a/38725 . El último punto se encuentra en la línea p=q=...=x=y=za una distancia de sqrt (2) de los demás. Así, el triángulo de arriba se puede convertir en un tetraedro mediante la adición de un punto en (-1/3,-1/3,-1/3)o (1,1,1). Estos 2 valores posibles de las coordenadas para el último punto están dados por (1-(1+n)**0.5)/ny(1+(1+n)**0.5)/n

Como la pregunta dice que el tamaño del n-tope no importa, prefiero multiplicarlo por n y usar las coordenadas (n,0,0..0)hasta (0..0,0,n)con el punto final en (t,t,..,t,t)donde t = 1-(1+n)**0.5por simplicidad.

Como el centro de este tetraedro no está en el origen, la línea debe hacer una corrección a todas las coordenadas s.map!{|j|j-((1-(1+n)**0.5)+n)/(1+n)}que encuentra qué tan lejos está el centro del origen y lo resta. He mantenido esto como una operación separada. Sin embargo, he utilizado s[i]+=nwhere s[i]=nwould would, para aludir al hecho de que cuando la matriz se inicializa s=[0]*n, podríamos colocar el desplazamiento correcto aquí y hacer la corrección de centrado desde el principio en lugar del final.

Explicación de la familia del tetraedro - topología gráfica

El gráfico del simplex es el gráfico completo: cada vértice está conectado exactamente una vez a todos los demás vértices. Si tenemos un n simplex, podemos eliminar cualquier vértice para dar un n-1 simplex, hasta el punto donde tenemos un triángulo o incluso un borde.

Por lo tanto, tenemos un total de 2 ** (n + 1) artículos para catalogar, cada uno representado por un número binario. Esto va desde todos los 0s para nada, pasando por uno 1para un vértice y dos 1s para un borde, hasta todos los 1s para el politopo completo.

Configuramos una matriz de matrices vacías para almacenar los elementos de cada tamaño. Luego hacemos un bucle de cero a (2 ** n + 1) para generar cada uno de los posibles subconjuntos de vértices y almacenarlos en la matriz de acuerdo con el tamaño de cada subconjunto.

No estamos interesados ​​en nada más pequeño que un borde (un vértice o un cero) ni en el politopo completo (ya que el cubo completo no se da en el ejemplo de la pregunta), por lo que volvemos tg[2..n]a eliminar estos elementos no deseados. Antes de regresar, añadimos [tv] que contiene las coordenadas del vértice al comienzo.

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

explicación de familias de cubo y octaedro - coordenadas

El n-cubo tiene 2**nvértices, cada uno representado por una matriz de n 1s y -1s (se permiten todas las posibilidades.) Nos iterar a través de los índices 0a 2**n-1de la lista de todos los vértices, y construir una matriz para cada vértice por iteración a través de los bits de la indexar y agregar -1o 1al conjunto (bit menos significativo a bit más significativo). De este modo, Binary se 1101convierte en el punto 4d [1,-1,1,1].

El n-octaedro o n-orthoplex tiene 2nvértices, con todas las coordenadas cero, excepto una, que puede ser 1o -1. El orden de los vértices en la matriz generada es [[1,0,0..],[-1,0,0..],[0,1,0..],[0,-1,0..],[0,0,1..],[0,0,-1..]...]. Tenga en cuenta que como el octaedro es el doble del cubo, los vértices del octaedro están definidos por los centros de las caras del cubo que lo rodea.

explicación de las familias de cubos y octaedros - topología gráfica

Se tomó algo de inspiración de los lados del hipercubo y del hecho de que el hiperoctaedro es el doble del hipercubo.

Para el n-cubo, hay 3**nartículos para catalogar. Por ejemplo, el cubo 3 tiene 3**3= 27 elementos. Esto se puede ver estudiando un cubo de rubik, que tiene 1 centro, 6 caras, 12 aristas y 8 vértices para un total de 27. iteramos a través de -1,0 y -1 en todas las dimensiones que definen un n-cubo de longitud lateral 2x2x2 .. y devuelve todos los vértices que NO están en el lado opuesto del cubo. Por lo tanto, el punto central del cubo devuelve todos los vértices de 2 ** n, y alejarse una unidad del centro a lo largo de cualquier eje reduce el número de vértices a la mitad.

Al igual que con la familia del tetraedro, comenzamos generando una matriz vacía de matrices y la llenamos de acuerdo con el número de vértices por elemento. Tenga en cuenta que debido a que el número de vértices varía como 2 ** n a medida que avanzamos a través de bordes, caras, cubos, etc., usamos en log2(s.size)+1lugar de simplemente s.size. Nuevamente, tenemos que eliminar el hipercubo en sí y todos los elementos con menos de 2 vértices antes de regresar de la función.

La familia octahedron / orthoplex son los duales de la familia de cubos, por lo tanto, nuevamente hay 3**nartículos para catalogar. Aquí iteramos -1,0,1para todas las dimensiones y si la coordenada distinta de cero de un vértice es igual a la coordenada correspondiente del punto, el vértice se agrega a la lista correspondiente a ese punto. Por lo tanto, un borde corresponde a un punto con dos coordenadas distintas de cero, un triángulo a un punto con 3 coordenadas distintas de cero y un tetraedro a un punto con 4 contactos distintos de cero (en el espacio 4d).

Las matrices de vértices resultantes para cada punto se almacenan en una matriz grande como en los otros casos, y tenemos que eliminar cualquier elemento con menos de 2 vértices antes de regresar. Pero en este caso no tenemos que eliminar el n-tope padre completo porque el algoritmo no lo registra.

Las implementaciones del código para el cubo fueron diseñadas para ser lo más similares posible. Si bien esto tiene una cierta elegancia, es probable que se puedan idear algoritmos más eficientes basados ​​en los mismos principios.

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 generar tablas para los casos especiales 3d

Se utilizó una orientación con el icosaedro / dodecaedro orientado con el eje de simetría quíntuple paralelo a la última dimensión, ya que contribuyó al etiquetado más consistente de las partes. La numeración de vértices y caras para el icosaedro se realiza según el diagrama en los comentarios del código, y se invierte para el dodecaedro.

De acuerdo con https://en.wikipedia.org/wiki/Regular_icosahedron, la latitud de los 10 vértices no polares del icosaedro es +/- arctan (1/2) Las coordenadas de los primeros 10 vértices del icosaedro se calculan a partir de esto, en dos círculos de radio 2 a una distancia +/- 2 del plano xy. Esto hace que el radio general de la circunsfera sea sqrt (5), por lo que los últimos 2 vértices están en (0,0, + / - sqrt (2)).

Las coordenadas de los vértices del dodecaedro se calculan sumando las coordenadas de los tres vértices de icosaedro que los rodean.

=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 generar las tablas para los casos especiales 4d

Esto es un poco hack. Este código tarda unos segundos en ejecutarse. Sería mejor almacenar el resultado en un archivo y cargarlo según sea necesario.

La lista de 120 coordenadas de vértice para la celda 600 es de http://mathworld.wolfram.com/600-Cell.html . Las 24 coordenadas de vértice que no presentan una proporción áurea forman los vértices de una celda de 24. Wikipedia tiene el mismo esquema pero tiene un error en la escala relativa de estas 24 coordenadas y las otras 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

Ejemplo de uso y salida

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
¡Guau, esta es una respuesta increíble! Estoy muy sorprendido de que hayas podido hacer esto en ~ 200 líneas. Corrí el cubo, tetraedro, 600 celdas y algunos otros, y se veían bien. Es difícil verificar la salida ya que hay mucho de ella; es bastante fácil que la salida sea más larga que el programa, pero aceptaré tu palabra. Voy a intentar cargar esto en openGL y ver los sólidos platónicos que deberían ser sencillos ya que todas las caras están en la lista. Creo que agregar teselaciones en el espacio plano sería fácil, y podría intentarlo también.
Tony Ruth

@TonyRuth la clave fue encontrar el mejor algoritmo. Menos líneas = menos margen de error. Lo primero que hice fue verificar lo que existía además de las 3 familias de dimensiones infinitas y fue entonces cuando decidí responder. Los comentarios de Will Jagy fueron un regalo del cielo (estaba pensando en ese tipo de solución ya que el método de Wikipedia parecía difícil) por lo que las coordenadas no enteras se mantienen al mínimo. Quería hacerlo antes de que expirara la recompensa, por lo que la verificación no ha sido exhaustiva y no los he planeado. Avíseme de cualquier error: corrigí la celda 24 hace unas horas.
Level River St

Los vértices de la cara @TonyRuth no están en un orden particular (no giran alrededor de la cara en sentido horario ni nada). Para dimensiones superiores, no hay un pedido estándar. Los hipercubos tienen caras enumeradas en orden numérico, por lo que los vértices segundo y tercero son diagonalmente opuestos (necesitará intercambiar el primer y segundo o tercer y cuarto vértice si los quiere en sentido horario / antihorario). El dodecaedro debe tener caras en en sentido horario / antihorario, pero la celda 120 tendrá los vértices de la cara en todos y cada uno de los órdenes.
Level River St
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.