Move root experiment scripts into their papers' experiments/ folders
Relocate the standalone Python scripts from the repo root into the
experiments/ folder of the paper each one belongs to:
plane_depth_sequencing/experiments/
plane_depth_sequencing.py, draw_quad_sequence.py,
draw_quad_sequence_diagram.py, extract_sequence.py,
plane_depth_sequencing_figure.py, quad_sequence_coloring_check.py
colored_edge_flip_classes/experiments/ colored_edge_flip_class_survey.py
colored_pentagon_contractions/experiments/ colored_pentagon_contractions.py
plane_diamond_coloring/experiments/ plane_diamond_coloring.py
Each file that imports lib.* (still in the repo root) or the sibling
plane_depth_sequencing module gets a sys.path shim that prepends the
repo root (computed three levels up) and, where needed, its own dir.
Imports verified to resolve from a neutral working directory.
flip_symmetric_census.py is intentionally left in the root.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,8 @@ For each min-degree-5 maximal planar graph G of order n:
|
|||||||
If for some (H, phi) a graph isomorphic to G is reached, G is "found" and we
|
If for some (H, phi) a graph isomorphic to G is reached, G is "found" and we
|
||||||
move on. Otherwise we save G and stop.
|
move on. Otherwise we save G and stop.
|
||||||
"""
|
"""
|
||||||
|
import os, sys
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))) # repo root for `lib`
|
||||||
from typing import Iterator, Any, cast
|
from typing import Iterator, Any, cast
|
||||||
from sage.all import Graph, graphs # type: ignore[attr-defined] # pylint: disable=no-name-in-module
|
from sage.all import Graph, graphs # type: ignore[attr-defined] # pylint: disable=no-name-in-module
|
||||||
from sage.graphs.graph_coloring import all_graph_colorings # type: ignore[attr-defined] # pylint: disable=no-name-in-module
|
from sage.graphs.graph_coloring import all_graph_colorings # type: ignore[attr-defined] # pylint: disable=no-name-in-module
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
"""Example: colored pentagon reduction on a random 20-vertex triangulation."""
|
"""Example: colored pentagon reduction on a random 20-vertex triangulation."""
|
||||||
|
import os, sys
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))) # repo root for `lib`
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, cast, TypedDict, Literal
|
from typing import Any, cast, TypedDict, Literal
|
||||||
|
|||||||
@@ -0,0 +1,278 @@
|
|||||||
|
"""Draw a graph with quadrilateral sequence diagram for the plane depth sequencing paper.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
sage draw_quad_sequence.py --seed 42 --n 7 --output quad_sequence.png
|
||||||
|
"""
|
||||||
|
import os, sys
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))) # repo root for `lib`
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) # sibling experiment modules
|
||||||
|
from PIL import Image, ImageDraw
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
from sage.all import graphs, Graph # type: ignore
|
||||||
|
from sage.misc.randstate import set_random_seed # type: ignore
|
||||||
|
from plane_depth_sequencing import (
|
||||||
|
quadrilateral_sequencing,
|
||||||
|
_quad_vertices,
|
||||||
|
_level_edge_of_face,
|
||||||
|
_quad_type,
|
||||||
|
get_plane_depth_labelling,
|
||||||
|
)
|
||||||
|
from lib.tutte_embedding import tutte_embedding
|
||||||
|
|
||||||
|
|
||||||
|
def generate_sequence(seed: int, n: int) -> tuple[list[dict], dict, Graph, list, Graph]:
|
||||||
|
"""Generate a quadrilateral sequence and return (sequence_data, depth_labelling, original_graph, outer_cycle, deep_embedding_graph)."""
|
||||||
|
set_random_seed(seed)
|
||||||
|
g = graphs.RandomTriangulation(n)
|
||||||
|
g.is_planar(set_embedding=True)
|
||||||
|
embedding = g.get_embedding()
|
||||||
|
faces = g.faces(embedding)
|
||||||
|
outer_cycle = [u for u, _ in faces[0]]
|
||||||
|
|
||||||
|
result = quadrilateral_sequencing(g, outer_cycle)
|
||||||
|
sequence = result['sequence']
|
||||||
|
move_codes = result['move_codes']
|
||||||
|
depth_labelling = result['depth_labelling']
|
||||||
|
g_prime = result['deep_embedding']
|
||||||
|
|
||||||
|
move_names = {0: "AD", 1: "LA", 2: "J", 3: "RC"}
|
||||||
|
quad_type_colors = {
|
||||||
|
"deep_diamond": (178, 223, 219),
|
||||||
|
"shallow_diamond": (255, 224, 178),
|
||||||
|
"s_quad": (248, 187, 208),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Pass 1: collect each quad's level-edge endpoints, apexes, and full vertex set.
|
||||||
|
raw = []
|
||||||
|
quads = sequence[:6] # Limit to first 6 for readability
|
||||||
|
for i, quad in enumerate(quads):
|
||||||
|
quad_type = _quad_type(quad, depth_labelling)
|
||||||
|
f1, f2 = list(quad)
|
||||||
|
level_edge = _level_edge_of_face(f1, depth_labelling)
|
||||||
|
p, q = list(level_edge)
|
||||||
|
a = next(v for v in f1 if v not in level_edge)
|
||||||
|
b = next(v for v in f2 if v not in level_edge)
|
||||||
|
# Apex with the smaller depth goes on top, larger depth on bottom.
|
||||||
|
top, bottom = (a, b) if depth_labelling[a] <= depth_labelling[b] else (b, a)
|
||||||
|
move = move_names[move_codes[i - 1]] if i > 0 else ""
|
||||||
|
move_label = f"Q_{i+1}" if i == 0 else f"Q_{i+1}^{{{move}}}"
|
||||||
|
raw.append({
|
||||||
|
"level": (p, q),
|
||||||
|
"verts": {p, q, a, b},
|
||||||
|
"top": top,
|
||||||
|
"bottom": bottom,
|
||||||
|
"move": move_label,
|
||||||
|
"type": quad_type,
|
||||||
|
"color": quad_type_colors[quad_type],
|
||||||
|
})
|
||||||
|
|
||||||
|
# Pass 2: chain the diamonds. The left/right corners of a diamond are its level
|
||||||
|
# edge endpoints. A vertex shared with the PREVIOUS quad's level edge was that
|
||||||
|
# quad's right corner, so it goes on the LEFT here; a vertex shared with the NEXT
|
||||||
|
# quad's level edge goes on the RIGHT. Apex vertices are centered (top/bottom) and
|
||||||
|
# impose no left/right constraint, so only level-edge sharing matters.
|
||||||
|
sequence_data = []
|
||||||
|
for i, r in enumerate(raw):
|
||||||
|
p, q = r["level"]
|
||||||
|
prev_level = set(raw[i - 1]["level"]) if i > 0 else set()
|
||||||
|
next_level = set(raw[i + 1]["level"]) if i + 1 < len(raw) else set()
|
||||||
|
|
||||||
|
p_prev, q_prev = p in prev_level, q in prev_level
|
||||||
|
p_next, q_next = p in next_level, q in next_level
|
||||||
|
|
||||||
|
if p_prev != q_prev: # exactly one shared with prev -> left
|
||||||
|
left = p if p_prev else q
|
||||||
|
right = q if p_prev else p
|
||||||
|
elif p_next != q_next: # else exactly one shared with next -> right
|
||||||
|
right = p if p_next else q
|
||||||
|
left = q if p_next else p
|
||||||
|
else: # no chain constraint: deterministic fallback
|
||||||
|
right = max(p, q, key=str)
|
||||||
|
left = q if right == p else p
|
||||||
|
|
||||||
|
sequence_data.append({
|
||||||
|
"left": left,
|
||||||
|
"right": right,
|
||||||
|
"top": r["top"],
|
||||||
|
"bottom": r["bottom"],
|
||||||
|
"depth_left": depth_labelling[left],
|
||||||
|
"depth_right": depth_labelling[right],
|
||||||
|
"depth_top": depth_labelling[r["top"]],
|
||||||
|
"depth_bottom": depth_labelling[r["bottom"]],
|
||||||
|
"move": r["move"],
|
||||||
|
"type": r["type"],
|
||||||
|
"color": r["color"],
|
||||||
|
})
|
||||||
|
|
||||||
|
return sequence_data, depth_labelling, g, outer_cycle, g_prime
|
||||||
|
|
||||||
|
|
||||||
|
def draw_graph(g_prime: Graph, outer_cycle: list, depth_labelling: dict, width: int, height: int) -> Image.Image:
|
||||||
|
"""Draw the deep embedding graph with Tutte embedding and depth labels in a square."""
|
||||||
|
img = Image.new('RGB', (width, height), 'white')
|
||||||
|
draw = ImageDraw.Draw(img)
|
||||||
|
|
||||||
|
# Get Tutte embedding for the deep embedding graph
|
||||||
|
pos = tutte_embedding(g_prime, outer_cycle)
|
||||||
|
|
||||||
|
# Get data bounds
|
||||||
|
xs = [p[0] for p in pos.values()]
|
||||||
|
ys = [p[1] for p in pos.values()]
|
||||||
|
x_min, x_max = min(xs), max(xs)
|
||||||
|
y_min, y_max = min(ys), max(ys)
|
||||||
|
x_range = x_max - x_min if x_max > x_min else 1
|
||||||
|
y_range = y_max - y_min if y_max > y_min else 1
|
||||||
|
|
||||||
|
# Create a square region in the center of the image
|
||||||
|
margin = 30
|
||||||
|
max_size = min(width, height) - 2 * margin
|
||||||
|
graph_size = max_size
|
||||||
|
|
||||||
|
# Center the square graph horizontally
|
||||||
|
graph_x_min = (width - graph_size) // 2
|
||||||
|
graph_x_max = graph_x_min + graph_size
|
||||||
|
graph_y_min = margin
|
||||||
|
graph_y_max = margin + graph_size
|
||||||
|
|
||||||
|
def data_to_pixel(x: float, y: float) -> tuple[int, int]:
|
||||||
|
px = graph_x_min + (x - x_min) / x_range * (graph_x_max - graph_x_min)
|
||||||
|
py = graph_y_min + (y - y_min) / y_range * (graph_y_max - graph_y_min)
|
||||||
|
return int(px), int(py)
|
||||||
|
|
||||||
|
# Draw edges
|
||||||
|
for u, v in g_prime.edges(labels=False):
|
||||||
|
px1, py1 = data_to_pixel(pos[u][0], pos[u][1])
|
||||||
|
px2, py2 = data_to_pixel(pos[v][0], pos[v][1])
|
||||||
|
# Color level edges differently
|
||||||
|
if depth_labelling[u] == depth_labelling[v]:
|
||||||
|
draw.line([(px1, py1), (px2, py2)], fill=(150, 150, 150), width=1)
|
||||||
|
else:
|
||||||
|
draw.line([(px1, py1), (px2, py2)], fill=(50, 50, 50), width=1)
|
||||||
|
|
||||||
|
# Draw vertices
|
||||||
|
for v, (x, y) in pos.items():
|
||||||
|
px, py = data_to_pixel(x, y)
|
||||||
|
depth = depth_labelling[v]
|
||||||
|
if v in outer_cycle:
|
||||||
|
color = (25, 118, 210) # blue
|
||||||
|
else:
|
||||||
|
color = (0, 0, 0) # black
|
||||||
|
draw.ellipse([px-5, py-5, px+5, py+5], fill=color)
|
||||||
|
# Draw label with vertex ID and depth
|
||||||
|
label = f"{v}^{depth}"
|
||||||
|
draw.text((px-12, py-20), label, fill='black')
|
||||||
|
|
||||||
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
def draw_diagram(sequence_data: list[dict], depth_labelling: dict, output_path: str):
|
||||||
|
"""Draw and save the sequence diagram."""
|
||||||
|
width = 300 + len(sequence_data) * 400
|
||||||
|
height = 380
|
||||||
|
img = Image.new('RGB', (width, height), 'white')
|
||||||
|
draw = ImageDraw.Draw(img)
|
||||||
|
|
||||||
|
# Data-to-pixel coordinate mapping
|
||||||
|
data_x_min = -2.0
|
||||||
|
data_x_max = 0.5 + len(sequence_data) * 3.5
|
||||||
|
data_y_min = -1.1
|
||||||
|
data_y_max = 2.3
|
||||||
|
pixel_x_min = 80
|
||||||
|
pixel_x_max = width - 40
|
||||||
|
pixel_y_min = 40
|
||||||
|
pixel_y_max = height - 40
|
||||||
|
|
||||||
|
def data_to_pixel(x: float, y: float) -> tuple[int, int]:
|
||||||
|
px = pixel_x_min + (x - data_x_min) / (data_x_max - data_x_min) * (pixel_x_max - pixel_x_min)
|
||||||
|
py = pixel_y_max - (y - data_y_min) / (data_y_max - data_y_min) * (pixel_y_max - pixel_y_min)
|
||||||
|
return int(px), int(py)
|
||||||
|
|
||||||
|
step_width = 3.5
|
||||||
|
r = 0.45
|
||||||
|
|
||||||
|
# Depth -> data-y: smaller depth higher on screen (larger data-y).
|
||||||
|
all_depths = [
|
||||||
|
d for quad in sequence_data
|
||||||
|
for d in (quad["depth_left"], quad["depth_right"], quad["depth_top"], quad["depth_bottom"])
|
||||||
|
]
|
||||||
|
min_d, max_d = min(all_depths), max(all_depths)
|
||||||
|
y_of = lambda d: 1.0 - (d - min_d) * 0.5
|
||||||
|
|
||||||
|
for i, quad_data in enumerate(sequence_data):
|
||||||
|
x = i * step_width
|
||||||
|
color = quad_data["color"]
|
||||||
|
|
||||||
|
# Place the four corners. The level edge {left,right} is horizontal (both at
|
||||||
|
# the same depth); the apexes sit above (smaller depth) and below (larger).
|
||||||
|
p_left = data_to_pixel(x - r, y_of(quad_data["depth_left"]))
|
||||||
|
p_right = data_to_pixel(x + r, y_of(quad_data["depth_right"]))
|
||||||
|
p_top = data_to_pixel(x, y_of(quad_data["depth_top"]))
|
||||||
|
p_bottom = data_to_pixel(x, y_of(quad_data["depth_bottom"]))
|
||||||
|
|
||||||
|
# Polygon in cyclic order so the diamond is non-self-intersecting.
|
||||||
|
corners = [p_left, p_top, p_right, p_bottom]
|
||||||
|
draw.polygon(corners, fill=color, outline='black', width=2)
|
||||||
|
for px, py in corners:
|
||||||
|
draw.ellipse([px-5, py-5, px+5, py+5], fill='black')
|
||||||
|
|
||||||
|
# Vertex labels, offset away from the diamond body.
|
||||||
|
labels = [
|
||||||
|
(p_left, quad_data["left"], (-18, -6)),
|
||||||
|
(p_right, quad_data["right"], (10, -6)),
|
||||||
|
(p_top, quad_data["top"], (-4, -18)),
|
||||||
|
(p_bottom, quad_data["bottom"], (-4, 8)),
|
||||||
|
]
|
||||||
|
for (px, py), vid, (dx, dy) in labels:
|
||||||
|
draw.text((px + dx, py + dy), str(vid), fill='black')
|
||||||
|
|
||||||
|
# Quad move label at the centroid.
|
||||||
|
cx = sum(p[0] for p in corners) // 4
|
||||||
|
cy = sum(p[1] for p in corners) // 4
|
||||||
|
draw.text((cx-15, cy-6), quad_data["move"], fill='black')
|
||||||
|
|
||||||
|
# Depth gridline labels on the left of the first quad.
|
||||||
|
if i == 0:
|
||||||
|
for d in range(min_d, max_d + 1):
|
||||||
|
label_px, label_py = data_to_pixel(x - 1.2, y_of(d))
|
||||||
|
draw.text((label_px-28, label_py-6), f"d={d}", fill=(80, 80, 80))
|
||||||
|
|
||||||
|
if output_path:
|
||||||
|
img.save(output_path)
|
||||||
|
print(f"Saved diagram to {output_path}")
|
||||||
|
print(f"Sequence diagram size: {img.size}")
|
||||||
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--seed", type=int, default=42, help="Random seed for graph generation")
|
||||||
|
parser.add_argument("--n", type=int, default=7, help="Number of vertices in the random triangulation")
|
||||||
|
parser.add_argument("--output", type=str, default="quad_sequence_diagram.png", help="Output file path")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
print(f"Generating sequence for n={args.n} with seed={args.seed}")
|
||||||
|
sequence_data, depth_labelling, g, outer_cycle, g_prime = generate_sequence(args.seed, args.n)
|
||||||
|
print(f"Generated {len(sequence_data)} quadrilaterals")
|
||||||
|
print(f"Deep embedding has {g_prime.order()} vertices (original had {g.order()})")
|
||||||
|
|
||||||
|
# Draw deep embedding graph with depths
|
||||||
|
graph_width = 300 + len(sequence_data) * 400
|
||||||
|
graph_height = 500
|
||||||
|
graph_img = draw_graph(g_prime, outer_cycle, depth_labelling, graph_width, graph_height)
|
||||||
|
|
||||||
|
# Draw sequence
|
||||||
|
seq_width = graph_width
|
||||||
|
seq_height = 380
|
||||||
|
seq_img = draw_diagram(sequence_data, depth_labelling, None) # Return image instead of saving
|
||||||
|
|
||||||
|
# Combine images vertically
|
||||||
|
combined_width = graph_width
|
||||||
|
combined_height = graph_height + seq_height + 20 # 20px gap
|
||||||
|
combined_img = Image.new('RGB', (combined_width, combined_height), 'white')
|
||||||
|
combined_img.paste(graph_img, (0, 0))
|
||||||
|
combined_img.paste(seq_img, (0, graph_height + 20))
|
||||||
|
|
||||||
|
combined_img.save(args.output)
|
||||||
|
print(f"Saved diagram to {args.output}")
|
||||||
|
print(f"Combined image size: {combined_img.size}")
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
"""Draw a labeled quadrilateral sequence diagram for the plane depth sequencing paper."""
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import matplotlib.patches as patches
|
||||||
|
from matplotlib.patches import FancyArrowPatch
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
sequence_data = [
|
||||||
|
{"vertices": [-2, -1, 3, 7], "ordered": [-2, 7, -1, 3], "depths": [0, 1, 0, 1], "move": "Q_1", "type": "deep_diamond"},
|
||||||
|
{"vertices": [-1, 3, 4, 8], "ordered": [3, -1, 4, 8], "depths": [1, 0, 1, 2], "move": "Q_2^{AD}", "type": "s_quad"},
|
||||||
|
{"vertices": [2, 4, 5, 8], "ordered": [4, 2, 5, 8], "depths": [1, 0, 1, 2], "move": "Q_3^{AD}", "type": "s_quad"},
|
||||||
|
{"vertices": [-1, 2, 4, 6], "ordered": [4, 2, 6, -1], "depths": [1, 0, 1, 0], "move": "Q_4^{LA}", "type": "shallow_diamond"},
|
||||||
|
]
|
||||||
|
|
||||||
|
COLORS = {
|
||||||
|
"deep_diamond": "#B2DFDB",
|
||||||
|
"shallow_diamond": "#FFE0B2",
|
||||||
|
"s_quad": "#F8BBD0",
|
||||||
|
}
|
||||||
|
|
||||||
|
def draw_sequence_diagram(sequence_data: list[dict], output_path: str = "quad_sequence_diagram.png"):
|
||||||
|
"""Draw the full quadrilateral sequence diagram."""
|
||||||
|
fig, ax = plt.subplots(figsize=(16, 5.5))
|
||||||
|
|
||||||
|
step_width = 4.0
|
||||||
|
y_center = 1.0
|
||||||
|
x_positions = [i * step_width for i in range(len(sequence_data))]
|
||||||
|
|
||||||
|
# Store vertex positions for connecting matching vertices
|
||||||
|
quad_vertex_positions = []
|
||||||
|
|
||||||
|
for i, quad_data in enumerate(sequence_data):
|
||||||
|
x = x_positions[i]
|
||||||
|
quad_type = quad_data["type"]
|
||||||
|
ordered_verts = quad_data["ordered"]
|
||||||
|
depths = quad_data["depths"]
|
||||||
|
|
||||||
|
# Standard positions: top, right, bottom, left (or tweezers variant)
|
||||||
|
if quad_type == "s_quad":
|
||||||
|
# Tweezers shape: top, left-pinched-upper, right, left-pinched-lower
|
||||||
|
r = 0.45
|
||||||
|
pinch = 0.25
|
||||||
|
std_positions = [
|
||||||
|
(x, y_center + r), # e1: top
|
||||||
|
(x - r + pinch, y_center + r * 0.35), # a: left-upper
|
||||||
|
(x + r, y_center), # e2: right
|
||||||
|
(x - r + pinch, y_center - r * 0.35), # b: left-lower
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
# Diamond shape: top, right, bottom, left
|
||||||
|
r = 0.45
|
||||||
|
std_positions = [
|
||||||
|
(x, y_center + r), # e1: top
|
||||||
|
(x + r, y_center), # a: right
|
||||||
|
(x, y_center - r), # e2: bottom
|
||||||
|
(x - r, y_center), # b: left
|
||||||
|
]
|
||||||
|
|
||||||
|
# Draw polygon
|
||||||
|
polygon = patches.Polygon(
|
||||||
|
std_positions,
|
||||||
|
closed=True,
|
||||||
|
facecolor=COLORS.get(quad_type, "#CCCCCC"),
|
||||||
|
edgecolor="black",
|
||||||
|
linewidth=1.5,
|
||||||
|
alpha=0.75,
|
||||||
|
)
|
||||||
|
ax.add_patch(polygon)
|
||||||
|
|
||||||
|
# Draw vertices and labels
|
||||||
|
vertex_positions = {}
|
||||||
|
for vert_idx, (vertex_id, (vx, vy), depth) in enumerate(zip(ordered_verts, std_positions, depths)):
|
||||||
|
# Draw vertex point
|
||||||
|
ax.plot(vx, vy, "ko", markersize=7, zorder=5)
|
||||||
|
vertex_positions[vertex_id] = (vx, vy)
|
||||||
|
|
||||||
|
# Draw vertex label below
|
||||||
|
ax.text(vx, vy - 0.2, str(vertex_id), ha="center", va="top", fontsize=8, fontweight="bold",
|
||||||
|
bbox=dict(boxstyle="round,pad=0.15", facecolor="lightyellow", alpha=0.9, edgecolor="none"))
|
||||||
|
|
||||||
|
# Draw quad label in center
|
||||||
|
center_x = np.mean([p[0] for p in std_positions])
|
||||||
|
center_y = np.mean([p[1] for p in std_positions])
|
||||||
|
ax.text(center_x, center_y, f"${quad_data['move']}$", ha="center", va="center",
|
||||||
|
fontsize=11, fontweight="bold",
|
||||||
|
bbox=dict(boxstyle="round,pad=0.3", facecolor="white", edgecolor="black", alpha=0.85))
|
||||||
|
|
||||||
|
# Draw depth labels on the left (only for first quad)
|
||||||
|
if i == 0:
|
||||||
|
unique_depths = sorted(set(depths))
|
||||||
|
for d in unique_depths:
|
||||||
|
label_y = y_center + 0.5 - d * 0.5
|
||||||
|
ax.text(x - 1.0, label_y, f"d={d}", ha="right", va="center", fontsize=9, style="italic")
|
||||||
|
|
||||||
|
quad_vertex_positions.append(vertex_positions)
|
||||||
|
|
||||||
|
# Draw arrows connecting matching vertices to next quad
|
||||||
|
if i < len(sequence_data) - 1:
|
||||||
|
next_quad_verts = set(sequence_data[i + 1]["vertices"])
|
||||||
|
for vertex_id, (vx, vy) in vertex_positions.items():
|
||||||
|
if vertex_id in next_quad_verts:
|
||||||
|
next_quad_data = sequence_data[i + 1]
|
||||||
|
next_x = x_positions[i + 1]
|
||||||
|
next_idx = next_quad_data["ordered"].index(vertex_id)
|
||||||
|
|
||||||
|
if next_quad_data["type"] == "s_quad":
|
||||||
|
r = 0.45
|
||||||
|
pinch = 0.25
|
||||||
|
next_std_positions = [
|
||||||
|
(next_x, y_center + r),
|
||||||
|
(next_x - r + pinch, y_center + r * 0.35),
|
||||||
|
(next_x + r, y_center),
|
||||||
|
(next_x - r + pinch, y_center - r * 0.35),
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
r = 0.45
|
||||||
|
next_std_positions = [
|
||||||
|
(next_x, y_center + r),
|
||||||
|
(next_x + r, y_center),
|
||||||
|
(next_x, y_center - r),
|
||||||
|
(next_x - r, y_center),
|
||||||
|
]
|
||||||
|
|
||||||
|
next_vx, next_vy = next_std_positions[next_idx]
|
||||||
|
|
||||||
|
# Draw connecting arrow
|
||||||
|
arrow = FancyArrowPatch(
|
||||||
|
(vx, vy), (next_vx, next_vy),
|
||||||
|
arrowstyle="->", color="gray", linewidth=0.8, alpha=0.5, zorder=1,
|
||||||
|
connectionstyle="arc3,rad=0.15"
|
||||||
|
)
|
||||||
|
ax.add_patch(arrow)
|
||||||
|
|
||||||
|
# Set axis properties
|
||||||
|
ax.set_xlim(-1.8, x_positions[-1] + 1.2)
|
||||||
|
ax.set_ylim(-1.0, 2.2)
|
||||||
|
ax.set_aspect("equal")
|
||||||
|
ax.axis("off")
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(output_path, dpi=150, bbox_inches="tight")
|
||||||
|
print(f"Saved diagram to {output_path}")
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
draw_sequence_diagram(sequence_data, "quad_sequence_diagram.png")
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
"""Extract sequence data for diagram."""
|
||||||
|
import os, sys
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) # sibling experiment modules
|
||||||
|
from sage.all import graphs # type: ignore
|
||||||
|
from sage.misc.randstate import set_random_seed # type: ignore
|
||||||
|
from plane_depth_sequencing import (
|
||||||
|
quadrilateral_sequencing,
|
||||||
|
_quad_vertices,
|
||||||
|
_level_edge_of_face,
|
||||||
|
_quad_type
|
||||||
|
)
|
||||||
|
|
||||||
|
set_random_seed(42)
|
||||||
|
g = graphs.RandomTriangulation(7)
|
||||||
|
g.is_planar(set_embedding=True)
|
||||||
|
embedding = g.get_embedding()
|
||||||
|
faces = g.faces(embedding)
|
||||||
|
outer_cycle = [u for u, _ in faces[0]]
|
||||||
|
|
||||||
|
result = quadrilateral_sequencing(g, outer_cycle)
|
||||||
|
sequence = result['sequence']
|
||||||
|
move_codes = result['move_codes']
|
||||||
|
depth_labelling = result['depth_labelling']
|
||||||
|
|
||||||
|
move_names = {0: "AD", 1: "LA", 2: "J", 3: "RC"}
|
||||||
|
|
||||||
|
print("sequence_data = [")
|
||||||
|
for i, quad in enumerate(sequence[:4]):
|
||||||
|
quad_type = _quad_type(quad, depth_labelling)
|
||||||
|
f1, f2 = list(quad)
|
||||||
|
level_edge = _level_edge_of_face(f1, depth_labelling)
|
||||||
|
e1, e2 = list(level_edge)
|
||||||
|
a = next(v for v in f1 if v not in level_edge)
|
||||||
|
b = next(v for v in f2 if v not in level_edge)
|
||||||
|
ordered_verts = [e1, a, e2, b]
|
||||||
|
move = move_names[move_codes[i-1]] if i > 0 else ""
|
||||||
|
depths_str = [depth_labelling[v] for v in ordered_verts]
|
||||||
|
print(f' {{"vertices": {sorted(_quad_vertices(quad))}, "ordered": {ordered_verts}, "depths": {depths_str}, "move": "Q_{i+1}^{{{move}}}", "type": "{quad_type}"}},')
|
||||||
|
print("]")
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
"""Plane depth sequencing on maximal planar graphs."""
|
"""Plane depth sequencing on maximal planar graphs."""
|
||||||
|
import os, sys
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))) # repo root for `lib`
|
||||||
import random
|
import random
|
||||||
from typing import Any, TypedDict
|
from typing import Any, TypedDict
|
||||||
from sage.all import Graph, graphs # type: ignore[attr-defined] # pylint: disable=no-name-in-module
|
from sage.all import Graph, graphs # type: ignore[attr-defined] # pylint: disable=no-name-in-module
|
||||||
@@ -160,9 +162,11 @@ def extended_deep_embedding(
|
|||||||
next_vertex += 1
|
next_vertex += 1
|
||||||
g_prime.add_vertex(x)
|
g_prime.add_vertex(x)
|
||||||
g_prime.add_edges([(x, a), (x, b), (x, c)])
|
g_prime.add_edges([(x, a), (x, b), (x, c)])
|
||||||
depth_labelling[x] = depth_labelling[a] + 1
|
|
||||||
if frozenset(face_vertices) == outer_vertices:
|
if frozenset(face_vertices) == outer_vertices:
|
||||||
outer_cap_vertex = x
|
outer_cap_vertex = x
|
||||||
|
depth_labelling[x] = -1 # Outer-cap vertex has depth -1
|
||||||
|
else:
|
||||||
|
depth_labelling[x] = depth_labelling[a] + 1
|
||||||
|
|
||||||
g_prime.is_planar(set_embedding=True)
|
g_prime.is_planar(set_embedding=True)
|
||||||
embedding = g_prime.get_embedding()
|
embedding = g_prime.get_embedding()
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ quadrilateral with a color encoding its type, and overlays the index it
|
|||||||
occupies in the canonical sequence Q_1, ..., Q_N. The output is a PDF placed
|
occupies in the canonical sequence Q_1, ..., Q_N. The output is a PDF placed
|
||||||
next to paper.tex.
|
next to paper.tex.
|
||||||
"""
|
"""
|
||||||
|
import os, sys
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))) # repo root for `lib`
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) # sibling experiment modules
|
||||||
import argparse
|
import argparse
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ failure as one of:
|
|||||||
- 'ring_completion_monochromatic': a ring completion's quad has a monochromatic edge
|
- 'ring_completion_monochromatic': a ring completion's quad has a monochromatic edge
|
||||||
- 'success': global proper 4-coloring of G'.
|
- 'success': global proper 4-coloring of G'.
|
||||||
"""
|
"""
|
||||||
|
import os, sys
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))) # repo root for `lib`
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) # sibling experiment modules
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from sage.all import Graph, graphs # type: ignore[attr-defined] # pylint: disable=no-name-in-module
|
from sage.all import Graph, graphs # type: ignore[attr-defined] # pylint: disable=no-name-in-module
|
||||||
from lib.colored_graphs import canonize_and_save_graph
|
from lib.colored_graphs import canonize_and_save_graph
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
"""Plane diamond coloring on maximal planar graphs."""
|
"""Plane diamond coloring on maximal planar graphs."""
|
||||||
|
import os, sys
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))) # repo root for `lib`
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from sage.all import Graph, graphs # type: ignore[attr-defined] # pylint: disable=no-name-in-module
|
from sage.all import Graph, graphs # type: ignore[attr-defined] # pylint: disable=no-name-in-module
|
||||||
from lib.colored_graphs import canonize_and_save_graph
|
from lib.colored_graphs import canonize_and_save_graph
|
||||||
|
|||||||
Reference in New Issue
Block a user