File size: 5,418 Bytes
e0ef700
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#!/usr/bin/env python3
"""Test rigid geometric construction from Rivin LP angles."""

import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))

import numpy as np
from scipy.spatial import Delaunay as scipy_Delaunay
from ideal_poly_volume_toolkit.plantri_interface import find_plantri_executable
from ideal_poly_volume_toolkit.planar_utils import extract_faces_from_planar_embedding
from ideal_poly_volume_toolkit.rivin_delaunay import (
    check_delaunay_realizability,
    compute_triangle_angle
)
from ideal_poly_volume_toolkit.geometric_realization import realize_from_angles_rigid
import subprocess


def get_octahedron():
    """Get the octahedron triangulation."""
    plantri = find_plantri_executable()
    args = [plantri, '-pc3', '-a', '6']
    result = subprocess.run(args, capture_output=True, text=True)

    triangulations = []
    for line in result.stdout.split('\n'):
        line = line.strip()
        if not line or line.startswith('>'):
            continue

        parts = line.split(maxsplit=1)
        if len(parts) != 2:
            continue

        n = int(parts[0])
        adj_str = parts[1]

        adj = {}
        for v_idx, neighbor_str in enumerate(adj_str.split(',')):
            neighbors = [ord(c) - ord('a') for c in neighbor_str]
            adj[v_idx] = neighbors

        closed_tri = extract_faces_from_planar_embedding(n, adj)
        planar_tri = [tri for tri in closed_tri if 0 not in tri]

        if planar_tri:
            triangulations.append(planar_tri)

    return triangulations[6]  # The octahedron


if __name__ == '__main__':
    triangles = get_octahedron()

    print("="*70)
    print("RIGID GEOMETRIC CONSTRUCTION TEST - OCTAHEDRON")
    print("="*70)
    print(f"\nTriangles: {triangles}")

    # Check strict realizability
    result = check_delaunay_realizability(triangles, verbose=False, strict=True)

    if not result['realizable']:
        print("Not realizable!")
        sys.exit(1)

    print(f"✓ Realizable (strict mode)")

    # Extract ALL angles from LP (in scaled units where π = 1)
    angles_scaled = result['angles']
    n_triangles = len(triangles)

    # Convert from scaled units to radians
    angles_radians = angles_scaled * np.pi

    print(f"\nLP solution gives ALL angles for ALL triangles:")
    for i, tri in enumerate(triangles):
        ang = angles_radians.reshape((n_triangles, 3))[i]
        print(f"  Triangle {tri}: {np.degrees(ang)} degrees (sum={np.degrees(ang.sum()):.1f}°)")

    # Rigid construction
    print(f"\n{'='*70}")
    print("RIGID CONSTRUCTION")
    print(f"{'='*70}\n")

    construction = realize_from_angles_rigid(
        triangles,
        angles_radians.reshape((n_triangles, 3)),
        verbose=True
    )

    if not construction['success']:
        print(f"\n✗ Construction failed: {construction['message']}")
        sys.exit(1)

    print(f"\n✓ Construction successful!")

    points = construction['points']
    vertex_list = construction['vertex_list']
    vertex_to_idx = {v: i for i, v in enumerate(vertex_list)}

    print(f"\nPoint positions:")
    for i, v in enumerate(vertex_list):
        print(f"  v{v}: ({points[i, 0]:10.6f}, {points[i, 1]:10.6f})")

    # Verify triangulation
    print(f"\n{'='*70}")
    print("VERIFICATION")
    print(f"{'='*70}")

    # Check Delaunay triangulation
    tri = scipy_Delaunay(points)
    realized_triangles = set()
    for simplex in tri.simplices:
        v0, v1, v2 = [vertex_list[i] for i in simplex]
        realized_triangles.add(tuple(sorted([v0, v1, v2])))

    expected_triangles = set(tuple(sorted(t)) for t in triangles)

    print(f"\nExpected triangles: {len(expected_triangles)}")
    print(f"Realized triangles: {len(realized_triangles)}")

    if expected_triangles == realized_triangles:
        print(f"✓ Triangulation matches perfectly!")
    else:
        print(f"✗ Triangulation mismatch")
        missing = expected_triangles - realized_triangles
        extra = realized_triangles - expected_triangles
        if missing:
            print(f"  Missing: {missing}")
        if extra:
            print(f"  Extra: {extra}")

    # Check angles
    print(f"\nAngle verification:")
    max_error = 0.0
    total_error_sq = 0.0

    for i, tri in enumerate(triangles):
        v0, v1, v2 = tri
        p0 = points[vertex_to_idx[v0]]
        p1 = points[vertex_to_idx[v1]]
        p2 = points[vertex_to_idx[v2]]

        angle0 = compute_triangle_angle(p0, p1, p2)
        angle1 = compute_triangle_angle(p1, p2, p0)
        angle2 = compute_triangle_angle(p2, p0, p1)

        actual = np.array([angle0, angle1, angle2])
        target = angles_radians.reshape((n_triangles, 3))[i]
        error = np.abs(target - actual)

        max_error = max(max_error, np.max(error))
        total_error_sq += np.sum(error**2)

        print(f"\n  Triangle {tri}:")
        print(f"    Target: {np.degrees(target)}")
        print(f"    Actual: {np.degrees(actual)}")
        print(f"    Error:  {np.degrees(error)} deg")

    rms_error = np.sqrt(total_error_sq / (n_triangles * 3))

    print(f"\n{'='*70}")
    print(f"SUMMARY")
    print(f"{'='*70}")
    print(f"Max angle error:  {np.degrees(max_error):.6f}°")
    print(f"RMS angle error:  {np.degrees(rms_error):.6f}°")
    print(f"Triangulation:    {'✓ MATCH' if expected_triangles == realized_triangles else '✗ MISMATCH'}")