Schläfli Выпуклый обычный многогранник


15

Фон

Шлефли Символ представляет собой обозначение вида {р, Q, R, ...} , который определяет регулярные многогранники и мозаики.

Символ Шлефли - рекурсивное описание, начинающееся с p-стороннего правильного многоугольника как {p}. Например, {3} - равносторонний треугольник, {4} - квадрат и т. Д.

Правильный многогранник, имеющий q правильных граней p-стороннего многоугольника вокруг каждой вершины, представлен как {p, q}. Например, куб имеет 3 квадрата вокруг каждой вершины и представлен {4,3}.

Правильный 4-мерный многогранник с r {p, q} правильными многогранными клетками вокруг каждого ребра представлен {p, q, r}. Например, у тессеракта {4,3,3} есть 3 куба {4,3} по краю.

В общем случае правильный многогранник {p, q, r, ..., y, z} имеет грани z {p, q, r, ..., y} вокруг каждого пика, где пик является вершиной в многограннике, ребро в 4-многограннике, грань в 5-многограннике, ячейка в 6-многограннике и (n-3) -грань в n-многограннике.

Правильный многогранник имеет правильную фигуру вершины. Вершина правильного многогранника {p, q, r, ... y, z} равна {q, r, ... y, z}.

Обычные многогранники могут иметь элементы звездного многоугольника, такие как пентаграмма, с символом {5/2}, представленные вершинами пятиугольника, но соединенные попеременно.

Символ Шлефли может представлять собой конечный выпуклый многогранник, бесконечную тесселяцию евклидова пространства или бесконечную тесселяцию гиперболического пространства, в зависимости от углового дефекта конструкции. Дефект положительного угла позволяет вершинной фигуре складываться в более высокое измерение и возвращается обратно в себя как многогранник. Дефект нулевого угла тесселярует пространство того же размера, что и грани. Дефект отрицательного угла не может существовать в обычном пространстве, но может быть построен в гиперболическом пространстве.

Конкуренция

Ваша цель - создать программу, которая при прохождении символа Шлефли выдаст полное описание выпуклого многогранника. Это только подмножество символов Шлефли, но оно является самым простым, я считаю, что даже без других возможностей это будет очень трудной задачей, а многогранники являются отправной точкой для тесселяций. Правила этого вопроса были разработаны с учетом того, что этот результат является API, и я не смог найти такую ​​программу в Интернете.

Ваша программа должна выполнять все следующие действия.

  • Программа должна быть способна генерировать любой конечномерный регулярный выпуклый многогранник. В двух измерениях это включает в себя н-гонов. В 3-х измерениях это платоновые тела, в 4-х измерениях это тессеракт, ортоплекс и некоторые другие)
  • Программа должна либо (а) поместить точку в исходную точку, либо (б) убедиться, что среднее значение по всем точкам является исходной. Ориентация не имеет значения. Общий размер не имеет значения.
  • Программа должна предоставить полное описание, означающее, что для 4-мерного объекта программа вернет / напечатает вершины, ребра, грани и многогранники. Порядок их сообщения не имеет значения. Для многогранников это информация, которая понадобится вам для визуализации объекта.

Вам не нужно обрабатывать:

  • Tesselations
  • Гиперболическая геометрия
  • Дробные символы Шлефли (невыпуклые)
  • Встроенные символы Шлефли (неравномерный рельеф)

Если вас попросят сделать что-то из этого, вы можете вернуть ошибку.

Пример: куб

Входные данные:

4 3

Выход:

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

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

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

У меня были некоторые идеи о том, как этот алгоритм мог бы работать и быть очень рекурсивным, но до сих пор я потерпел неудачу, но если вы ищете вдохновение, проверьте: https://en.wikipedia.org/wiki/Euler_characteristic

В качестве примера определения количества вершин, ребер и граней, рассмотрим куб {4,3}. Если мы посмотрим на начальные 4, то у них есть 4 ребра и 4 вершины. Теперь, если мы посмотрим на следующие 3, мы знаем, что 3 ребра встречаются в каждой вершине, каждое ребро соединяется с 2 вершинами, 2 грани встречаются у каждого ребра, каждая грань соединяется с 4 ребрами (из-за квадратных сторон), и мы имеем Характеристическая формула Эйлера.

E = 3/2 В

E = 4/2 F

V - E + F = 2

Что дает E = 12, V = 8, F = 6.

счет

Чтобы оставить вопрос по теме, он был изменен на Code Golf. Самый короткий код выигрывает.

Github был создан для этого вопроса


1
Поиск в Google показывает, что есть только 3 семейства правильных многогранников, простирающихся за пределы четырех измерений: аналог куба, октаэдра и тетраэдра. Кажется, было бы проще написать для этих семейств и жестко закодировать остальные (два 3d-многогранника, три четырехмерных многогранника и бесконечное семейство двумерных многогранников). Насколько я вижу, это соответствует спецификации, но не может быть обобщено. Это будет правильный ответ? Возможно, было бы целесообразно написать рекурсивный алгоритм для генерации топологических графов, выходящих за рамки спецификации, но убийца с таким подходом даже внутри спецификации вычисляет координаты.
Уровень реки St

Как мы узнаем фактические вершины, зная, что они равносторонние?
Мэтью Ро

@SIGSEGV единственное указанное требование заключается в том, что начало координат должно соответствовать либо центру, либо одной из точек. Это дает много возможностей для поворота формы, как вам угодно. en.wikipedia.org/wiki/Simplex предоставляет алгоритм для вычисления координат гипертетраэдров (который, возможно, может быть расширен до икосаэдра и его 4-го аналога, но для меня это слишком много, поэтому мой вопрос). Гиперкубы и гипероктаэдры имеют хорошие целочисленные координаты (и гипертетраэдры тоже на самом деле, но часто только в большем количестве измерений, чем сама фигура, что неопрятно.)
Level River St

@LevelRiverSt, да, так как единственные существующие многогранники будут охватываться вашими предложениями, тогда да, вы можете жестко их кодировать.
Тони Рут

Я подал заключительное голосование по этому вопросу, потому что это самый быстрый вызов, когда победит первый действительный ответ. Обычно это не считается действительным критерием выигрыша. Я не знаю, как это было открыто так долго, оно должно было быть закрыто.
Пост Рок Гарф Хантер

Ответы:


2

питон

Вот рекурсивная программа без особых случаев. Не обращая внимания на пустые строки и комментарии, это менее 100 90 строк, включая бесплатную проверку формулы Эйлера в конце. Исключая определения специальных математических функций (которые, вероятно, могут быть предоставлены библиотекой) и ввода / вывода, генерация многогранника составляет 50 строк кода. И это даже делает звезды многогранниками!

Выходной многогранник будет иметь длину ребра 1 и будет находиться в каноническом положении и ориентации в следующем смысле:

  • первая вершина является источником,
  • первый край лежит вдоль оси + х,
  • первая грань находится в + y полуплоскости плоскости xy,
  • первая 3-ячейка находится в полупространстве + z пространства XYZ и т. д.

Кроме этого, выходные списки расположены в произвольном порядке. (Ну, на самом деле, это не совсем так - на самом деле они будут выглядеть примерно так, начиная с первого элемента и расширяясь наружу.)

Там нет проверки на недопустимый символ Schlafli; если вы его дадите, программа, вероятно, сойдет с пути (бесконечный цикл, переполнение стека или просто мусор).

Если вы запрашиваете бесконечное плоское разбиение, такое как {4,4} или {3,6} или {6,3}, программа фактически начнет генерировать разбиение, но оно будет длиться вечно, пока не закончится свободное пространство, никогда отделка или производство продукции. Это было бы не слишком сложно исправить (просто установите ограничение на количество генерируемых элементов; результатом должна быть довольно согласованная область бесконечной картинки, поскольку элементы генерируются в порядке поиска по ширине).

Код

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

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

import sys
from math import *

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

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

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

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

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

  gens = calcSymmetryGenerators(schlafli)

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

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

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

  return verts,edgesEtc

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

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

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

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

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

Опробовать это в некоторых случаях

Вход ( куб ):

4 3

Выход:

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

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

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

Ввод из командной оболочки unix ( многочлен из 120 ячеек ):

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

Выход:

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

Вход (10-мерный поперечный многогранник ):

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

Выход:

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

Вход (15-мерный симплекс ):

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

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

Звездные многогранники

Ха, и это, естественно, тоже звездные многогранники! Мне даже не нужно было пытаться :-) За исключением того, что немного о формуле Эйлера в конце терпит неудачу, так как эта формула не действительна для многогранников-звезд.

Вход ( маленький звездчатый додекаэдр ):

5/2 5

Выход:

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

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

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

Вход ( большой звездный 120-элементный ):

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

Выход:

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

Спасибо за оживление этого вопроса, и ваш ответ выглядит довольно внушительно. Мне нравится рекурсивная природа и звездные фигуры. Я подключил ваш код к некоторому opengl для рисования многогранников (см. Ссылку на github выше).
Тони Рут

14

Рубин

Фон

Есть три семейства правильных многогранников, простирающихся в бесконечные измерения:

  • симплексы, членом которых является тетраэдр (я буду часто называть их здесь гипертетраэдрами, хотя термин симплекс более правильный.) Их символы Шлафи имеют вид {3,3,...,3,3}

  • n-кубы, членом которых является куб. Их символы Schlafi имеют форму{4,3,...,3,3}

  • ортоплексы, членом которых является октаэдр (я буду часто называть их здесь гипероктаэдрами). Их символы Шлафи имеют вид {3,3,...,3,4}

Существует еще одно бесконечное семейство правильных многогранников, символ {m}, из двумерных многоугольников, которые могут иметь любое количество ребер m.

В дополнение к этому есть только пять других частных случаев правильного многогранника: трехмерный икосаэдр {3,5}и додекаэдр {5,3}; их 4-мерные аналоги - 600-элементная {3,3,5}и 120-элементная {5,3,3}; и еще один 4-мерный многогранник, 24-элементный {3,4,3}(его ближайшими аналогами в 3-х измерениях являются кубоктаэдр и его двойной ромбический додекаэдр.)

Основная функция

Ниже приведена основная polytopeфункция, которая интерпретирует символ Шлафи. Он ожидает массив чисел и возвращает массив, содержащий несколько массивов, следующим образом:

  • Массив всех вершин, каждая из которых выражается в виде n-элементного массива координат (где n - количество измерений)

  • Массив всех ребер, каждое из которых выражается в виде 2-х элементов индексов вершин

  • Массив всех граней, каждая из которых выражается как m-элемент индексов вершин (где m - количество вершин на грани)

и так далее в зависимости от количества измерений.

Он сам вычисляет 2d многогранники, вызывает функции для трех бесконечномерных семейств и использует таблицы поиска для пяти особых случаев. Он ожидает найти функции и таблицы, объявленные выше.

include Math

#code in subsequent sections of this answer should be inserted here 

polytope=->schl{
  if schl.size==1                                #if a single digit calculate and return a polygon
    return [(1..schl[0]).map{|i|[sin(PI*2*i/schl[0]),cos(PI*2*i/schl[0])]},(1..schl[0]).map{|i|[i%schl[0],(i+1)%schl[0]]}]  
  elsif  i=[[3,5],[5,3]].index(schl)             #if a 3d special, lookup from tables
    return [[vv,ee,ff],[uu,aa,bb]][i]
  elsif i=[[3,3,5],[5,3,3],[3,4,3]].index(schl)  #if a 4d special. lookup fromm tables
    return [[v,e,f,g],[u,x,y,z],[o,p,q,r]][i]
  elsif schl.size==schl.count(3)                 #if all threes, call tetr for a hypertetrahedron
    return tetr[schl.size+1]
  elsif schl.size-1==schl.count(3)               #if all except one number 3
    return cube[schl.size+1] if schl[0]==4       #and the 1st digit is 4, call cube for a hypercube
    return octa[schl.size+1] if schl[-1]==4      #and the last digit is 4, call octa for a hyperoctahedron
  end
  return "error"                                 #in any other case return an error
}

Функции для семейств тетраэдров, кубов и октаэдров

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

https://en.wikipedia.org/wiki/5-cell (4-й симплекс)

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

Объяснение семейства тетраэдров - координаты

n-мерный симплекс / гипертетраэдр имеет n + 1 точек. Очень легко дать вершины n-мерного симплекса в n + 1 измерениях.

Таким образом, (1,0,0),(0,1,0),(0,0,1)описывается двумерный треугольник, встроенный в 3 измерения, и (1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1)описывается трехмерный тетраэдр, встроенный в 4 измерения. Это легко проверить, подтвердив, что все расстояния между вершинами являются sqrt (2).

Различные сложные алгоритмы приведены в Интернете для поиска вершин для n-мерного симплекса в n-мерном пространстве. Я нашел удивительно простой ответ в комментариях Уилла Джейги к этому ответу /mathpro//a/38725 . Последняя точка лежит на прямой p=q=...=x=y=zна расстоянии sqrt (2) от остальных. Таким образом, указанный выше треугольник можно преобразовать в тетраэдр, добавив точку в точке (-1/3,-1/3,-1/3)или (1,1,1). Эти 2 возможных значения координат для последней точки задаются (1-(1+n)**0.5)/nи(1+(1+n)**0.5)/n

Поскольку вопрос говорит о том, что размер n-вершины не имеет значения, я предпочитаю умножать на n и использовать координаты с (n,0,0..0)точностью до (0..0,0,n)конечной точки, (t,t,..,t,t)где t = 1-(1+n)**0.5для простоты.

Поскольку центр этого тетраэдра не находится в начале координат, исправление всех координат должно быть выполнено линией, s.map!{|j|j-((1-(1+n)**0.5)+n)/(1+n)}которая определяет, как далеко находится центр от начала координат, и вычитает его. Я сохранил это как отдельную операцию. Тем не менее, я использовал « s[i]+=nгде» s[i]=n, чтобы сослаться на тот факт, что, когда массив инициализируется, s=[0]*nмы можем вместо этого поместить правильное смещение и выполнить коррекцию центрирования в начале, а не в конце.

Объяснение семейства тетраэдров - топология графа

Граф симплекса является полным графом: каждая вершина связана ровно один раз с каждой другой вершиной. Если у нас есть n симплекс, мы можем удалить любую вершину, чтобы получить n-1 симплекс, вплоть до точки, где у нас есть треугольник или даже ребро.

Таким образом, мы имеем в общей сложности 2 ** (n + 1) элементов для каталога, каждый из которых представлен двоичным числом. Это варьируется от всех 0s для небытия, до одного 1для вершины и двух 1s для ребра, до всех 1s для полного многогранника.

Мы создали массив пустых массивов для хранения элементов каждого размера. Затем мы выполняем цикл от нуля до (2 ** n + 1), чтобы сгенерировать каждое из возможных подмножеств вершин и сохранить их в массиве в соответствии с размером каждого подмножества.

Мы не заинтересованы ни в чем меньше, чем ребро (вершина или ноль), ни в полном многограннике (поскольку полный куб не приведен в примере в вопросе), поэтому мы возвращаемся, tg[2..n]чтобы удалить эти нежелательные элементы. Прежде чем вернуться, мы прикрепляем [tv], содержащий координаты вершины, в начало.

код

tetr=->n{

  #Tetrahedron Family Vertices
  tv=(0..n).map{|i|
    s=[0]*n
    if i==n
      s.map!{(1-(1+n)**0.5)}
    else
      s[i]+=n
    end
    s.map!{|j|j-((1-(1+n)**0.5)+n)/(1+n)}
  s}

  #Tetrahedron Family Graph
  tg=(0..n+1).map{[]}
  (2**(n+1)).times{|i|
    s=[]
    (n+1).times{|j|s<<j if i>>j&1==1}
    tg[s.size]<<s
  }

return [tv]+tg[2..n]}

cube=->n{

  #Cube Family Vertices
  cv=(0..2**n-1).map{|i|s=[];n.times{|j|s<<(i>>j&1)*2-1};s}

  #Cube Family Graph
  cg=(0..n+1).map{[]}
  (3**n).times{|i|                         #for each point
    s=[]
    cv.size.times{|j|                      #and each vertex
      t=true                               #assume vertex goes with point
      n.times{|k|                          #and each pair of opposite sides
        t&&= (i/(3**k)%3-1)*cv[j][k]!=-1   #if the vertex has kingsmove distance >1 from point it does not belong      
      }
      s<<j if t                            #add the vertex if it belongs
    }
    cg[log2(s.size)+1]<<s if s.size > 0
  } 

return [cv]+cg[2..n]}

octa=->n{

  #Octahedron Family Vertices
  ov=(0..n*2-1).map{|i|s=[0]*n;s[i/2]=(-1)**i;s}

  #Octahedron Family Graph
  og=(0..n).map{[]}
  (3**n).times{|i|                         #for each point
    s=[]
    ov.size.times{|j|                      #and each vertex
      n.times{|k|                          #and each pair of opposite sides
        s<<j if (i/(3**k)%3-1)*ov[j][k]==1 #if the vertex is located in the side corresponding to the point, add the vertex to the list      
      }    
    }
    og[s.size]<<s
  } 

return [ov]+og[2..n]}

объяснение семейств куба и октаэдра - координаты

П-куба имеет 2**nвершины, каждый из которых представлено массивом п 1с и -1х (все возможности разрешены.) Мы итерация через индексы 0к 2**n-1списку всех вершин, и построить массив для каждой вершины перебором бит индекс и добавление -1или 1к массиву (младший значащий бит к старшему значащему биту). Таким образом, двоичный 1101становится 4-й точкой [1,-1,1,1].

У n-октаэдра или n-ортоплекса есть 2nвершины, причем все координаты равны нулю, кроме одной, которая может быть 1или -1. Порядок вершин в массиве вырабатываемого [[1,0,0..],[-1,0,0..],[0,1,0..],[0,-1,0..],[0,0,1..],[0,0,-1..]...]. Обратите внимание, что, поскольку октаэдр является двойником куба, вершины октаэдра определяются центрами граней окружающего его куба.

объяснение семейств куба и октаэдра - топология графа

Некоторое вдохновение было взято со стороны гиперкуба и того факта, что гипероктаэдр является двойником гиперкуба.

Для n-cube есть 3**nэлементы для каталога. Например, куб 3 имеет 3**3= 27 элементов. Это можно увидеть, изучив кубик Рубика, который имеет 1 центр, 6 граней, 12 ребер и 8 вершин, что в общей сложности составляет 27. Мы перебираем -1,0 и -1 во всех измерениях, определяя n-куб с длиной стороны 2x2x2 .. и вернуть все вершины, которые НЕ находятся на противоположной стороне куба. Таким образом, центральная точка куба возвращает все 2 ** n вершин, а перемещение на одну единицу от центра вдоль любой оси уменьшает количество вершин вдвое.

Как и в случае с семейством тетраэдров, мы начинаем с создания пустого массива массивов и заполняем его в соответствии с количеством вершин на элемент. Обратите внимание, что, поскольку число вершин изменяется как 2 ** n при прохождении ребер, граней, кубов и т. Д., Мы используем log2(s.size)+1вместо простого s.size. Опять же, мы должны удалить сам гиперкуб и все элементы с менее чем 2 вершинами, прежде чем вернуться из функции.

Семейство октаэдров / ортоплексов - это двойники семейства кубов, поэтому снова есть 3**nэлементы для каталога. Здесь мы повторяем -1,0,1все измерения и, если ненулевая координата вершины равна соответствующей координате точки, вершина добавляется в список, соответствующий этой точке. Таким образом, ребро соответствует точке с двумя ненулевыми координатами, треугольнику - точке с 3 ненулевыми координатами и тетраэдру - точке с 4 ненулевыми контактами (в 4-мерном пространстве).

Полученные массивы вершин для каждой точки сохраняются в большом массиве, как и в других случаях, и мы должны удалить все элементы с менее чем 2 вершинами перед возвратом. Но в этом случае нам не нужно удалять полный родительский n-tope, потому что алгоритм его не записывает.

Реализации кода для куба должны быть максимально похожими. Хотя это имеет определенную элегантность, вполне вероятно, что могут быть разработаны более эффективные алгоритмы, основанные на тех же принципах.

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

Код для генерации таблиц для 3d особых случаев

Ориентация с икосаэдром / додекаэдром, ориентированным с пятикратной осью симметрии, параллельной последнему измерению, была использована для наиболее последовательной маркировки деталей. Нумерация вершин и граней для икосаэдра соответствует схеме в комментариях к коду и обратная для додекаэдра.

Согласно https://en.wikipedia.org/wiki/Regular_icosahedron широта 10 неполярных вершин икосаэдра равна +/- arctan (1/2). Координаты первых 10 вершин икосаэдра вычисляются из это на двух окружностях радиуса 2 на расстоянии +/- 2 от плоскости ху. Это делает общий радиус окружности sqrt (5), поэтому последние 2 вершины находятся в (0,0, + / - sqrt (2)).

Координаты вершин додекаэдра вычисляются путем суммирования координат трех окружающих их вершин икосаэдра.

=begin
TABLE NAMES      vertices     edges      faces
icosahedron      vv           ee         ff
dodecahedron     uu           aa         bb 

    10
    / \   / \   / \   / \   / \
   /10 \ /12 \ /14 \ /16 \ /18 \
   -----1-----3-----5-----7-----9
   \ 0 / \ 2 / \ 4 / \ 6 / \ 8 / \
    \ / 1 \ / 3 \ / 5 \ / 7 \ / 9 \
     0-----2-----4-----6-----8-----
      \11 / \13 / \15 / \17 / \19 /
       \ /   \ /   \ /   \ /   \ / 
       11
=end

vv=[];ee=[];ff=[]
10.times{|i|
  vv[i]=[2*sin(PI/5*i),2*cos(PI/5*i),(-1)**i]
  ee[i]=[i,(i+1)%10];ee[i+10]=[i,(i+2)%10];ee[i+20]=[i,11-i%2]
  ff[i]=[(i-1)%10,i,(i+1)%10];ff[i+10]=[(i-1)%10,10+i%2,(i+1)%10]

}
vv+=[[0,0,-5**0.5],[0,0,5**0.5]]

uu=[];aa=[];bb=[]
10.times{|i|
  uu[i]=(0..2).map{|j|vv[ff[i][0]][j]+vv[ff[i][1]][j]+vv[ff[i][2]][j]}
  uu[i+10]=(0..2).map{|j|vv[ff[i+10][0]][j]+vv[ff[i+10][1]][j]+vv[ff[i+10][2]][j]}
  aa[i]=[i,(i+1)%10];aa[i+10]=[i,(i+10)%10];aa[i+20]=[(i-1)%10+10,(i+1)%10+10]
  bb[i]=[(i-1)%10+10,(i-1)%10,i,(i+1)%10,(i+1)%10+10] 
}
bb+=[[10,12,14,16,18],[11,13,15,17,19]]

Код для генерации таблиц для 4-х особых случаев

Это немного взломать. Этот код занимает несколько секунд для запуска. Было бы лучше сохранить вывод в файл и загрузить его по мере необходимости.

Список из 120 координат вершин для 600cell взят с http://mathworld.wolfram.com/600-Cell.html . 24 координаты вершины, которые не имеют золотого сечения, образуют вершины из 24 ячеек. Википедия имеет ту же схему, но имеет ошибку в относительном масштабе этих 24 координат и других 96.

#TABLE NAMES                           vertices     edges      faces   cells
#600 cell (analogue of icosahedron)    v            e          f       g
#120 cell (analogue of dodecahedron)   u            x          y       z 
#24 cell                               o            p          q       r

#600-CELL

# 120 vertices of 600cell. First 24 are also vertices of 24-cell

v=[[2,0,0,0],[0,2,0,0],[0,0,2,0],[0,0,0,2],[-2,0,0,0],[0,-2,0,0],[0,0,-2,0],[0,0,0,-2]]+

(0..15).map{|j|[(-1)**(j/8),(-1)**(j/4),(-1)**(j/2),(-1)**j]}+

(0..95).map{|i|j=i/12
   a,b,c,d=1.618*(-1)**(j/4),(-1)**(j/2),0.618*(-1)**j,0
   h=[[a,b,c,d],[b,a,d,c],[c,d,a,b],[d,c,b,a]][i%12/3]
   (i%3).times{h[0],h[1],h[2]=h[1],h[2],h[0]}
h}

#720 edges of 600cell. Identified by minimum distance of 2/phi between them

e=[]
120.times{|i|120.times{|j|
  e<<[i,j]  if i<j && ((v[i][0]-v[j][0])**2+(v[i][1]-v[j][1])**2+(v[i][2]-v[j][2])**2+(v[i][3]-v[j][3])**2)**0.5<1.3  
}}

#1200 faces of 600cell. 
#If 2 edges share a common vertex and the other 2 vertices form an edge in the list, it is a valid triangle.

f=[]
720.times{|i|720.times{|j|
  f<< [e[i][0],e[i][1],e[j][1]] if i<j && e[i][0]==e[j][0] && e.index([e[i][1],e[j][1]])
}}

#600 cells of 600cell.
#If 2 triangles share a common edge and the other 2 vertices form an edge in the list, it is a valid tetrahedron.

g=[]
1200.times{|i|1200.times{|j|
  g<< [f[i][0],f[i][1],f[i][2],f[j][2]] if i<j && f[i][0]==f[j][0] && f[i][1]==f[j][1] && e.index([f[i][2],f[j][2]])

}}

#120 CELL (dual of 600 cell)

#600 vertices of 120cell, correspond to the centres of the cells of the 600cell
u=g.map{|i|s=[0,0,0,0];i.each{|j|4.times{|k|s[k]+=v[j][k]/4.0}};s}

#1200 edges of 120cell at centres of faces of 600-cell. Search for pairs of tetrahedra with common face
x=f.map{|i|s=[];600.times{|j|s<<j if i==(i & g[j])};s}

#720 pentagonal faces, surrounding edges of 600-cell. Search for sets of 5 tetrahedra with common edge
y=e.map{|i|s=[];600.times{|j|s<<j if i==(i & g[j])};s}

#120 dodecahedral cells surrounding vertices of 600-cell. Search for sets of 20 tetrahedra with common vertex
z=(0..119).map{|i|s=[];600.times{|j|s<<j if [i]==([i] & g[j])};s}


#24-CELL
#24 vertices, a subset of the 600cell
o=v[0..23]

#96 edges, length 2, found by minimum distances between vertices
p=[]
24.times{|i|24.times{|j|
  p<<[i,j]  if i<j && ((v[i][0]-v[j][0])**2+(v[i][1]-v[j][1])**2+(v[i][2]-v[j][2])**2+(v[i][3]-v[j][3])**2)**0.5<2.1  
}}

#96 triangles
#If 2 edges share a common vertex and the other 2 vertices form an edge in the list, it is a valid triangle.
q=[]
96.times{|i|96.times{|j|
  q<< [p[i][0],p[i][1],p[j][1]] if i<j && p[i][0]==p[j][0] && p.index([p[i][1],p[j][1]])
}}


#24 cells. Calculates the centre of the cell and the 6 vertices nearest it
r=(0..23).map{|i|a,b=(-1)**i,(-1)**(i/2)
    c=[[a,b,0,0],[a,0,b,0],[a,0,0,b],[0,a,b,0],[0,a,0,b],[0,0,a,b]][i/4]
    s=[]
    24.times{|j|t=v[j]
    s<<j if (c[0]-t[0])**2+(c[1]-t[1])**2+(c[2]-t[2])**2+(c[3]-t[3])**2<=2 
    }
s}

https://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

Пример использования и вывода

cell24 = polytope[[3,4,3]]

puts "vertices"
cell24[0].each{|i|p i}
puts "edges"
cell24[1].each{|i|p i}
puts "faces"
cell24[2].each{|i|p i}
puts "cells"
cell24[3].each{|i|p i}

vertices
[2, 0, 0, 0]
[0, 2, 0, 0]
[0, 0, 2, 0]
[0, 0, 0, 2]
[-2, 0, 0, 0]
[0, -2, 0, 0]
[0, 0, -2, 0]
[0, 0, 0, -2]
[1, 1, 1, 1]
[1, 1, 1, -1]
[1, 1, -1, 1]
[1, 1, -1, -1]
[1, -1, 1, 1]
[1, -1, 1, -1]
[1, -1, -1, 1]
[1, -1, -1, -1]
[-1, 1, 1, 1]
[-1, 1, 1, -1]
[-1, 1, -1, 1]
[-1, 1, -1, -1]
[-1, -1, 1, 1]
[-1, -1, 1, -1]
[-1, -1, -1, 1]
[-1, -1, -1, -1]
edges
[0, 8]
[0, 9]
[0, 10]
[0, 11]
[0, 12]
[0, 13]
[0, 14]
[0, 15]
[1, 8]
[1, 9]
[1, 10]
[1, 11]
[1, 16]
[1, 17]
[1, 18]
[1, 19]
[2, 8]
[2, 9]
[2, 12]
[2, 13]
[2, 16]
[2, 17]
[2, 20]
[2, 21]
[3, 8]
[3, 10]
[3, 12]
[3, 14]
[3, 16]
[3, 18]
[3, 20]
[3, 22]
[4, 16]
[4, 17]
[4, 18]
[4, 19]
[4, 20]
[4, 21]
[4, 22]
[4, 23]
[5, 12]
[5, 13]
[5, 14]
[5, 15]
[5, 20]
[5, 21]
[5, 22]
[5, 23]
[6, 10]
[6, 11]
[6, 14]
[6, 15]
[6, 18]
[6, 19]
[6, 22]
[6, 23]
[7, 9]
[7, 11]
[7, 13]
[7, 15]
[7, 17]
[7, 19]
[7, 21]
[7, 23]
[8, 9]
[8, 10]
[8, 12]
[8, 16]
[9, 11]
[9, 13]
[9, 17]
[10, 11]
[10, 14]
[10, 18]
[11, 15]
[11, 19]
[12, 13]
[12, 14]
[12, 20]
[13, 15]
[13, 21]
[14, 15]
[14, 22]
[15, 23]
[16, 17]
[16, 18]
[16, 20]
[17, 19]
[17, 21]
[18, 19]
[18, 22]
[19, 23]
[20, 21]
[20, 22]
[21, 23]
[22, 23]
faces
[0, 8, 9]
[0, 8, 10]
[0, 8, 12]
[0, 9, 11]
[0, 9, 13]
[0, 10, 11]
[0, 10, 14]
[0, 11, 15]
[0, 12, 13]
[0, 12, 14]
[0, 13, 15]
[0, 14, 15]
[1, 8, 9]
[1, 8, 10]
[1, 8, 16]
[1, 9, 11]
[1, 9, 17]
[1, 10, 11]
[1, 10, 18]
[1, 11, 19]
[1, 16, 17]
[1, 16, 18]
[1, 17, 19]
[1, 18, 19]
[2, 8, 9]
[2, 8, 12]
[2, 8, 16]
[2, 9, 13]
[2, 9, 17]
[2, 12, 13]
[2, 12, 20]
[2, 13, 21]
[2, 16, 17]
[2, 16, 20]
[2, 17, 21]
[2, 20, 21]
[3, 8, 10]
[3, 8, 12]
[3, 8, 16]
[3, 10, 14]
[3, 10, 18]
[3, 12, 14]
[3, 12, 20]
[3, 14, 22]
[3, 16, 18]
[3, 16, 20]
[3, 18, 22]
[3, 20, 22]
[4, 16, 17]
[4, 16, 18]
[4, 16, 20]
[4, 17, 19]
[4, 17, 21]
[4, 18, 19]
[4, 18, 22]
[4, 19, 23]
[4, 20, 21]
[4, 20, 22]
[4, 21, 23]
[4, 22, 23]
[5, 12, 13]
[5, 12, 14]
[5, 12, 20]
[5, 13, 15]
[5, 13, 21]
[5, 14, 15]
[5, 14, 22]
[5, 15, 23]
[5, 20, 21]
[5, 20, 22]
[5, 21, 23]
[5, 22, 23]
[6, 10, 11]
[6, 10, 14]
[6, 10, 18]
[6, 11, 15]
[6, 11, 19]
[6, 14, 15]
[6, 14, 22]
[6, 15, 23]
[6, 18, 19]
[6, 18, 22]
[6, 19, 23]
[6, 22, 23]
[7, 9, 11]
[7, 9, 13]
[7, 9, 17]
[7, 11, 15]
[7, 11, 19]
[7, 13, 15]
[7, 13, 21]
[7, 15, 23]
[7, 17, 19]
[7, 17, 21]
[7, 19, 23]
[7, 21, 23]
cells
[0, 1, 8, 9, 10, 11]
[1, 4, 16, 17, 18, 19]
[0, 5, 12, 13, 14, 15]
[4, 5, 20, 21, 22, 23]
[0, 2, 8, 9, 12, 13]
[2, 4, 16, 17, 20, 21]
[0, 6, 10, 11, 14, 15]
[4, 6, 18, 19, 22, 23]
[0, 3, 8, 10, 12, 14]
[3, 4, 16, 18, 20, 22]
[0, 7, 9, 11, 13, 15]
[4, 7, 17, 19, 21, 23]
[1, 2, 8, 9, 16, 17]
[2, 5, 12, 13, 20, 21]
[1, 6, 10, 11, 18, 19]
[5, 6, 14, 15, 22, 23]
[1, 3, 8, 10, 16, 18]
[3, 5, 12, 14, 20, 22]
[1, 7, 9, 11, 17, 19]
[5, 7, 13, 15, 21, 23]
[2, 3, 8, 12, 16, 20]
[3, 6, 10, 14, 18, 22]
[2, 7, 9, 13, 17, 21]
[6, 7, 11, 15, 19, 23]

1
Вау, это потрясающий ответ !! Я очень удивлен, что вы смогли сделать это в ~ 200 строк. Я управлял кубом, тетраэдром, 600-клеткой и несколькими другими, и они выглядели хорошо. Трудно проверить вывод, так как его так много; выходной сигнал довольно легко длится дольше, чем программа, но я верю вашему слову. Я собираюсь попытаться загрузить это в openGL и просмотреть платонические тела, которые должны быть простыми, так как все лица перечислены. Я думаю, что добавить тесселяции в плоском пространстве было бы легко, и я мог бы попробовать это тоже.
Тони Рут

@TonyRuth ключ находил лучший алгоритм. Меньше строк = меньше места для ошибок. Первым делом я проверил, что существует, кроме трех бесконечных семейств, и тогда я решил ответить. Комментарии Уилла Яги были находкой (я думал об этом типе решения, так как метод Википедии выглядел жестко), поэтому нецелые координаты сведены к минимуму. Я хотел сделать это до истечения срока действия щедрости, поэтому проверка не была достаточно тщательной, и я не планировал их. Сообщите мне о любых ошибках - я исправил 24cell несколько часов назад.
Уровень Река St

Вершины лица @TonyRuth расположены в произвольном порядке (они не вращаются вокруг лица по часовой стрелке или что-либо еще). Для более высоких размеров нет стандартного заказа. Гиперкубы имеют грани, перечисленные в числовом порядке, поэтому 2-я и 3-я вершины расположены по диагонали (вам нужно поменять местами 1-ю и 2-ю или 3-ю и 4-ю вершины, если вы хотите, чтобы они были по часовой стрелке / против часовой стрелки.) Додекаэдр должен иметь грани в по часовой стрелке / против часовой стрелки, но ячейка 120 будет иметь грани вершин в любом порядке.
Уровень Река St
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.