tzurshubi commited on
Commit
e6c805a
·
verified ·
1 Parent(s): c54e8fa

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +135 -18
app.py CHANGED
@@ -193,6 +193,92 @@ def classify_path(path, d: int):
193
  return "snake", True
194
 
195
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  def edge_dimension(a: int, b: int) -> int | None:
197
  """
198
  Return the dimension index of the edge (a,b) if they are adjacent,
@@ -1255,14 +1341,13 @@ def render(d, show_labels_vals, path, mark_vals, mark_dist_vals, subsel, switchs
1255
  )
1256
  def path_info(d, path):
1257
  path = path or []
 
1258
 
1259
  if not path:
1260
  return html.Span("Path: (empty)")
1261
 
1262
- # Vertex list
1263
  path_str = ", ".join(str(v) for v in path)
1264
 
1265
- # Classification
1266
  label, valid = classify_path(path, d)
1267
  color = {
1268
  "snake": "green",
@@ -1276,25 +1361,57 @@ def path_info(d, path):
1276
  dims = []
1277
  for i in range(len(path) - 1):
1278
  dim = edge_dimension(path[i], path[i + 1])
1279
- if dim is not None:
1280
- dims.append(dim)
1281
- else:
1282
- # non adjacent step, keep something explicit
1283
- dims.append("?")
1284
-
1285
  dims_str = ", ".join(str(x) for x in dims) if dims else "(none)"
1286
 
1287
- return html.Div([
1288
- html.Div([
1289
- html.Span(f"Path: {path_str} "),
1290
- html.Span(f"[{label}]", style={"color": color, "fontWeight": "bold"}),
1291
- ]),
1292
- html.Div([
1293
- html.Span("Dimensions: "),
1294
- html.Span(dims_str, style={"fontFamily": "monospace"}),
1295
- ]),
1296
- ])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1297
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1298
 
1299
  @app.callback(
1300
  Output("path_bits", "children"),
 
193
  return "snake", True
194
 
195
 
196
+ def snake_violations(path: List[int], d: int):
197
+ """
198
+ Return a dict with violating pairs for the snake/coil inducedness + adjacency rules.
199
+
200
+ We follow the same conventions as classify_path:
201
+ - If path is closed (path[0]==path[-1]), work with cycle = path[:-1]
202
+ - Consecutive edges must be adjacent
203
+ - Inducedness forbids any chord between non-consecutive vertices,
204
+ except we always allow the endpoints pair (0, n-1)
205
+ (closing edge for coil, or would-be closing edge for almost coil)
206
+ """
207
+ if not path or len(path) <= 1:
208
+ return {
209
+ "dup_vertices": [],
210
+ "non_adjacent_steps": [],
211
+ "chords": [],
212
+ "bad_closing_edge": [],
213
+ }
214
+
215
+ is_closed = (path[0] == path[-1])
216
+ cycle = path[:-1] if is_closed else path[:]
217
+ n = len(cycle)
218
+
219
+ dup_vertices = []
220
+ non_adjacent_steps = []
221
+ chords = []
222
+ bad_closing_edge = []
223
+
224
+ # 1) duplicates (report as pairs of positions, but return as vertex pairs for display)
225
+ # We'll report every duplicate occurrence pair (v, v).
226
+ pos = {}
227
+ for i, v in enumerate(cycle):
228
+ if v in pos:
229
+ dup_vertices.append((v, v))
230
+ else:
231
+ pos[v] = i
232
+
233
+ # 2) consecutive non-adjacent steps (pairs of vertices)
234
+ for i in range(n - 1):
235
+ a, b = cycle[i], cycle[i + 1]
236
+ if hamming_dist(a, b) != 1:
237
+ non_adjacent_steps.append((a, b))
238
+
239
+ # closing edge required only if explicitly closed
240
+ if n >= 2:
241
+ closing_adjacent = (hamming_dist(cycle[0], cycle[-1]) == 1)
242
+ if is_closed and not closing_adjacent:
243
+ bad_closing_edge.append((cycle[-1], cycle[0]))
244
+
245
+ # 3) inducedness chords: any non-consecutive adjacent pair, skipping (0,n-1)
246
+ # To avoid O(n^2) scans on long paths, we use bit-neighbor checks.
247
+ # For each vertex v, check all its d neighbors u; if u appears in the path,
248
+ # and the indices are not consecutive (modulo nothing; this is snake logic),
249
+ # then it's a chord (unless it's endpoints pair).
250
+ idx_of = {v: i for i, v in enumerate(cycle)}
251
+ for v, i in idx_of.items():
252
+ for bit in range(d):
253
+ u = v ^ (1 << bit)
254
+ j = idx_of.get(u)
255
+ if j is None:
256
+ continue
257
+
258
+ # skip actual path edges
259
+ if abs(i - j) == 1:
260
+ continue
261
+
262
+ # skip endpoints pair (0, n-1) always
263
+ if (i == 0 and j == n - 1) or (i == n - 1 and j == 0):
264
+ continue
265
+
266
+ # record chord once (as an unordered pair)
267
+ a, b = (v, u) if v < u else (u, v)
268
+ chords.append((a, b))
269
+
270
+ # de-duplicate chords list
271
+ chords = sorted(set(chords))
272
+
273
+ return {
274
+ "dup_vertices": dup_vertices,
275
+ "non_adjacent_steps": non_adjacent_steps,
276
+ "chords": chords,
277
+ "bad_closing_edge": bad_closing_edge,
278
+ }
279
+
280
+
281
+
282
  def edge_dimension(a: int, b: int) -> int | None:
283
  """
284
  Return the dimension index of the edge (a,b) if they are adjacent,
 
1341
  )
1342
  def path_info(d, path):
1343
  path = path or []
1344
+ d = int(d)
1345
 
1346
  if not path:
1347
  return html.Span("Path: (empty)")
1348
 
 
1349
  path_str = ", ".join(str(v) for v in path)
1350
 
 
1351
  label, valid = classify_path(path, d)
1352
  color = {
1353
  "snake": "green",
 
1361
  dims = []
1362
  for i in range(len(path) - 1):
1363
  dim = edge_dimension(path[i], path[i + 1])
1364
+ dims.append(dim if dim is not None else "?")
 
 
 
 
 
1365
  dims_str = ", ".join(str(x) for x in dims) if dims else "(none)"
1366
 
1367
+ extra = None
1368
+ if label == "not snake":
1369
+ viol = snake_violations(path, d)
1370
+
1371
+ pairs = []
1372
+ # Keep categories separate so it's interpretable
1373
+ if viol["dup_vertices"]:
1374
+ pairs.extend(viol["dup_vertices"])
1375
+ if viol["non_adjacent_steps"]:
1376
+ pairs.extend(viol["non_adjacent_steps"])
1377
+ if viol["bad_closing_edge"]:
1378
+ pairs.extend(viol["bad_closing_edge"])
1379
+ if viol["chords"]:
1380
+ pairs.extend(viol["chords"])
1381
+
1382
+ # Format: (a,b), (c,d), ...
1383
+ # If there are many, don’t explode the UI
1384
+ MAX_SHOW = 30
1385
+ shown = pairs[:MAX_SHOW]
1386
+ pairs_str = ", ".join(f"({a},{b})" for a, b in shown)
1387
+ if len(pairs) > MAX_SHOW:
1388
+ pairs_str += f", ... (+{len(pairs) - MAX_SHOW} more)"
1389
+
1390
+ extra = html.Div(
1391
+ [
1392
+ html.Span("Violations: ", style={"fontWeight": "bold"}),
1393
+ html.Span(pairs_str if pairs_str else "(none)", style={"fontFamily": "monospace"}),
1394
+ ],
1395
+ style={"marginTop": "4px"},
1396
+ )
1397
 
1398
+ return html.Div(
1399
+ [
1400
+ html.Div(
1401
+ [
1402
+ html.Span(f"Path: {path_str} "),
1403
+ html.Span(f"[{label}]", style={"color": color, "fontWeight": "bold"}),
1404
+ ]
1405
+ ),
1406
+ html.Div(
1407
+ [
1408
+ html.Span("Dimensions: "),
1409
+ html.Span(dims_str, style={"fontFamily": "monospace"}),
1410
+ ]
1411
+ ),
1412
+ extra if extra is not None else html.Span(),
1413
+ ]
1414
+ )
1415
 
1416
  @app.callback(
1417
  Output("path_bits", "children"),