Schläfli Convex Regular Polytope Interpreter


15

Contexte

Le symbole Schläfli est une notation de la forme {p, q, r, ...} qui définit les polytopes et les pavages réguliers.

Le symbole Schläfli est une description récursive, commençant par un polygone régulier à p côtés comme {p}. Par exemple, {3} est un triangle équilatéral, {4} est un carré et ainsi de suite.

Un polyèdre régulier qui a q faces polygonales régulières à p p autour de chaque sommet est représenté par {p, q}. Par exemple, le cube a 3 carrés autour de chaque sommet et est représenté par {4,3}.

Un polytope à 4 dimensions régulier, avec r {p, q} cellules polyédriques régulières autour de chaque bord est représenté par {p, q, r}. Par exemple, un tesseract, {4,3,3}, a 3 cubes, {4,3}, autour d'un bord.

En général, un polytope régulier {p, q, r, ..., y, z} a z {p, q, r, ..., y} facettes autour de chaque pic, où un pic est un sommet dans un polyèdre, une arête dans un polytope 4, une face dans un polytope 5, une cellule dans un polytope 6 et une face (n-3) dans un n-polytope.

Un polytope régulier a une figure de sommet régulière. La figure du sommet d'un polytope régulier {p, q, r, ... y, z} est {q, r, ... y, z}.

Les polytopes réguliers peuvent avoir des éléments polygonaux en étoile, comme le pentagramme, avec le symbole {5/2}, représentés par les sommets d'un pentagone mais connectés alternativement.

Le symbole Schläfli peut représenter un polyèdre convexe fini, une tessellation infinie d'espace euclidien ou une tessellation infinie d'espace hyperbolique, selon le défaut d'angle de la construction. Un défaut d'angle positif permet à la figure de sommet de se replier dans une dimension supérieure et de se replier sur elle-même en tant que polytope. Un défaut d'angle nul tesselle un espace de même dimension que les facettes. Un défaut d'angle négatif ne peut pas exister dans l'espace ordinaire, mais peut être construit dans l'espace hyperbolique.

Compétition

Votre objectif est de créer un programme qui, une fois passé un symbole Schläfli, renverra une description complète d'un polytope convexe. Ce n'est qu'un sous-ensemble des symboles Schläfli, mais c'est le plus simple, je pense que même sans les autres possibilités, ce sera une tâche très difficile, et les polytopes sont le point de départ des pavages. Les règles de cette question ont été conçues avec l'idée que ce résultat soit une API, et je n'ai pas pu localiser un tel programme sur Internet.

Votre programme doit accomplir toutes les tâches suivantes.

  • Le programme doit être capable de générer n'importe quel polytope convexe régulier de dimension finie. En 2 dimensions, cela comprend n-gons. En 3 dimensions, ce sont les solides platoniques, en 4 dimensions, cela comprend le tesseract, l'orthoplex et quelques autres)
  • Le programme doit soit (a) placer un point sur l'origine, soit (b) s'assurer que la moyenne de tous les points est l'origine. L'orientation n'a pas d'importance. La taille globale n'a pas d'importance.
  • Le programme doit fournir une description complète signifiant que pour un objet à 4 dimensions, le programme retournera / imprimera les sommets, les bords, les faces et les polyèdres. L'ordre dans lequel ils sont rapportés n'a pas d'importance. Pour les polyèdres, il s'agit des informations dont vous auriez besoin pour rendre l'objet.

Vous n'avez pas besoin de gérer:

  • Tesselations
  • Géométrie hyperbolique
  • Symboles Schläfli fractionnaires (non convexes)
  • Symboles Schläfli intégrés (pavages non uniformes)

Si vous êtes invité à effectuer l'une de ces opérations, vous pouvez renvoyer une erreur.

Exemple: Cube

Contribution:

4 3

Production:

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

J'ai eu quelques idées sur la façon dont cet algorithme pourrait fonctionner et être très récursif, mais jusqu'à présent, j'ai échoué, mais si vous cherchez de l'inspiration, consultez: https://en.wikipedia.org/wiki/Euler_characteristic

Comme exemple de calcul du nombre de sommets, d'arêtes et de faces, considérons le cube qui est {4,3}. Si nous regardons le 4 initial, alors il a 4 arêtes et 4 sommets. Maintenant, si nous regardons les 3 suivants, nous savons que 3 arêtes se rencontrent à chaque sommet, chaque arête se connecte à 2 sommets, 2 faces se rencontrent à chaque arête, chaque face se connecte à 4 arêtes (à cause des côtés carrés), et nous avons la formule caractéristique d'Euler.

E = 3/2 V

E = 4/2 F

V - E + F = 2

Ce qui donne E = 12, V = 8, F = 6.

Notation

Afin de garder la question sur le sujet, cela a été révisé en Code Golf. Le code le plus court gagne.

Un github a été créé pour cette question


1
La recherche sur Google montre qu'il n'y a que 3 familles de polytopes réguliers s'étendant au-delà de 4 dimensions: analogues au cube, à l'octaèdre et au tétraèdre. Il semble qu'il serait plus simple d'écrire pour ces familles et de coder en dur le reste (deux polytopes 3D, trois polytopes 4d et la famille infinie de polytopes 2D). Serait-ce une réponse valable? Il pourrait être possible d'écrire un algorithme récursif pour générer des graphiques topologiques au-delà de la portée de la spécification, mais le tueur avec cette approche même dans la spécification calcule les coordonnées.
Level River St

Comment connaître les sommets réels, en sachant seulement qu'ils sont équilatéraux?
Matthew Roh

@SIGSEGV la seule exigence spécifiée est que l'origine doit correspondre au centre ou à l'un des points. Cela donne beaucoup de latitude pour faire pivoter la forme à votre guise. en.wikipedia.org/wiki/Simplex donne un algorithme de calcul des coordonnées des hypertétraèdres (qui pourrait peut-être être étendu à l'icosaèdre et à son analogue 4d mais faire cela est trop pour moi, d'où ma question.) Les hypercubes et les hyperoctaèdres ont jolies coordonnées entières (et les hypertétraèdres aussi en fait, mais souvent seulement en plus de dimensions que la forme elle-même, ce qui est désordonné.)
Level River St

@LevelRiverSt, oui puisque les seuls polytopes réguliers qui existent seraient couverts dans vos suggestions, alors oui, vous pouvez les coder en dur.
Tony Ruth

J'ai émis le vote de clôture sur cette question, car il s'agit d'un défi de style le plus rapide dans l'Ouest , où la première réponse valide l'emporte. Ce n'est généralement pas considéré comme un critère de gain valable. Je ne sais pas comment cela a été ouvert depuis si longtemps, il aurait dû être fermé.
Post Rock Garf Hunter

Réponses:


2

Python

Voici un programme récursif sans cas particulier. En ignorant les lignes vides et les commentaires, c'est moins de 100 90 lignes, y compris une vérification gratuite de la formule d'Euler à la fin. Excluant les définitions des fonctions mathématiques ad hoc (qui pourraient probablement être fournies par une bibliothèque) et les E / S, la génération de polytopes est de 50 lignes de code. Et il fait même les polytopes étoiles!

Le polytope de sortie aura une longueur de bord 1 et sera en position et orientation canoniques, dans le sens suivant:

  • le premier sommet est l'origine,
  • le premier bord se situe le long de l'axe + x,
  • la première face est dans le demi-plan + y du plan xy,
  • la première cellule 3 est dans le demi-espace + z de l'espace xyz, etc.

À part cela, les listes de sortie ne sont pas dans un ordre particulier. (Eh bien, en fait, ce n'est pas tout à fait vrai - ils sortiront en gros dans l'ordre en commençant par le premier élément et en s'étendant vers l'extérieur.)

Il n'y a pas de vérification pour le symbole schlafli invalide; si vous lui en donnez un, le programme va probablement dérailler (boucle sans fin, débordement de pile, ou tout simplement gâcher).

Si vous demandez un pavage plan infini tel que {4,4} ou {3,6} ou {6,3}, le programme commencera réellement à générer le pavage, mais il restera éternel jusqu'à ce qu'il manque d'espace, jamais finition ni production de sortie. Ce ne serait pas trop difficile à résoudre (il suffit de limiter le nombre d'éléments à générer; le résultat devrait être une région assez cohérente de l'image infinie, car les éléments sont générés dans un ordre de recherche approximativement large).

Le code

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

L'essayer sur certains cas

Entrée ( cube ):

4 3

Production:

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

Entrée d'un shell de commande unix ( polychore 120 cellules ):

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

Production:

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

Entrée ( polytope croisé à 10 dimensions ):

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

Production:

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

Entrée ( simplexe à 15 dimensions ):

$ 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 étoilés

Ha, et il fait aussi naturellement des polytopes étoiles! Je n'ai même pas eu besoin d'essayer :-) Sauf que le bit sur la formule d'Euler à la fin échoue, car cette formule n'est pas valable pour les polytopes en étoile.

Entrée ( petit dodécaèdre étoilé ):

5/2 5

Production:

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

Entrée ( grand 120 cellules étoilées ):

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

Production:

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

Merci d'avoir relancé cette question, et votre réponse semble assez impressionnante. J'aime la nature récursive et les figures d'étoiles. J'ai connecté votre code à un opengl pour dessiner des polytopes (voir le lien github ci-dessus).
Tony Ruth

14

Rubis

Contexte

Il existe trois familles de polytopes réguliers s'étendant dans des dimensions infinies:

  • les simplexes, dont le tétraèdre est un membre (je les désignerai souvent ici sous le nom d'hypertétraèdres, bien que le terme simplex soit plus correct.) Leurs symboles schlafi sont de la forme {3,3,...,3,3}

  • les n-cubes, dont le cube est membre. Leurs symboles schlafi sont de la forme{4,3,...,3,3}

  • les orthoplexes, dont l'octaèdre est membre (je les désignerai souvent ici par hyperoctaèdres). Leurs symboles schlafi sont de la forme {3,3,...,3,4}

Il existe une autre famille infinie de polytopes réguliers, symbole {m}, celle des polygones bidimensionnels, qui peuvent avoir un nombre quelconque d'arêtes m.

En plus de cela, il n'y a que cinq autres cas spéciaux de polytope régulier: l'icosaèdre {3,5}et le dodécaèdre tridimensionnels {5,3}; leurs analogues à 4 dimensions les cellules 600 {3,3,5}et 120 cellules {5,3,3}; et un autre polytope à 4 dimensions, les 24 cellules {3,4,3}(dont les analogues les plus proches en 3 dimensions sont le cuboctaèdre et son double le dodécaèdre rhombique.)

Fonction principale

Ci-dessous se trouve la polytopefonction principale qui interprète le symbole schlafi. Il attend un tableau de nombres et renvoie un tableau contenant un groupe de tableaux comme suit:

  • Un tableau de tous les sommets, chacun étant exprimé sous la forme d'un tableau de coordonnées à n éléments (où n est le nombre de dimensions)

  • Un tableau de toutes les arêtes, chacune exprimée en 2 éléments d'indices de sommet

  • Un tableau de toutes les faces, chacune exprimée comme un m-élément d'indices de sommet (où m est le nombre de sommets par face)

et ainsi de suite selon le nombre de dimensions.

Il calcule lui-même les polytopes 2D, appelle des fonctions pour les 3 familles de dimensions infinies et utilise des tables de recherche pour les cinq cas spéciaux. Il s'attend à trouver les fonctions et les tables déclarées au-dessus.

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
}

Fonctions pour les familles tétraèdre, cube et octaèdre

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

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

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

Explication de la famille des tétraèdres - coordonnées

un simplex / hypertétraèdre à n dimensions a n + 1 points. Il est très facile de donner les sommets du simplexe à n dimensions en n + 1 dimensions.

(1,0,0),(0,1,0),(0,0,1)Décrit ainsi un triangle 2D intégré dans 3 dimensions et (1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1)décrit un tétraèdre 3D intégré dans 4 dimensions. Ceci est facilement vérifié en confirmant que toutes les distances entre les sommets sont sqrt (2).

Divers algorithmes compliqués sont fournis sur Internet pour trouver les sommets du simplexe à n dimensions dans l'espace à n dimensions. J'en ai trouvé une remarquablement simple dans les commentaires de Will Jagy sur cette réponse /mathpro//a/38725 . Le dernier point se trouve sur la ligne p=q=...=x=y=zà une distance de sqrt (2) des autres. Ainsi, le triangle ci-dessus peut être converti en tétraèdre en ajoutant un point à l'un (-1/3,-1/3,-1/3)ou à l'autre (1,1,1). Ces 2 valeurs possibles des coordonnées du dernier point sont données par (1-(1+n)**0.5)/net(1+(1+n)**0.5)/n

Comme la question dit que la taille du n-tope n'a pas d'importance, je préfère multiplier par n et utiliser les coordonnées (n,0,0..0)jusqu'au (0..0,0,n)point final (t,t,..,t,t)où t = 1-(1+n)**0.5pour plus de simplicité.

Comme le centre de ce tétraèdre n'est pas à l'origine, une correction de toutes les coordonnées doit être effectuée par la ligne s.map!{|j|j-((1-(1+n)**0.5)+n)/(1+n)}qui trouve à quelle distance le centre est de l'origine et le soustrait. J'ai gardé cela comme une opération distincte. Cependant, j'ai utilisé s[i]+=ns[i]=nferait, pour faire allusion au fait que lorsque le tableau est initialisé par s=[0]*nnous pourrions mettre le bon décalage ici à la place et faire la correction de centrage au début plutôt qu'à la fin.

Explication de la famille des tétraèdres - topologie des graphes

Le graphe du simplexe est le graphe complet: chaque sommet est connecté exactement une fois à chaque autre sommet. Si nous avons un n simplex, nous pouvons supprimer n'importe quel sommet pour donner un n-1 simplex, jusqu'au point où nous avons un triangle ou même une arête.

Nous avons donc un total de 2 ** (n + 1) éléments à cataloguer, chacun représenté par un nombre binaire. Cela va de tous les 0s pour le néant, à un 1pour un sommet et à deux 1s pour une arête, jusqu'à tous les 1s pour le polytope complet.

Nous avons mis en place un tableau de tableaux vides pour stocker les éléments de chaque taille. Ensuite, nous bouclons de zéro à (2 ** n + 1) pour générer chacun des sous-ensembles possibles de sommets et les stockons dans le tableau en fonction de la taille de chaque sous-ensemble.

Nous ne sommes pas intéressés par quelque chose de plus petit qu'un bord (un sommet ou un zéro) ni par le polytope complet (car le cube complet n'est pas donné dans l'exemple de la question), nous retournons donc tg[2..n]pour supprimer ces éléments indésirables. Avant de revenir, nous plaçons [tv] contenant les coordonnées du sommet sur le début.

code

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

explication des familles de cubes et d'octaèdres - coordonnées

Le n-cube a des 2**nsommets, chacun représenté par un tableau de n 1s et -1s (toutes les possibilités sont permises.) Nous itérer à travers des index 0à 2**n-1la liste de tous les sommets, et de construire un tableau pour chaque sommet en parcourant les bits du indexer et ajouter -1ou 1au tableau (du bit le moins significatif au bit le plus significatif.) Ainsi, le binaire 1101devient le point 4d [1,-1,1,1].

Le n-octaèdre ou n-orthoplexe a des 2nsommets, avec toutes les coordonnées nulles sauf une, qui peut être 1ou -1. L'ordre des sommets dans le tableau généré est [[1,0,0..],[-1,0,0..],[0,1,0..],[0,-1,0..],[0,0,1..],[0,0,-1..]...]. Notez que comme l'octaèdre est le dual du cube, les sommets de l'octaèdre sont définis par les centres des faces du cube qui l'entoure.

Explication des familles de cubes et d'octaèdres - topologie des graphes

Une certaine inspiration a été tirée des côtés de l' hypercube et du fait que l'hyperoctaèdre est le double de l'hypercube.

Pour le n-cube, il y a des 3**néléments à cataloguer. Par exemple, le cube 3 a 3**3= 27 éléments. Cela peut être vu en étudiant un cube de rubik, qui a 1 centre, 6 faces, 12 arêtes et 8 sommets pour un total de 27. Nous itérons par -1,0 et -1 dans toutes les dimensions définissant un n-cube de longueur latérale 2x2x2 .. et renvoie tous les sommets qui ne sont PAS du côté opposé du cube. Ainsi, le point central du cube renvoie tous les 2 ** n sommets, et l'éloignement d'une unité du centre le long de n'importe quel axe réduit le nombre de sommets de moitié.

Comme pour la famille des tétraèdres, nous commençons par générer un tableau vide de tableaux et le remplissons en fonction du nombre de sommets par élément. Notez que parce que le nombre de sommets varie comme 2 ** n lorsque nous montons à travers des arêtes, des faces, des cubes, etc., nous utilisons log2(s.size)+1au lieu de simplement s.size. Encore une fois, nous devons supprimer l'hypercube lui-même et tous les éléments avec moins de 2 sommets avant de revenir de la fonction.

La famille des octaèdres / orthoplexes est le duel de la famille des cubes, il y a donc encore des 3**narticles à cataloguer. Ici, nous parcourons -1,0,1toutes les dimensions et si la coordonnée non nulle d'un sommet est égale à la coordonnée correspondante du point, le sommet est ajouté à la liste correspondant à ce point. Ainsi, une arête correspond à un point à deux coordonnées non nulles, un triangle à un point à 3 coordonnées non nulles et un tétraèdre à un point à 4 contacts non nuls (dans l'espace 4d).

Les tableaux de sommets résultants pour chaque point sont stockés dans un grand tableau comme pour les autres cas, et nous devons supprimer tous les éléments avec moins de 2 sommets avant de revenir. Mais dans ce cas, nous n'avons pas à supprimer le parent complet n-tope car l'algorithme ne l'enregistre pas.

Les implémentations du code du cube ont été conçues pour être aussi similaires que possible. Bien que cela ait une certaine élégance, il est probable que des algorithmes plus efficaces basés sur les mêmes principes pourraient être conçus.

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

Code de génération de tableaux pour les cas spéciaux 3D

Une orientation avec l'icosaèdre / dodécaèdre orienté avec le quintuple axe de symétrie parallèle à la dernière dimension a été utilisée, car elle permettait l'étiquetage le plus cohérent des pièces. La numérotation des sommets et des faces pour l'icosaèdre est selon le diagramme dans les commentaires de code, et inversée pour le dodécaèdre.

Selon https://en.wikipedia.org/wiki/Regular_icosahedron, la latitude des 10 sommets non polaires de l'icosaèdre est de +/- arctan (1/2) Les coordonnées des 10 premiers sommets de l'icosaèdre sont calculées à partir de ceci, sur deux cercles de rayon 2 à distance +/- 2 du plan xy. Cela rend le rayon global de la circonscription sqrt (5) donc les 2 derniers sommets sont à (0,0, + / - sqrt (2)).

Les coordonnées des sommets du dodécaèdre sont calculées en additionnant les coordonnées des trois sommets de l'icosaèdre qui les entourent.

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

Code de génération des tableaux pour les cas spéciaux 4d

C'est un peu un hack. Ce code prend quelques secondes pour s'exécuter. Il serait préférable de stocker la sortie dans un fichier et de la charger au besoin.

La liste des 120 coordonnées de sommet pour la 600cell provient de http://mathworld.wolfram.com/600-Cell.html . Les 24 coordonnées de sommets qui ne présentent pas de nombre d'or forment les sommets d'un 24 cellules. Wikipedia a le même schéma mais a une erreur dans l'échelle relative de ces 24 coordonnées et des 96 autres.

#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

Exemple d'utilisation et de sortie

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
Wow c'est une réponse géniale !! Je suis très surpris que vous ayez pu le faire en ~ 200 lignes. J'ai couru le cube, le tétraèdre, 600 cellules et quelques autres, et ils avaient l'air bien. Il est difficile de vérifier la sortie car il y en a tellement; il est assez facile que la sortie soit plus longue que le programme, mais je vais vous croire sur parole. Je vais essayer de charger ceci dans openGL et voir les solides platoniques qui devraient être simples puisque toutes les faces sont répertoriées. Je pense que l'ajout de tesselations dans un espace plat serait facile, et je pourrais essayer aussi.
Tony Ruth

@TonyRuth, la clé était de trouver le meilleur algorithme. Moins de lignes = moins de marge d'erreur. La première chose que j'ai faite a été de vérifier ce qui existait en dehors des 3 familles de dimensions infinies et c'est là que j'ai décidé de répondre. Les commentaires de Will Jagy étaient une aubaine (je pensais à ce type de solution car la méthode de wikipedia semblait difficile), donc les coordonnées non entières sont réduites au minimum. Je voulais le faire avant l'expiration de la prime, donc la vérification n'a pas été massivement approfondie et je ne les ai pas complotés. Faites-moi part de toute erreur - j'ai corrigé la cellule 24 il y a quelques heures.
Level River St

Les sommets de visage @TonyRuth ne sont pas dans un ordre particulier (ils ne font pas le tour du visage dans le sens horaire ou quoi que ce soit). Pour les dimensions supérieures, il n'y a pas de commande standard. Les hypercubes ont des faces répertoriées dans l'ordre numérique, donc les 2e et 3e sommets sont diagonalement opposés (vous devrez permuter les 1er et 2e ou 3e et 4e sommets si vous les voulez dans le sens horaire / antihoraire.) Le dodécaèdre doit avoir des faces dans dans le sens horaire / antihoraire, mais la cellule 120 aura les sommets de face dans tous les ordres.
Level River St
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.