Update app.py
Browse files
app.py
CHANGED
|
@@ -23,6 +23,62 @@ DEFAULTS = {
|
|
| 23 |
|
| 24 |
# ---------- Hypercube utilities ----------
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
def index_in_path(path: List[int], vid: int):
|
| 27 |
"""Return the index of vid in path (first occurrence), or None."""
|
| 28 |
try:
|
|
@@ -124,6 +180,9 @@ def classify_path(path, d: int):
|
|
| 124 |
|
| 125 |
if is_closed:
|
| 126 |
# distinct, consecutive adjacent, closed by an edge, no chords
|
|
|
|
|
|
|
|
|
|
| 127 |
return "coil", True
|
| 128 |
|
| 129 |
# Open path, induced
|
|
@@ -1022,6 +1081,7 @@ def path_info(d, path):
|
|
| 1022 |
color = {
|
| 1023 |
"snake": "green",
|
| 1024 |
"coil": "green",
|
|
|
|
| 1025 |
"almost coil": "green",
|
| 1026 |
"not snake": "red",
|
| 1027 |
}[label]
|
|
|
|
| 23 |
|
| 24 |
# ---------- Hypercube utilities ----------
|
| 25 |
|
| 26 |
+
def cycle_edge_dims(cycle: List[int], d: int) -> List[int] | None:
|
| 27 |
+
"""
|
| 28 |
+
cycle is vertex list WITHOUT repeating start at end.
|
| 29 |
+
Returns list of edge dimensions around the cycle (length n),
|
| 30 |
+
or None if any step is non-adjacent.
|
| 31 |
+
"""
|
| 32 |
+
n = len(cycle)
|
| 33 |
+
dims = []
|
| 34 |
+
for i in range(n):
|
| 35 |
+
a = cycle[i]
|
| 36 |
+
b = cycle[(i + 1) % n]
|
| 37 |
+
dim = edge_dimension(a, b)
|
| 38 |
+
if dim is None:
|
| 39 |
+
return None
|
| 40 |
+
dims.append(dim)
|
| 41 |
+
return dims
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def is_symmetric_coil(path: List[int], d: int, allow_reverse: bool = True) -> bool:
|
| 45 |
+
"""
|
| 46 |
+
A "doubled coil": valid coil whose second half repeats the structure of the first half.
|
| 47 |
+
We detect this via the cyclic edge-dimension sequence.
|
| 48 |
+
|
| 49 |
+
allow_reverse=True also accepts that the second half is the reverse traversal
|
| 50 |
+
of the first half (often happens depending on where you cut the cycle).
|
| 51 |
+
"""
|
| 52 |
+
if not path or len(path) < 4:
|
| 53 |
+
return False
|
| 54 |
+
if path[0] != path[-1]:
|
| 55 |
+
return False # must be explicitly closed
|
| 56 |
+
|
| 57 |
+
cycle = path[:-1]
|
| 58 |
+
n = len(cycle)
|
| 59 |
+
if n % 2 != 0:
|
| 60 |
+
return False
|
| 61 |
+
|
| 62 |
+
dims = cycle_edge_dims(cycle, d)
|
| 63 |
+
if dims is None:
|
| 64 |
+
return False
|
| 65 |
+
|
| 66 |
+
half = n // 2
|
| 67 |
+
first = dims[:half]
|
| 68 |
+
second = dims[half:]
|
| 69 |
+
|
| 70 |
+
if second == first:
|
| 71 |
+
return True
|
| 72 |
+
|
| 73 |
+
if allow_reverse:
|
| 74 |
+
# If the second half traverses the "same structure" but reversed,
|
| 75 |
+
# edge-dim sequence matches reversed order.
|
| 76 |
+
if second == list(reversed(first)):
|
| 77 |
+
return True
|
| 78 |
+
|
| 79 |
+
return False
|
| 80 |
+
|
| 81 |
+
|
| 82 |
def index_in_path(path: List[int], vid: int):
|
| 83 |
"""Return the index of vid in path (first occurrence), or None."""
|
| 84 |
try:
|
|
|
|
| 180 |
|
| 181 |
if is_closed:
|
| 182 |
# distinct, consecutive adjacent, closed by an edge, no chords
|
| 183 |
+
# If it is also a doubled coil, label it as symmetric coil
|
| 184 |
+
if is_symmetric_coil(path, d, allow_reverse=True):
|
| 185 |
+
return "symmetric coil", True
|
| 186 |
return "coil", True
|
| 187 |
|
| 188 |
# Open path, induced
|
|
|
|
| 1081 |
color = {
|
| 1082 |
"snake": "green",
|
| 1083 |
"coil": "green",
|
| 1084 |
+
"symmetric coil": "green",
|
| 1085 |
"almost coil": "green",
|
| 1086 |
"not snake": "red",
|
| 1087 |
}[label]
|