Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
File size: 5,847 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 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 |
import argparse, numpy as np, torch, time
from ideal_poly_volume_toolkit.geometry import (
delaunay_triangulation_indices,
triangle_volume_from_points_torch,
)
def random_complex_points(K, rng):
"""Generate K random complex points"""
# Start with points roughly on unit circle but with some variation
r = 0.5 + 1.0 * rng.random(K)
theta = 2 * np.pi * rng.random(K)
return r * np.exp(1j * theta)
def build_Z_free(real_parts: torch.Tensor, imag_parts: torch.Tensor) -> torch.Tensor:
"""Build complex array from real and imaginary parts"""
Z = torch.empty(real_parts.numel() + 2, dtype=torch.complex128, device=real_parts.device)
Z[0] = 0 + 0j # Fixed at origin
Z[1] = 1 + 0j # Fixed at 1
Z[2:] = torch.complex(real_parts, imag_parts)
return Z
def torch_sum_volume(Z_t: torch.Tensor, idx, series_terms: int) -> torch.Tensor:
total = torch.zeros((), dtype=torch.float64, device=Z_t.device)
for (i, j, k) in idx:
total = total + triangle_volume_from_points_torch(
Z_t[i], Z_t[j], Z_t[k], series_terms=series_terms
)
return total
def main():
ap = argparse.ArgumentParser()
ap.add_argument('--seed', type=int, default=0)
ap.add_argument('--iters', type=int, default=200)
ap.add_argument('--series', type=int, default=96)
ap.add_argument('--print-every', type=int, default=20)
ap.add_argument('--device', type=str, default='cpu')
ap.add_argument('--lr', type=float, default=1.0)
args = ap.parse_args()
rng = np.random.default_rng(args.seed)
K = 5 # Five free points
# Initialize with random complex points
z_init = random_complex_points(K, rng)
real_parts = torch.tensor(z_init.real, dtype=torch.float64, device=args.device, requires_grad=True)
imag_parts = torch.tensor(z_init.imag, dtype=torch.float64, device=args.device, requires_grad=True)
print(f"Initial points (besides 0 and 1):")
for i in range(K):
print(f" z[{i+2}] = {real_parts[i].item():.4f} + {imag_parts[i].item():.4f}i")
# Use LBFGS optimizer on both real and imaginary parts
opt = torch.optim.LBFGS([real_parts, imag_parts], lr=args.lr, max_iter=20, line_search_fn='strong_wolfe')
history = []
triangulation_changes = 0
prev_triangulation = None
t0 = time.time()
for it in range(1, args.iters + 1):
# Rebuild Delaunay triangulation
with torch.no_grad():
Z_np = build_Z_free(real_parts, imag_parts).detach().cpu().numpy()
idx = delaunay_triangulation_indices(Z_np)
# Check if triangulation changed
if prev_triangulation is not None:
if idx.shape != prev_triangulation.shape or not np.array_equal(idx, prev_triangulation):
triangulation_changes += 1
if it <= 10 or it % args.print_every == 0:
print(f" [Triangulation changed at iteration {it}]")
prev_triangulation = idx.copy()
# Closure for LBFGS
def closure():
opt.zero_grad(set_to_none=True)
Z_t = build_Z_free(real_parts, imag_parts)
total = torch_sum_volume(Z_t, idx, args.series)
loss = -total # maximize volume
loss.backward()
# Clip gradients to prevent instability
torch.nn.utils.clip_grad_norm_([real_parts, imag_parts], max_norm=10.0)
return loss
opt.step(closure)
# Log progress
with torch.no_grad():
Z_post = build_Z_free(real_parts, imag_parts)
val_post = torch_sum_volume(Z_post, idx, args.series)
history.append(float(val_post.item()))
if it % args.print_every == 0 or it in (1, args.iters):
print(f'\n[{it:03d}] volume ~ {history[-1]:.10f} (tris={idx.shape[0]})')
# Print current positions
for i in range(K):
z = complex(real_parts[i].item(), imag_parts[i].item())
r, theta = abs(z), np.angle(z) * 180/np.pi
print(f' z[{i+2}] = {z.real:+.4f} {z.imag:+.4f}i (r={r:.4f}, θ={theta:+.1f}°)')
t1 = time.time()
# Final exact evaluation
with torch.no_grad():
Zf = build_Z_free(real_parts, imag_parts).detach().cpu().numpy()
from ideal_poly_volume_toolkit.geometry import ideal_poly_volume_via_delaunay
vol_exact = ideal_poly_volume_via_delaunay(Zf, mode='eval_only', dps=250)
print('\n=== Optimization (5 free points) done ===')
print(f'iters={args.iters}, time={t1-t0:.2f}s')
print(f'triangulation changes: {triangulation_changes}')
print(f'initial volume ~ {history[0] if history else 0:.10f}')
print(f'final fast volume ~ {history[-1]:.10f}')
print(f'final exact volume {vol_exact:.12f}')
print('\nFinal configuration:')
print(' z[0] = 0.0000 + 0.0000i (fixed)')
print(' z[1] = 1.0000 + 0.0000i (fixed)')
for i in range(K):
z = complex(real_parts[i].item(), imag_parts[i].item())
r, theta = abs(z), np.angle(z) * 180/np.pi
print(f' z[{i+2}] = {z.real:+.4f} {z.imag:+.4f}i (r={r:.4f}, θ={theta:+.1f}°)')
print('\nExpected volumes:')
print(' Regular tetrahedron: ~1.0149')
print(' Regular cube: ~5.0747 (5 × tetrahedron)')
print(f' Our result: {vol_exact:.4f}')
# Check distances between points
print('\nPairwise distances:')
Z_final = build_Z_free(real_parts, imag_parts).detach().numpy()
for i in range(len(Z_final)):
for j in range(i+1, len(Z_final)):
dist = abs(Z_final[i] - Z_final[j])
if dist > 0.01: # Skip very small distances
print(f' |z[{i}] - z[{j}]| = {dist:.4f}')
if __name__ == '__main__':
main() |