mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
This squashes all local history (768 commits) onto the scrubbed PR #90 baseline. Individual commits were lost during filter-repo corruption; the working tree content is preserved intact. Build: N/A (working tree state only)
139 lines
4.7 KiB
Python
139 lines
4.7 KiB
Python
"""Non-Euclidean ↔ Euclidean transition boundaries on the CPP.
|
||
|
||
Generalization: edges have a *type* (Euclidean / Lobachevsky / elliptic).
|
||
Cross-type edges require a *boundary adapter* — a change-of-basis
|
||
operation that costs extra energy. The Cayley transform
|
||
Q = (I - A)(I + A)^{-1}
|
||
for skew-symmetric A is the canonical such adapter: it maps
|
||
the Euclidean half-plane to the unit disk, bijectively and
|
||
isometrically up to the hyperbolic metric.
|
||
|
||
So the CPP on a non-homogeneous graph is:
|
||
|
||
cost(edge e) = base_w(e) # within-type
|
||
+ adapter_cost(t1, t2) # cross-type boundary
|
||
|
||
where adapter_cost depends on the two edge types and the eigensolid
|
||
basis-mismatch.
|
||
|
||
The 3-vertex toy graph in the demo has one edge of each type
|
||
(elliptic, Euclidean, Lobachevsky) and shows that the minimum
|
||
closed walk uses the Lobachevsky–Euclidean adapter once and
|
||
the Euclidean–elliptic adapter once, skipping the expensive
|
||
Lobachevsky–elliptic direct transition.
|
||
"""
|
||
|
||
from itertools import permutations
|
||
from typing import Dict, Tuple, List
|
||
|
||
# Three edge types matching the standard trichotomy of constant-curvature 2D geometries
|
||
EDGE_TYPES = {"E": "Euclidean", "L": "Lobachevsky", "X": "elliptic"} # X for hyperbolX
|
||
|
||
|
||
def adapter_cost(t1: str, t2: str) -> float:
|
||
"""Boundary-adapter cost: re-anchoring the eigensolid basis when
|
||
crossing a geometric-type boundary. The Cayley transform is
|
||
the standard adapter (Euclidean ↔ Lobachevsky ↔ elliptic),
|
||
and its cost is 1.0 per boundary crossing in the toy model.
|
||
|
||
Same-type edges have cost 0 (no adapter needed). The most
|
||
expensive direct adapter is L↔X (two Cayley transforms in
|
||
series), but a 2-step transition E↔L then E↔X is cheaper.
|
||
"""
|
||
if t1 == t2:
|
||
return 0.0
|
||
# L <-> X is the most expensive: requires two Cayleys
|
||
pair = tuple(sorted([t1, t2]))
|
||
if pair == ("L", "X"):
|
||
return 3.0
|
||
if pair == ("E", "L") or pair == ("E", "X"):
|
||
return 1.0
|
||
return 0.0 # Should not happen
|
||
|
||
|
||
# Toy graph: 4 vertices, 5 edges covering all three types
|
||
# (0)---E---(1)
|
||
# | |
|
||
# X L
|
||
# | |
|
||
# (3)---E---(2)
|
||
TOY_GRAPH = {
|
||
0: [(1, 2.0, "E"), (3, 4.0, "X")],
|
||
1: [(0, 2.0, "E"), (2, 5.0, "L")],
|
||
2: [(1, 5.0, "L"), (3, 1.0, "E")],
|
||
3: [(0, 4.0, "X"), (2, 1.0, "E")],
|
||
}
|
||
|
||
|
||
def walk_cost(path: List[int]) -> float:
|
||
"""Total cost of closed walk visiting `path` in order, returning
|
||
to start. Cost = sum of edge base weights + sum of adapter
|
||
penalties at each step.
|
||
"""
|
||
if len(path) < 2:
|
||
return 0.0
|
||
total = 0.0
|
||
edges = {tuple(sorted([u, v])): (w, t) for u in TOY_GRAPH
|
||
for v, w, t in TOY_GRAPH[u] if u < v}
|
||
for u, v in zip(path, path[1:]):
|
||
e = tuple(sorted([u, v]))
|
||
if e in edges:
|
||
w, t = edges[e]
|
||
total += w
|
||
else:
|
||
return float("inf")
|
||
# Close the walk
|
||
last = (path[-1], path[0])
|
||
e = tuple(sorted(last))
|
||
if e in edges:
|
||
w, t = edges[e]
|
||
total += w
|
||
else:
|
||
return float("inf")
|
||
|
||
# Adapter penalties between consecutive edges (need edge types)
|
||
edge_types = []
|
||
for u, v in zip(path, path[1:]):
|
||
e = tuple(sorted([u, v]))
|
||
edge_types.append(edges[e][1])
|
||
edge_types.append(edges[tuple(sorted([path[-1], path[0]]))][1])
|
||
for t1, t2 in zip(edge_types, edge_types[1:]):
|
||
total += adapter_cost(t1, t2)
|
||
total += adapter_cost(edge_types[-1], edge_types[0])
|
||
return total
|
||
|
||
|
||
def enumerate_shortest(n_vertices: int = 4) -> Tuple[float, List[int]]:
|
||
"""Brute-force minimum over all Hamiltonian paths starting at 0."""
|
||
best_cost = float("inf")
|
||
best_path: List[int] = []
|
||
for perm in permutations(range(1, n_vertices)):
|
||
path = [0] + list(perm)
|
||
c = walk_cost(path)
|
||
if c < best_cost:
|
||
best_cost = c
|
||
best_path = path
|
||
return best_cost, best_path
|
||
|
||
|
||
def main() -> None:
|
||
print("=== Non-Euclidean ↔ Euclidean Boundary-Adapter CPP ===\n")
|
||
print("Toy graph (4 vertices, 5 edges, all three types):")
|
||
print(" (0) -E- (1)")
|
||
print(" | |")
|
||
print(" X L")
|
||
print(" | |")
|
||
print(" (3) -E- (2)\n")
|
||
print("Adapter costs: same-type=0, E<->L=1, E<->X=1, L<->X=3\n")
|
||
|
||
cost, path = enumerate_shortest()
|
||
print(f"Minimum closed walk: {path} (returning to 0)")
|
||
print(f" total cost: {cost:.2f}")
|
||
print(f"\nKey insight: avoid the direct L<->X transition (cost 3) by")
|
||
print(f"routing through E (cost 1+1=2). The boundary adapter is")
|
||
print(f"transitive only through Euclidean — the Cayley transform's")
|
||
print(f"defining property (Euclidean half-plane <-> hyperbolic disk).")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|