igriv's picture
Major reorganization and feature additions
d7d27f0
import argparse, numpy as np
from math import sqrt
from ideal_poly_volume_toolkit.geometry import lift_to_sphere_with_inf, ideal_poly_volume_via_hull_project_back, ideal_poly_volume_via_delaunay
def unit(V): V = np.asarray(V, float); return V/np.linalg.norm(V, axis=1, keepdims=True)
def rotate_vec_a_to_b(a, b):
a = a/np.linalg.norm(a); b = b/np.linalg.norm(b)
v = np.cross(a, b); c = np.dot(a, b)
if np.allclose(v, 0) and c > 0: return np.eye(3)
if np.allclose(v, 0) and c < 0:
axis = np.array([1,0,0]) if abs(a[0]) < 0.9 else np.array([0,1,0])
v = np.cross(a, axis); v = v/np.linalg.norm(v)
K = np.array([[0,-v[2],v[1]],[v[2],0,-v[0]],[-v[1],v[0],0]])
return np.eye(3)+2*K@K
s = np.linalg.norm(v); K = np.array([[0,-v[2],v[1]],[v[2],0,-v[0]],[-v[1],v[0],0]])
return np.eye(3)+K+K@K*((1-c)/(s*s))
def inverse_stereographic(X):
x,y,z = X[:,0], X[:,1], X[:,2]; denom = (1-z); return (x/denom) + 1j*(y/denom)
def vertices_platonic(name):
phi = (1+sqrt(5))/2
if name == 'tetrahedron':
V = np.array([[ 1, 1, 1],[-1,-1, 1],[-1, 1,-1],[ 1,-1,-1]], float)
elif name == 'octahedron':
V = np.array([[ 1,0,0],[-1,0,0],[0,1,0],[0,-1,0],[0,0,1],[0,0,-1]], float)
elif name == 'cube':
V = np.array([[ 1, 1, 1],[ 1, 1,-1],[ 1,-1, 1],[ 1,-1,-1],
[-1, 1, 1],[-1, 1,-1],[-1,-1, 1],[-1,-1,-1]], float)
elif name == 'icosahedron':
V = np.array([[0, 1, phi],[0,-1, phi],[0, 1,-phi],[0,-1,-phi],
[ 1, phi,0],[-1, phi,0],[ 1,-phi,0],[-1,-phi,0],
[ phi,0, 1],[ phi,0,-1],[-phi,0, 1],[-phi,0,-1]], float)
elif name == 'dodecahedron':
a = 1/phi; b = phi; V = []
for s1 in (-1,1):
for s2 in (-1,1):
for s3 in (-1,1):
V.append([s1, s2, s3])
for s1 in (-1,1):
for s2 in (-1,1):
V.append([0, s1*a, s2*b]); V.append([s1*a, s2*b, 0]); V.append([s1*b, 0, s2*a])
V = np.array(V, float)
else:
raise ValueError('unknown solid')
return unit(V)
def build_finite_complex(name):
V = vertices_platonic(name)
R = rotate_vec_a_to_b(V[0], np.array([0,0,1.0]))
Vr = (R @ V.T).T
Z = inverse_stereographic(Vr[1:]) # finite ones; ∞ is the dropped vertex
return Z
def main():
ap = argparse.ArgumentParser()
ap.add_argument('--solid', type=str, required=True,
choices=['tetrahedron','octahedron','cube','dodecahedron','icosahedron'])
ap.add_argument('--mode', type=str, default='exact', choices=['fast','exact'])
ap.add_argument('--dps', type=int, default=200)
args = ap.parse_args()
Z = build_finite_complex(args.solid)
mode = 'eval_only' if args.mode == 'exact' else 'fast'
vol_del = ideal_poly_volume_via_delaunay(Z, mode=mode, dps=args.dps, series_terms=96)
W = np.empty(len(Z)+1, dtype=np.complex128); W[0] = np.inf+0j; W[1:] = Z
vol_hull = ideal_poly_volume_via_hull_project_back(W, index_inf=0, mode=mode, dps=args.dps, series_terms=96)
print(f'[Regular ideal {args.solid}]')
print(f' volume via Delaunay: {vol_del:.12f}')
print(f' volume via hull→project: {vol_hull:.12f}')
print(f' |difference|: {abs(vol_del - vol_hull):.3e}')
if __name__ == '__main__':
main()