tzurshubi commited on
Commit
d1daf18
·
verified ·
1 Parent(s): 5b9757d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +122 -100
app.py CHANGED
@@ -23,6 +23,31 @@ DEFAULTS = {
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.
@@ -540,17 +565,31 @@ def make_figure(d: int,
540
  # ---------- selected vertices for switch-dims (3 consecutive vertices) ----------
541
  switchsel = switchsel or {}
542
  switch_active = bool(switchsel.get("active"))
543
- sw_s = switchsel.get("start_idx")
544
- sw_e = switchsel.get("end_idx")
545
-
546
  switch_vertices = set()
547
- if switch_active and sw_s is not None and sw_e is not None and path:
 
 
548
  is_closed = (len(path) >= 2 and path[0] == path[-1])
549
  cycle = path[:-1] if is_closed else path[:]
550
  n = len(cycle)
 
 
551
  if n > 0:
552
- dist = (sw_e - sw_s) % n
553
- switch_vertices = {cycle[(sw_s + i) % n] for i in range(dist + 1)}
 
 
 
 
 
 
 
 
 
 
 
554
 
555
 
556
 
@@ -677,29 +716,22 @@ def make_figure(d: int,
677
  )
678
  )
679
 
680
- # ---------- highlight switch-dims selection edges ----------
681
- if switch_active and sw_s is not None and sw_e is not None and path:
682
- is_closed = (len(path) >= 2 and path[0] == path[-1])
683
- cycle = path[:-1] if is_closed else path[:]
684
- n = len(cycle)
685
- if n > 0:
686
- dist = (sw_e - sw_s) % n
687
- for k in range(dist):
688
- a = cycle[(sw_s + k) % n]
689
- b = cycle[(sw_s + k + 1) % n]
690
- x1, y1 = pos[a]
691
- x2, y2 = pos[b]
692
- edge_traces.append(
693
- go.Scatter(
694
- x=[x1, x2],
695
- y=[y1, y2],
696
- mode="lines",
697
- line=dict(width=max(2, edge_w * 2), color="#DC2626"),
698
- opacity=1.0,
699
- hoverinfo="skip",
700
- name="switch dims selection",
701
- )
702
  )
 
703
 
704
 
705
 
@@ -888,10 +920,9 @@ app.layout = html.Div(
888
 
889
  dcc.Store(
890
  id="switch_dims_store",
891
- data={"active": False, "start_idx": None, "end_idx": None}
892
  ),
893
 
894
-
895
  html.Div(), # empty cell just to keep the grid tidy
896
  ],
897
  ),
@@ -1141,11 +1172,9 @@ def update_path(clickData,
1141
  if trigger == "btn_switch_dims":
1142
  active = bool(switchsel.get("active"))
1143
  if not active:
1144
- # enter switch-dims selection mode
1145
- return path, subsel, {"active": True, "start_idx": None, "end_idx": None}
1146
  else:
1147
- # cancel switch-dims selection mode
1148
- return path, subsel, {"active": False, "start_idx": None, "end_idx": None}
1149
 
1150
 
1151
  # 8) Figure clicks
@@ -1154,82 +1183,75 @@ def update_path(clickData,
1154
  cd = p.get("customdata")
1155
 
1156
  # If we're in switch-dims mode, vertex clicks define exactly 3 consecutive vertices
 
1157
  if switchsel.get("active") and isinstance(cd, (int, float)):
1158
  vid = int(cd)
1159
-
1160
- # Work on a cycle if path is explicitly closed
1161
  is_closed = (len(path) >= 2 and path[0] == path[-1])
1162
  cycle = path[:-1] if is_closed else path[:]
1163
  n = len(cycle)
1164
-
1165
  if n < 3:
1166
- return path, subsel, {"active": False, "start_idx": None, "end_idx": None}
1167
-
1168
- # In a proper cycle representation, vertices are unique
1169
  try:
1170
- idx = cycle.index(vid)
1171
  except ValueError:
1172
  return path, subsel, switchsel
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1173
 
1174
- s = switchsel.get("start_idx")
1175
- e = switchsel.get("end_idx")
1176
-
1177
- # first vertex
1178
- if s is None:
1179
- return path, subsel, {"active": True, "start_idx": idx, "end_idx": idx}
1180
-
1181
- if e is None:
1182
- e = s
1183
-
1184
- # distance forward on the cycle (0,1,2,...)
1185
- dist = (e - s) % n
1186
-
1187
- # must extend consecutively forward (with wrap)
1188
- expected = (e + 1) % n
1189
- if idx == expected and dist < 2:
1190
- new_e = idx
1191
- new_dist = (new_e - s) % n
1192
-
1193
- # if we have 3 vertices selected: apply switch
1194
- if new_dist == 2:
1195
- x = cycle[s]
1196
- y = cycle[(s + 1) % n]
1197
- z = cycle[(s + 2) % n]
1198
-
1199
- a = edge_dimension(x, y)
1200
- b = edge_dimension(y, z)
1201
- if a is None or b is None:
1202
- return path, subsel, {"active": False, "start_idx": None, "end_idx": None}
1203
-
1204
- # new middle vertex to traverse b then a
1205
- y2 = x ^ (1 << b)
1206
-
1207
- # verify it actually reaches z by traversing a next
1208
- if edge_dimension(y2, z) != a:
1209
- return path, subsel, {"active": False, "start_idx": None, "end_idx": None}
1210
-
1211
- # avoid creating duplicates in the cycle (allow x,z)
1212
- if y2 in set(cycle) and y2 not in {x, z}:
1213
- return path, subsel, {"active": False, "start_idx": None, "end_idx": None}
1214
-
1215
- cycle2 = cycle[:]
1216
- cycle2[(s + 1) % n] = y2
1217
-
1218
- new_path = cycle2 + [cycle2[0]] if is_closed else cycle2
1219
- return new_path, subsel, {"active": False, "start_idx": None, "end_idx": None}
1220
-
1221
- return path, subsel, {"active": True, "start_idx": s, "end_idx": new_e}
1222
-
1223
- # allow shrinking by clicking current end
1224
- if idx == e:
1225
- if e != s:
1226
- new_e = (e - 1) % n
1227
- else:
1228
- new_e = s
1229
- return path, subsel, {"active": True, "start_idx": s, "end_idx": new_e}
1230
-
1231
- # otherwise ignore
1232
- return path, subsel, switchsel
1233
 
1234
 
1235
  # If we're in subpath selection mode, vertex clicks define (start_idx, end_idx)
 
23
 
24
  # ---------- Hypercube utilities ----------
25
 
26
+
27
+ def consecutive_triple_start(indices: list[int], n: int, is_closed: bool) -> int | None:
28
+ """
29
+ Given 3 indices, return the start index s such that {s,s+1,s+2} (mod n if closed)
30
+ equals the given set. If not possible, return None.
31
+ """
32
+ if len(indices) != 3 or n <= 0:
33
+ return None
34
+
35
+ S = set(indices)
36
+
37
+ if is_closed:
38
+ for s in range(n):
39
+ if {(s) % n, (s + 1) % n, (s + 2) % n} == S:
40
+ return s
41
+ return None
42
+ else:
43
+ # open path: must be literal consecutive indices
44
+ mn = min(S)
45
+ if S == {mn, mn + 1, mn + 2} and (mn + 2) < n:
46
+ return mn
47
+ return None
48
+
49
+
50
+
51
  def cycle_edge_dims(cycle: List[int], d: int) -> List[int] | None:
52
  """
53
  cycle is vertex list WITHOUT repeating start at end.
 
565
  # ---------- selected vertices for switch-dims (3 consecutive vertices) ----------
566
  switchsel = switchsel or {}
567
  switch_active = bool(switchsel.get("active"))
568
+ picked = switchsel.get("picked") or []
569
+
 
570
  switch_vertices = set()
571
+ switch_segment_edges = [] # list of (a,b) edges to highlight if we can infer the triple segment
572
+
573
+ if switch_active and path:
574
  is_closed = (len(path) >= 2 and path[0] == path[-1])
575
  cycle = path[:-1] if is_closed else path[:]
576
  n = len(cycle)
577
+
578
+ # picked are indices into cycle/path
579
  if n > 0:
580
+ for idx in picked:
581
+ if isinstance(idx, int):
582
+ switch_vertices.add(cycle[idx % n] if is_closed else cycle[idx])
583
+
584
+ # If exactly 3 picked and they form a consecutive triple, highlight the 2 edges of that triple
585
+ if len(picked) == 3:
586
+ s0 = consecutive_triple_start([int(i) for i in picked], n, is_closed)
587
+ if s0 is not None:
588
+ a = cycle[s0 % n] if is_closed else cycle[s0]
589
+ b = cycle[(s0 + 1) % n] if is_closed else cycle[s0 + 1]
590
+ c = cycle[(s0 + 2) % n] if is_closed else cycle[s0 + 2]
591
+ switch_segment_edges = [(a, b), (b, c)]
592
+
593
 
594
 
595
 
 
716
  )
717
  )
718
 
719
+ # ---------- highlight switch-dims selection edges (only when triple is valid) ----------
720
+ if switch_active and switch_segment_edges:
721
+ for (a, b) in switch_segment_edges:
722
+ x1, y1 = pos[a]
723
+ x2, y2 = pos[b]
724
+ edge_traces.append(
725
+ go.Scatter(
726
+ x=[x1, x2],
727
+ y=[y1, y2],
728
+ mode="lines",
729
+ line=dict(width=max(2, edge_w * 2), color="#DC2626"),
730
+ opacity=1.0,
731
+ hoverinfo="skip",
732
+ name="switch dims selection",
 
 
 
 
 
 
 
 
733
  )
734
+ )
735
 
736
 
737
 
 
920
 
921
  dcc.Store(
922
  id="switch_dims_store",
923
+ data={"active": False, "picked": []} # picked holds indices into cycle/path
924
  ),
925
 
 
926
  html.Div(), # empty cell just to keep the grid tidy
927
  ],
928
  ),
 
1172
  if trigger == "btn_switch_dims":
1173
  active = bool(switchsel.get("active"))
1174
  if not active:
1175
+ return path, subsel, {"active": True, "picked": []}
 
1176
  else:
1177
+ return path, subsel, {"active": False, "picked": []}
 
1178
 
1179
 
1180
  # 8) Figure clicks
 
1183
  cd = p.get("customdata")
1184
 
1185
  # If we're in switch-dims mode, vertex clicks define exactly 3 consecutive vertices
1186
+ # If we're in switch-dims mode, user can pick 3 consecutive vertices in ANY order
1187
  if switchsel.get("active") and isinstance(cd, (int, float)):
1188
  vid = int(cd)
1189
+
 
1190
  is_closed = (len(path) >= 2 and path[0] == path[-1])
1191
  cycle = path[:-1] if is_closed else path[:]
1192
  n = len(cycle)
1193
+
1194
  if n < 3:
1195
+ return path, subsel, {"active": False, "picked": []}
1196
+
1197
+ # find index of clicked vertex inside cycle/path
1198
  try:
1199
+ idx = cycle.index(vid) # vertices assumed unique in a valid snake/coil
1200
  except ValueError:
1201
  return path, subsel, switchsel
1202
+
1203
+ picked = list(switchsel.get("picked") or [])
1204
+
1205
+ # toggle behavior: click again removes it
1206
+ if idx in picked:
1207
+ picked = [i for i in picked if i != idx]
1208
+ return path, subsel, {"active": True, "picked": picked}
1209
+
1210
+ # add it (up to 3)
1211
+ if len(picked) >= 3:
1212
+ # start over from this click
1213
+ picked = [idx]
1214
+ return path, subsel, {"active": True, "picked": picked}
1215
+
1216
+ picked.append(idx)
1217
+
1218
+ # if not yet 3, just store
1219
+ if len(picked) < 3:
1220
+ return path, subsel, {"active": True, "picked": picked}
1221
+
1222
+ # now we have 3: check if they form a consecutive triple
1223
+ s0 = consecutive_triple_start([int(i) for i in picked], n, is_closed)
1224
+ if s0 is None:
1225
+ # not a valid triple, keep them so user can adjust (toggle)
1226
+ return path, subsel, {"active": True, "picked": picked}
1227
+
1228
+ # canonical triple order along the cycle/path
1229
+ x = cycle[s0 % n] if is_closed else cycle[s0]
1230
+ y = cycle[(s0 + 1) % n] if is_closed else cycle[s0 + 1]
1231
+ z = cycle[(s0 + 2) % n] if is_closed else cycle[s0 + 2]
1232
+
1233
+ a = edge_dimension(x, y)
1234
+ b = edge_dimension(y, z)
1235
+ if a is None or b is None:
1236
+ return path, subsel, {"active": False, "picked": []}
1237
+
1238
+ # new middle vertex to traverse b then a
1239
+ y2 = x ^ (1 << b)
1240
+
1241
+ # verify the new 2-edge path really reaches z via dimensions b then a
1242
+ if edge_dimension(y2, z) != a:
1243
+ return path, subsel, {"active": False, "picked": []}
1244
+
1245
+ # avoid duplicates in the cycle/path (allow x and z)
1246
+ if y2 in set(cycle) and y2 not in {x, z}:
1247
+ return path, subsel, {"active": False, "picked": []}
1248
+
1249
+ cycle2 = cycle[:]
1250
+ cycle2[(s0 + 1) % n if is_closed else (s0 + 1)] = y2
1251
+
1252
+ new_path = cycle2 + [cycle2[0]] if is_closed else cycle2
1253
+ return new_path, subsel, {"active": False, "picked": []}
1254
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1255
 
1256
 
1257
  # If we're in subpath selection mode, vertex clicks define (start_idx, end_idx)