File size: 3,364 Bytes
82a8f4b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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()