4 维多面体 120-cell 的可视化

这个脚本的原作者是 Madore ,原脚本是用 Perl 写的,我改写成了 Python,主要的改动就是用算法生成 120-cell 的所有顶点,而不是直接罗列一大堆坐标。

脚本会在当且目录下生成若干 .pov 文件,然后用 Povray 渲染即可。

原理就是利用的 Gnomonic Projection,对一个 4 维空间中的点,从原点将(4维空间中的)球面的北半球投影到与北极相切的(3 维)平面上,将南半球投影到与南极相切的(3 维)平面上。白色的柱子表示北半球的顶点投影的效果,红色的细线表示南半球上的顶点投影后的效果。

 

 

Python 代码如下:

  1 #coding=utf-8
  2 
  3 
  4 #------------
  5 #程序会在当且目录下生成若干 .pov (default=1000)文件, 渲染命令为
  6 #for i in `seq 0 999`; do povray +I `printf frame%03d.pov $i` render.ini;done
  7 
  8 #这里 render.ini 如下:
  9 #Width = 640
 10 #Height = 480    
 11 #Display = off
 12 #Pause_when_Done = off
 13 #Bounding_Threshold = 3
 14 #Test_Abort=On
 15 #Test_Abort_Count=100
 16 #Quality=11
 17 #Antialias_Depth=3
 18 #Antialias=On
 19 #Antialias_Threshold=0.1
 20 #Jitter_Amount=0.5
 21 #Jitter=On
 22 #-------------
 23 
 24 
 25 import numpy as np
 26 from numpy import pi, sin, cos
 27 import itertools
 28 
 29 SQRT5 = np.sqrt(5)
 30 PHI = (1+SQRT5)/2
 31 
 32 
 33 num_frames = 1000
 34 user_trans = True
 35 
 36 
 37 def negate(vector, index):
 38     u = list(vector)
 39     u[index] = -u[index]
 40     return tuple(u)
 41 
 42 def plus_minus(vector):
 43     pool = set([vector])
 44     for i in range(len(vector)):
 45         new_set = set([negate(vector,i) for vector in pool])
 46         pool = pool.union(new_set)
 47     return pool
 48 
 49 def perm_parity(vector, parity):
 50     u = list(vector)
 51     if len(vector) == 2:
 52         if parity == 0:
 53             return set([vector])
 54         else:
 55             swap = (vector[1],vector[0])
 56             return set([swap])
 57 
 58     pool = set()
 59     for index, value in enumerate(u):
 60         rest = tuple(u[:index]+u[index+1:])
 61         rest_set = perm_parity(rest, (index+parity)%2)
 62         for rest_tuple in rest_set:
 63             pool.add(tuple([value]+list(rest_tuple)))
 64     return pool
 65 
 66 
 67 def even_perm(vector):
 68     return perm_parity(vector,0)
 69 
 70 def plus_minus_even_perm(vector):
 71     pool = plus_minus(vector)
 72     new_pool = set()
 73     for v in pool:
 74         for pv in even_perm(v):
 75             new_pool.add(pv)
 76     return new_pool
 77 
 78 def plus_minus_all_perm(vector):
 79     pool = plus_minus(vector)
 80     new_pool = set()
 81     for v in pool:
 82         for pv in itertools.permutations(v):
 83             new_pool.add(pv)
 84     return new_pool
 85 
 86 vertices = []
 87 
 88 vertices += plus_minus_all_perm((2,2,0,0))
 89 vertices += plus_minus_all_perm((1,1,1,SQRT5))
 90 vertices += plus_minus_all_perm((1/PHI**2,PHI,PHI,PHI))
 91 vertices += plus_minus_all_perm((1/PHI,1/PHI,1/PHI,PHI**2))
 92 
 93 vertices += plus_minus_even_perm((0,1/PHI**2,1,PHI**2))
 94 vertices += plus_minus_even_perm((0,1/PHI,PHI,SQRT5))
 95 vertices += plus_minus_even_perm((1/PHI,1,PHI,2))
 96 vertices = np.array(vertices)
 97 edge_length = 3 - SQRT5
 98 
 99 edges = []
100 for i in xrange(600):
101     for j in xrange(i+1,600):
102         u = vertices[i]
103         v = vertices[j]
104         dist = np.linalg.norm(u-v)
105         if abs(dist-edge_length) < 1e-8:
106             edges.append([i,j])
107 
108 print('there are %d vertices, %d edges of the polytope'%(len(vertices),len(edges)))
109 
110 def gram_schimdt(M):
111     """
112     The QR decompostion. Q will be columnly orthogonal.
113     """
114     Q,R = np.linalg.qr(M)
115     return Q
116 
117 M = np.random.normal(size=(4,4))
118 M = gram_schimdt(M)
119 
120 for k_frame in range(num_frames):
121     T = np.eye(4)
122     
123     ctheta = cos(2*pi*k_frame/num_frames)
124     stheta = sin(2*pi*k_frame/num_frames)
125     T[[0,0,3,3],[0,3,0,3]] = [ctheta,stheta,-stheta,ctheta]
126     M_i = np.dot(M,T)
127     
128     rotated = np.zeros((len(vertices),4))  
129     for i in range(len(vertices)):
130         rotated[i] = np.dot(vertices[i],M_i)
131 
132     f = open('frame%03d.pov'%(k_frame),'w')
133     content = """camera { location <0,0,0> look_at <1,0,0> up <0,0,1> right <0,1.5,0> }
134 light_source { <0,0,0>, 1 }
135 #declare Std = texture { pigment { color <1,1,1> } finish { ambient .25 diffuse .4 phong .35 reflection .1 } }
136 #declare Far = texture { pigment { color <1,0.5,0.5> } finish { ambient .4 diffuse .0 } }\n"""
137     f.write(content)
138     for e in edges:
139         u = rotated[e[0]]
140         v = rotated[e[1]]
141         
142         p0 = u[0:3] / u[3]
143         p1 = v[0:3] / v[3]
144         dir1 = p1 - p0
145         dir1 /= np.linalg.norm(dir1)
146         
147         if user_trans == True:
148             q0 = u[0:3] / u[0]
149             q1 = v[0:3] / v[0]
150             if ((u[0]<0) != (v[0]<0)):
151                 qdir = np.array([1,q1[1]-q0[1],q1[2]-q0[2]])
152                 qdir /= np.linalg.norm(qdir[1:3])
153                 f.write('cylinder { <10000,%.5f,%.5f>, <10000,%.5f,%.5f>, 20 texture { Far } }\n'%(1.e4*q0[1],1.e4*q0[2],1.e4*(q0[1]-10*qdir[1]),1.e4*(q0[2]-10*qdir[2])))
154                 f.write('cylinder { <10000,%.5f,%.5f>, <10000,%.5f,%.5f>, 20 texture { Far } }\n'%(1.e4*q1[1],1.e4*q1[2],1.e4*(q1[1]+10*qdir[1]),1.e4*(q1[2]+10*qdir[2])))
155             else:
156                 f.write('cylinder { <10000,%.5f,%.5f>, <10000,%.5f,%.5f>, 20 texture { Far } }\n'%(1.e4*q0[1],1.e4*q0[2],1.e4*q1[1],1.e4*q1[2]))
157         
158         if (u[3] < 0 and v[3] < 0):
159             continue
160         if u[3] < 0:
161             p1 = p0 - 1.e4 * dir1
162         elif v[3] < 0:
163             p0 = p1 + 1.e4 * dir1
164         f.write('cylinder { <%.5f,%.5f,%.5f>, <%.5f,%.5f,%.5f>, 0.01 texture { Std } }\n'%( p0[0], p0[1], p0[2], p1[0], p1[1], p1[2]))
165         
166     f.close()
167         
120-cell

 

你可能感兴趣的:(4 维多面体 120-cell 的可视化)