Interprete Polytope normale convesso di Schläfli


15

sfondo

Il simbolo Schläfli è una notazione della forma {p, q, r, ...} che definisce polipropilene e tessellazioni regolari.

Il simbolo Schläfli è una descrizione ricorsiva, che inizia con un poligono regolare p-side come {p}. Ad esempio, {3} è un triangolo equilatero, {4} è un quadrato e così via.

Un poliedro regolare che ha q facce poligonali regolari p attorno a ciascun vertice è rappresentato da {p, q}. Ad esempio, il cubo ha 3 quadrati attorno a ciascun vertice ed è rappresentato da {4,3}.

Un polytope regolare a 4 dimensioni, con r {p, q} celle poliedriche regolari attorno a ciascun bordo è rappresentato da {p, q, r}. Ad esempio un tesseract, {4,3,3}, ha 3 cubi, {4,3}, attorno a un bordo.

In generale un polytope regolare {p, q, r, ..., y, z} ha z {p, q, r, ..., y} sfaccettature attorno ad ogni picco, dove un picco è un vertice in un poliedro, un bordo in un 4-politipo, una faccia in un 5-politipo, una cella in un 6-politipo e una faccia (n-3) in un n-politopo.

Un politopo normale ha una figura di vertice regolare. La figura del vertice di un politipo normale {p, q, r, ... y, z} è {q, r, ... y, z}.

I polipetti regolari possono avere elementi poligonali a stella, come il pentagramma, con il simbolo {5/2}, rappresentato dai vertici di un pentagono ma collegati alternativamente.

Il simbolo Schläfli può rappresentare un poliedro convesso finito, un'infinita tassellatura dello spazio euclideo o un'infinita tassellatura dello spazio iperbolico, a seconda del difetto angolare della costruzione. Un difetto dell'angolo positivo consente alla figura del vertice di piegarsi in una dimensione superiore e ricorre in se stessa come un politopo. Un difetto ad angolo zero tessera lo spazio della stessa dimensione delle sfaccettature. Un difetto dell'angolo negativo non può esistere nello spazio ordinario, ma può essere costruito nello spazio iperbolico.

concorrenza

Il tuo obiettivo è creare un programma che, una volta passato un simbolo Schläfli, restituisca una descrizione completa di un politopo convesso. Questo è solo un sottoinsieme dei Simboli Schläfli, ma è il più semplice, credo che anche senza le altre possibilità questo sarà un compito molto difficile, e i polipetti sono il punto di partenza per le tessellazioni. Le regole di questa domanda sono state progettate con l'idea che questo risultato sia un'API e non sono stato in grado di individuare alcun programma di questo tipo su Internet.

Il programma deve eseguire tutte le seguenti operazioni.

  • Il programma deve essere in grado di generare qualsiasi politopo convesso regolare di dimensioni finite. In 2 dimensioni questo include n-gon. In 3 dimensioni questi sono i solidi platonici, in 4 dimensioni questo include il tesseract, l'ortopice e pochi altri)
  • Il programma deve (a) posizionare un punto sull'origine o (b) assicurarsi che la media di tutti i punti sia l'origine. L'orientamento non ha importanza. Le dimensioni complessive non contano.
  • Il programma deve fornire una descrizione completa, nel senso che per un oggetto a 4 dimensioni, il programma restituirà / stamperà vertici, bordi, facce e poliedri. L'ordine in cui vengono riportati non ha importanza. Per i poliedri, queste sono le informazioni necessarie per rendere l'oggetto.

Non è necessario gestire:

  • Tesselations
  • Geometria iperbolica
  • Simboli frazionari di Schläfli (non convessi)
  • Simboli Schläfli incorporati (tetti non uniformi)

Se ti viene chiesto di eseguire una di queste operazioni, puoi restituire un errore.

Esempio: cubo

Ingresso:

4 3

Produzione:

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

Ho avuto alcune idee su come questo algoritmo potrebbe funzionare ed essere molto ricorsivo, ma finora ho fallito, ma se stai cercando ispirazione dai un'occhiata a: https://en.wikipedia.org/wiki/Euler_characteristic

Come esempio per calcolare il numero di vertici, bordi e facce, considera il cubo che è {4,3}. Se guardiamo i 4 iniziali, allora ha 4 bordi e 4 vertici. Ora se guardiamo i prossimi 3, sappiamo che 3 bordi si incontrano in ciascun vertice, ogni bordo si collega a 2 vertici, 2 facce si incontrano su ogni bordo, ogni faccia si collega a 4 bordi (a causa dei lati quadrati), e abbiamo la formula caratteristica di Eulero.

E = 3/2 V

E = 4/2 F

V - E + F = 2

Che dà E = 12, V = 8, F = 6.

punteggio

Al fine di mantenere la domanda sull'argomento, questo è stato rivisto in Code Golf. Il codice più corto vince.

È stato creato un github per questa domanda


1
Googling mostra che ci sono solo 3 famiglie di politopo normali che si estendono oltre le 4 dimensioni: analogo a cubo, ottaedro e tetraedro. Sembra che sarebbe più semplice scrivere per queste famiglie e codificare il resto (due politopi 3d, tre politopi 4d e la famiglia infinita di politopodi 2D). Per quanto posso vedere, soddisfa le specifiche ma non sarebbe generalizzabile. Sarebbe una risposta valida? Potrebbe essere possibile scrivere un algoritmo ricorsivo per generare grafici topologici oltre lo scopo della specifica, ma il killer con quell'approccio anche all'interno della specifica sta calcolando le coordinate.
Level River St

Come facciamo a conoscere i vertici reali, sapendo solo che sono equilateri?
Matthew Roh,

@SIGSEGV l'unico requisito specificato è che l'origine deve corrispondere al centro o ad uno dei punti. Ciò offre ampio spazio per ruotare la forma come preferisci. en.wikipedia.org/wiki/Simplex fornisce un algoritmo per il calcolo delle coordinate degli ipertetraedri (che potrebbe forse essere esteso all'icosaedro e al suo analogo 4d ma farlo è troppo per me, quindi la mia domanda.) Gli ipercubi e gli iperocedri belle coordinate intere (e gli ipertetraedri anche in realtà, ma spesso solo in più dimensioni della forma stessa, che è disordinata.)
Level River St

@LevelRiverSt, sì, poiché i soli politopi esistenti sarebbero coperti dai tuoi suggerimenti, quindi sì, potresti codificarli.
Tony Ruth,

Ho espresso il voto di chiusura su questa domanda perché è una sfida in stile pistole più veloci in Occidente , dove vince la prima risposta valida. Questo non è generalmente considerato un criterio vincente valido. Non so come sia stato aperto per così tanto tempo, avrebbe dovuto essere chiuso.
Post Rock Garf Hunter,

Risposte:


2

Pitone

Ecco un programma ricorsivo senza casi particolari. Ignorando righe e commenti vuoti, sono meno di 100 90 righe, incluso un controllo gratuito della formula di Euler alla fine. Escludendo le definizioni delle funzioni matematiche ad hoc (che potrebbero essere fornite probabilmente da una libreria) e i / o, la generazione di polytope è di 50 righe di codice. E fa anche i polipetti di stelle!

Il politopo in uscita avrà la lunghezza del bordo 1 e sarà in posizione e orientamento canonici, nel senso seguente:

  • il primo vertice è l'origine,
  • il primo bordo si trova lungo l'asse + x,
  • la prima faccia è nel semipiano + y del piano xy,
  • la prima 3-cella si trova nel semispazio + z dello spazio xyz, ecc.

Oltre a ciò, gli elenchi di output non sono in un ordine particolare. (Beh, in realtà, questo non è del tutto vero - in realtà usciranno approssimativamente in ordine a partire dal primo elemento e espandendosi verso l'esterno.)

Non c'è controllo per il simbolo schlafli non valido; se ne dai uno, il programma probabilmente uscirà dai binari (loop infinito, overflow dello stack o semplicemente immondizia).

Se chiedi una piastrellatura planare infinita come {4,4} o {3,6} o {6,3}, il programma inizierà effettivamente a generare la piastrellatura, ma continuerà per sempre finché non si esaurisce lo spazio, mai finitura o produzione. Questo non sarebbe troppo difficile da risolvere (basta mettere un limite al numero di elementi da generare; il risultato dovrebbe essere una regione abbastanza coerente dell'immagine infinita, dal momento che gli elementi sono generati in un ordine approssimativamente ampio per la prima ricerca).

Il codice

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

Provandolo su alcuni casi

Input ( cubo ):

4 3

Produzione:

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

Input da una shell di comando unix ( polychoron a 120 celle ):

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

Produzione:

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

Input ( polytope trasversale a 10 dimensioni ):

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

Produzione:

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

Input ( simplex tridimensionale ):

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

Polipetti a stella

Ah, e naturalmente fa anche i polipetti di stelle! Non ho nemmeno avuto bisogno di provare :-) Tranne il fatto che la parte relativa alla formula di Euler alla fine fallisce, dal momento che quella formula non è valida per i polipetti a stella.

Input ( piccolo dodecaedro stellato ):

5/2 5

Produzione:

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

Input ( grande 120 celle stellate ):

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

Produzione:

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

Grazie per aver rianimato questa domanda e la tua risposta sembra piuttosto impressionante. Mi piace la natura ricorsiva e le figure di stelle. Ho collegato il tuo codice ad alcuni opengl per disegnare polipropi (vedi link github sopra).
Tony Ruth,

14

Rubino

sfondo

Esistono tre famiglie di politopo normali che si estendono in dimensioni infinite:

  • i simplex, di cui fa parte il tetraedro (spesso li chiamerò qui ipertetraedri, sebbene il termine simplex sia più corretto). I loro simboli schlafi sono della forma {3,3,...,3,3}

  • gli n-cubi, di cui il cubo è un membro. I loro simboli schlafi sono della forma{4,3,...,3,3}

  • gli ortoplessi, di cui fa parte l'ottaedro (spesso li riferirò qui come iperottaedri) I loro simboli schlafi hanno la forma {3,3,...,3,4}

Esiste un'ulteriore infinita famiglia di polipetti regolari, simbolo {m} , quello dei poligoni bidimensionali, che può avere un numero qualsiasi di bordi m.

Inoltre, ci sono solo altri cinque casi speciali di politopo normale: l'icosaedro tridimensionale {3,5}e il dodecaedro {5,3}; i loro analoghi a 4 dimensioni 600 {3,3,5}e 120 celle {5,3,3}; e un altro politopo 4 dimensionale, la 24-cella {3,4,3}(i cui analoghi più vicini in 3 dimensioni sono il cubottaedro e il suo doppio il dodecaedro rombico).

Funzione principale

Di seguito è la polytopefunzione principale che interpreta il simbolo di Schlafi. Si aspetta una matrice di numeri e restituisce una matrice contenente un mazzo di matrici come segue:

  • Un array di tutti i vertici, ciascuno espresso come un array di coordinate n-element (dove n è il numero di dimensioni)

  • Una matrice di tutti i bordi, ciascuno espresso come un 2 elemento di indici di vertici

  • Un array di tutte le facce, ciascuna espressa come elemento m di indici di vertici (dove m è il numero di vertici per faccia)

e così via a seconda del numero di dimensioni.

Calcola da solo i polipropoli 2D, chiama le funzioni per le 3 famiglie di dimensioni infinite e usa le tabelle di ricerca per i cinque casi speciali. Si aspetta di trovare le funzioni e le tabelle sopra dichiarate.

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
}

Funzioni per le famiglie tetraedro, cubo e ottaedro

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

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

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

Spiegazione della famiglia del tetraedro - coordinate

un simplex / ipertetraedro n-dimensionale ha n + 1 punti. È molto facile dare i vertici del simplex n-dimensionale in n + 1 dimensioni.

(1,0,0),(0,1,0),(0,0,1)Descrive quindi un triangolo 2d incorporato in 3 dimensioni e (1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1)descrive un tetraedro 3d incorporato in 4 dimensioni. Ciò è facilmente verificabile confermando che tutte le distanze tra i vertici sono sqrt (2).

Diversi algoritmi complicati sono dati su Internet per trovare i vertici del simplex n-dimensionale nello spazio n-dimensionale. Ne ho trovato uno estremamente semplice nei commenti di Will Jagy su questa risposta /mathpro//a/38725 . L'ultimo punto si trova sulla linea p=q=...=x=y=zad una distanza di sqrt (2) dagli altri. Quindi il triangolo sopra può essere convertito in un tetraedro aggiungendo un punto in uno (-1/3,-1/3,-1/3)o (1,1,1). Questi 2 possibili valori delle coordinate per l'ultimo punto sono indicati da (1-(1+n)**0.5)/ne(1+(1+n)**0.5)/n

Poiché la domanda dice che la dimensione di n-tope non ha importanza, preferisco moltiplicare per n e usare le coordinate (n,0,0..0)fino al (0..0,0,n)punto finale in (t,t,..,t,t)cui t = 1-(1+n)**0.5per semplicità.

Poiché il centro di questo tetraedro non è all'origine, una linea deve essere corretta da tutte le coordinate s.map!{|j|j-((1-(1+n)**0.5)+n)/(1+n)} che trova quanto è distante il centro dall'origine e lo sottrae. L'ho tenuto come un'operazione separata. Comunque ho usato s[i]+=ndove s[i]=nfarebbe, per alludere al fatto che quando l'array è inizializzato da s=[0]*nnoi potremmo inserire l'offset corretto qui e fare la correzione del centraggio all'inizio piuttosto che alla fine.

Spiegazione della famiglia del tetraedro - topologia grafica

Il grafico del simplex è il grafico completo: ogni vertice è collegato esattamente una volta ad ogni altro vertice. Se abbiamo un n simplex, possiamo rimuovere qualsiasi vertice per dare un n-1 simplex, fino al punto in cui abbiamo un triangolo o addirittura un bordo.

Pertanto abbiamo un totale di 2 ** (n + 1) articoli da catalogare, ognuno rappresentato da un numero binario. Questo va da tutte le 0s per il nulla, attraverso una 1per un vertice e due1 s per un bordo, fino a tutte le 1s per il politipo completo.

Abbiamo impostato una matrice di array vuoti per memorizzare gli elementi di ogni dimensione. Quindi eseguiamo il ciclo da zero a (2 ** n + 1) per generare ciascuno dei possibili sottoinsiemi di vertici e li memorizziamo nell'array in base alle dimensioni di ciascun sottoinsieme.

Non siamo interessati a qualcosa di più piccolo di un bordo (un vertice o uno zero) né al politopo completo (poiché il cubo completo non è indicato nell'esempio nella domanda), quindi torniamo tg[2..n]a rimuovere questi elementi indesiderati. Prima di tornare, viriamo [tv] che contiene le coordinate del vertice all'inizio.

codice

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

spiegazione delle famiglie di cubi e ottaedri - coordinate

Il n-cubo ha 2**nvertici, ciascuna rappresentata da una matrice di n 1s e -1s (tutte le possibilità sono consentiti.) Iteriamo indici 0per 2**n-1la lista di tutti i vertici, e costruire una matrice per ogni vertice scorrendo bit della indice e aggiunta -1o 1all'array (dal bit meno significativo al bit più significativo). In questo modo Binary 1101diventa il punto 4d [1,-1,1,1].

Il n-ottaedro o n-orthoplex ha 2nvertici, con tutte le coordinate zero tranne uno, che un essere 1o -1. L'ordine dei vertici nell'array generato è [[1,0,0..],[-1,0,0..],[0,1,0..],[0,-1,0..],[0,0,1..],[0,0,-1..]...]. Si noti che poiché l'ottaedro è il doppio del cubo, i vertici dell'ottaedro sono definiti dai centri delle facce del cubo che lo circonda.

spiegazione delle famiglie di cubi e ottaedri - topologia dei grafi

Qualche ispirazione è stata presa dai lati dell'ipercubo e dal fatto che l'iperocedro è il doppio dell'ipercubo.

Per n-cube, ci sono 3**narticoli da catalogare. Ad esempio, il cubo 3 ha 3**3= 27 elementi. Questo può essere visto studiando un cubo di rubik, che ha 1 centro, 6 facce, 12 bordi e 8 vertici per un totale di 27. Passiamo da -1,0 a -1 in tutte le dimensioni definendo un cubo n di lunghezza 2 x 2x2 .. e restituisce tutti i vertici che NON si trovano sul lato opposto del cubo. Pertanto, il punto centrale del cubo restituisce tutti i 2 ** n vertici e allontanando un'unità dal centro lungo un asse qualsiasi riduce il numero di vertici della metà.

Come per la famiglia del tetraedro, iniziamo generando una matrice vuota di array e la popoliamo in base al numero di vertici per elemento. Si noti che poiché il numero di vertici varia come 2 ** n mentre saliamo attraverso bordi, facce, cubi, ecc., Utilizziamo log2(s.size)+1anziché semplicemente s.size. Ancora una volta, dobbiamo rimuovere l'ipercubo stesso e tutti gli elementi con meno di 2 vertici prima di tornare dalla funzione.

La famiglia ottaedro / orthoplex sono i doppi della famiglia dei cubi, quindi di nuovo ci sono 3**nelementi da catalogare. Qui eseguiamo l'iterazione -1,0,1per tutte le dimensioni e se la coordinata diversa da zero di un vertice è uguale alla coordinata corrispondente del punto, il vertice viene aggiunto all'elenco corrispondente a quel punto. Quindi un bordo corrisponde a un punto con due coordinate diverse da zero, un triangolo a un punto con 3 coordinate diverse da zero e un tetraedro a un punto con 4 contatti diversi da zero (nello spazio 4d).

Le matrici di vertici risultanti per ciascun punto sono memorizzate in un array di grandi dimensioni come per gli altri casi e prima di tornare dobbiamo rimuovere tutti gli elementi con meno di 2 vertici. Ma in questo caso non è necessario rimuovere l'intero n-tope parent poiché l'algoritmo non lo registra.

Le implementazioni del codice per il cubo sono state progettate per essere il più simile possibile. Sebbene ciò abbia una certa eleganza, è probabile che possano essere elaborati algoritmi più efficienti basati sugli stessi principi.

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

Codice per la generazione di tabelle per i casi speciali 3d

È stato utilizzato un orientamento con icosaedro / dodecaedro orientato con l'asse di simmetria cinque volte parallelo all'ultima dimensione, in quanto realizzato per l'etichettatura più coerente delle parti. La numerazione dei vertici e delle facce dell'icosaedro è secondo il diagramma nei commenti del codice e invertita per il dodecaedro.

Secondo https://en.wikipedia.org/wiki/Regular_icosahedron la latitudine dei 10 vertici non polari dell'icosaedro è +/- arctan (1/2) Le coordinate dei primi 10 vertici dell'icosaedro sono calcolate da questo, su due cerchi di raggio 2 a distanza +/- 2 dal piano xy. Questo rende il raggio complessivo della circumsfera sqrt (5), quindi gli ultimi 2 vertici sono a (0,0, + / - sqrt (2)).

Le coordinate dei vertici del dodecaedro sono calcolate sommando le coordinate dei tre vertici icosaedri che li circondano.

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

Codice per la generazione delle tabelle per i casi speciali 4d

Questo è un po 'un trucco. L'esecuzione di questo codice richiede alcuni secondi. Sarebbe meglio archiviare l'output in un file e caricarlo come richiesto.

L'elenco delle 120 coordinate del vertice per la 600cell è da http://mathworld.wolfram.com/600-Cell.html . Le 24 coordinate del vertice che non presentano un rapporto aureo formano i vertici di una 24 celle. Wikipedia ha lo stesso schema ma presenta un errore nella scala relativa di queste 24 coordinate e delle altre 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

Esempio di utilizzo e output

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
Caspita, questa è una risposta fantastica !! Sono molto sorpreso che tu sia riuscito a farlo in circa 200 righe. Ho fatto funzionare il cubo, il tetraedro, 600 celle e alcuni altri, e sembravano buoni. È difficile verificare l'output dal momento che ce n'è tanto; è abbastanza facile che l'output sia più lungo del programma, ma ci penserò io. Proverò a caricarlo in openGL e visualizzerò i solidi platonici che dovrebbero essere semplici poiché tutte le facce sono elencate. Penso che aggiungere tassellazioni nello spazio piatto sarebbe facile, e potrei provare anche quello.
Tony Ruth,

@TonyRuth la chiave stava trovando l'algoritmo migliore. Meno righe = meno spazio per l'errore. La prima cosa che feci fu controllare cosa esistesse oltre alle 3 famiglie di dimensioni infinite e fu allora che decisi di rispondere. I commenti di Will Jagy sono stati una manna dal cielo (stavo pensando a quel tipo di soluzione visto che il metodo di Wikipedia sembrava difficile) quindi le coordinate non intere sono ridotte al minimo. Volevo farlo prima della scadenza della ricompensa, quindi il controllo non è stato estremamente accurato e non li ho tracciati. Fammi sapere di eventuali errori: ho corretto la cella 24 ore fa.
Level River St

I vertici della faccia di @TonyRuth non sono in un ordine particolare (non girano attorno alla faccia in senso orario o altro). Per dimensioni superiori, non esiste un ordine standard. Gli ipercubi hanno facce elencate in ordine numerico, quindi il 2o e il 3o vertice sono diagonalmente opposti (dovrai scambiare il 1o, il 2o, il 3o e il 3o e il 4o vertice se li vuoi in senso orario / antiorario). Il dodecaedro dovrebbe avere facce in in senso orario / antiorario ma la 120cell avrà i vertici della faccia in tutti gli ordini.
Level River St
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.