Регулярний перекладач політопів Schläfli Convex


15

Фон

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

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

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

Правильний чотиривимірний багатогранник з 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-політопі (n-3).

У звичайного багатогранника є регулярна вершинна фігура. Фігура вершини звичайного багатогранника {p, q, r, ... y, z} дорівнює {q, r, ... y, z}.

Регулярні багатогранники можуть мати зіркові елементи багатокутника, як пентаграма, із символом {5/2}, представленими вершинами п’ятикутника, але з'єднаними по черзі.

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

Конкуренція

Ваша мета - створити програму, яка після передачі символу Schläfli поверне повний опис опуклого багатогранника. Це лише підмножина символів Шляфлі, але це найпростіший, я вважаю, що навіть без інших можливостей це буде дуже складним завданням, а багатогранники - це відправна точка для тесселяцій. Правила цього питання були розроблені з ідеєю, що цей результат є API, і я не зміг знайти будь-яку подібну програму в Інтернеті.

Ваша програма повинна виконати все наступне.

  • Програма повинна бути здатна генерувати будь-який регулярний опуклий політоп кінцевих розмірів. У двох вимірах сюди входять n-гони. У трьох вимірах - це платонові тверді речовини, у 4 виміри - це тессеракт, ортоплекс та деякі інші)
  • Програма повинна або (a) розмістити крапку за початком, або (b) переконатися, що середнє значення всіх балів є початком. Орієнтація не має значення. Загальний розмір не має значення.
  • Програма повинна надати повний опис, що означає, що для 4-мірного об'єкта програма повертає / друкує вершини, краї, грані та багатогранники. Порядок повідомлень про них не має значення. Для багатогранників це інформація, яка вам знадобиться для надання об'єкта.

Вам не потрібно обробляти:

  • Tesselations
  • Гіперболічна геометрія
  • Дробові символи Шляфлі (не опуклі)
  • Вбудовані символи Schläfli (неоднорідні обшивки)

Якщо вас попросять зробити щось із цього, ви можете повернути помилку.

Приклад: Куб

Вхід:

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 V

E = 4/2 F

V - E + F = 2

Що дає E = 12, V = 8, F = 6.

Оцінка балів

Щоб зберегти питання на тему, це було переглянуто на Code Golf. Найкоротший код виграє.

Для цього питання було створено github


1
Гуглінг показує, що є лише 3 родини звичайних багатогранників, що виходять за рамки 4 розмірів: аналог куба, октаедра та тетраедра. Здається, було б простіше написати для цих сімей і жорстко зафіксувати решту (два 3d багатогранника, три 4d багатогранника і нескінченну сімейство 2d багатогранників.) Наскільки я бачу, це відповідає специфікації, але не може бути узагальненою. Це була би правдива відповідь? Можливо, можливо створити рекурсивний алгоритм для генерації топологічних графіків, що виходять за межі спектра, але вбивця з таким підходом навіть у межах специфікації обчислює координати.
Рівень річки Св.

Як ми можемо знати фактичні вершини, лише знаючи, що вони рівносторонні?
Меттью Рох

@SIGSEGV єдина вказана вимога - походження має відповідати або центру, або одній з точок. Це дає достатньо можливостей для обертання фігури за вашим бажанням. en.wikipedia.org/wiki/Simplex дає алгоритм для обчислення координат гіпертетраедронів (який, можливо, може бути розширений до ікосаедра та його 4d аналога, але робити це занадто багато для мене, звідси моє питання.) Гіперкуби та гіпероктаедри мають приємні цілі координати (і гіпертетраедра теж насправді, але часто лише в більших розмірах, ніж сама форма, яка є неохайною.)
рівень річки Св.

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

Я завершив голосування з цього питання, тому що це найпростіший виклик у стилі гармати , де виграє перша правильна відповідь. Зазвичай це не вважається дійсним критерієм виграшу. Я не знаю, як це було відкрито так довго, воно мало бути закритим.
Опублікувати Rock Garf Hunter

Відповіді:


2

Пітон

Ось рекурсивна програма без особливих випадків. Ігноруючи порожні рядки та коментарі, це менше 100 90 рядків, включаючи безоплатну перевірку формули Ейлера наприкінці. За винятком визначень спеціальних математичних функцій (які, можливо, можуть бути надані бібліотекою) та вводу-виводу, генерація політопів становить 50 рядків коду. І це навіть роблять зіркові багатогранники!

Вихідний політоп матиме довжину ребра 1 і буде знаходитись у канонічному положенні та орієнтації у такому значенні:

  • перша вершина - це походження,
  • перший край лежить уздовж осі + x,
  • перше обличчя знаходиться в + y півплощині площини xy,
  • перша 3-комірка знаходиться в напівпросторі + z простору xyz тощо.

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

Немає перевірки на наявність недійсного символу шлафлі; якщо ви дасте його, програма, ймовірно, піде з рейок (нескінченна петля, переповнення стека або просто сміття).

Якщо ви попросите нескінченну площинну плитку, наприклад {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-кубів, членом яких є куб. Їх символи шлафі мають форму{4,3,...,3,3}

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

Є ще одне нескінченне сімейство правильних багатогранників, символу {m}, двох двовимірних багатокутників, які можуть мати будь-яку кількість ребер m.

Окрім цього, існує лише п’ять інших особливих випадків регулярного багатогранника: 3-мірний ікосаедр {3,5}та додекаедр {5,3}; їх 4-мірні аналоги 600-клітинні {3,3,5}та 120-клітинні {5,3,3}; і ще один 4-мірний багатогранник, 24-елементний {3,4,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 (4d симплекс)

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

Пояснення сім'ї тетраедрів - координати

n-мірний симплекс / гіпертетраедр має n + 1 балів. Дуже легко надати вершини n-мірного симплексу в n + 1 вимірах.

Таким чином (1,0,0),(0,1,0),(0,0,1)описується 2d трикутник, вбудований у 3 виміри, та (1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1)описується 3d тетраедр, вбудований у 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]}

Пояснення сімейства кубів та октаедрів - координати

У n-куба є 2**nвершини, кожна з яких представлена ​​масивом n 1s і -1s (всі можливості дозволені.) Ітераціюємо через індекси 0до 2**n-1списку всіх вершин і будуємо масив для кожної вершини шляхом ітерації через біти індекс та додавання -1або 1до масиву (найменш значущий біт до найбільш значущого біта.) Таким чином, Бінарний 1101стає 4d точкою [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-куба є 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. Знову ж таки, нам доведеться видалити саму гіперкуб і всі елементи з менш ніж двома вершинами, перш ніж повернутися з функції.

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

Отримані масиви вершин для кожної точки зберігаються у великому масиві, як і для інших випадків, і перед поверненням ми повинні видалити будь-які елементи, що мають менше 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

Код для генерації таблиць для 3-х спеціальних випадків

Була використана орієнтація на ікосаедр / додекаедр, орієнтовану на вісь п’ятикратної симетрії, паралельну останньому розміру, як це робиться для найбільш послідовного маркування деталей. Нумерація вершин і граней ікосаедра наведена на діаграмі в коментарях до коду та зворотна для додекаедра.

Відповідно до https://en.wikipedia.org/wiki/Regular_icosahedron широта 10 неполярних вершин ікосаедра становить +/- arctan (1/2) Координати перших 10 вершин ікосаедра обчислюються від це на двох колах радіусом 2 на відстані +/- 2 від площини xy. Це робить загальний радіус кругообігу 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]]

Код для генерації таблиць для 4d особливих випадків

Це трохи хак. Цей код займає кілька секунд. Було б краще зберегти вихід у файл і завантажити його у міру необхідності.

Список 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 і переглянути платонічні тверді речовини, які мають бути прямими, оскільки перераховані всі грані. Я думаю, що додавати tesselations у плоскій просторі було б легко, і я можу спробувати це також.
Тоні Рут

@TonyRuth ключовим був пошук найкращого алгоритму. Менше рядків = менше місця для помилок. Перше, що я зробив, це перевірити, що існує крім трьох нескінченних розмірних сімей, і саме тоді я вирішив відповісти. Чи будуть коментарі Джагі були знахідкою (я думав про такий тип рішення, як метод wikipedia виглядав важко), тому нецілі координати зводяться до мінімуму. Я хотів це зробити до того, як термін щедрості закінчився, тому перевірка не була досить ретельною, і я не будував їх. Повідомте мене про будь-які помилки - я виправив 24клітинку кілька годин тому.
Рівень р. Св.

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