Some tool drawio and sequence diagram

This commit is contained in:
ilgeco
2026-08-06 14:34:22 +02:00
parent 0712c5ba29
commit 7a3a808ae8
4 changed files with 1246 additions and 69 deletions
@@ -0,0 +1,567 @@
#!/usr/bin/env python3
"""Generate an editable diagrams.net library for Conv lowering comparisons."""
from __future__ import annotations
import argparse
import xml.etree.ElementTree as ET
from dataclasses import dataclass
from pathlib import Path
TILE = 760
GAP = 40
COLS = 4
INK = "#172033"
MUTED = "#667085"
GRID = "#d7deea"
PALE = "#f8fafc"
REFERENCE = "#5f6b7a"
PIMCOMP = "#ef8354"
RAPTOR = "#3b82f6"
INPUT = ("#f4a261", "#52b788", "#4895ef")
WEIGHT = ("#c86418", "#237a57", "#2768b2")
OUTPUT = ("#8b5cf6", "#ec4899", "#06b6d4", "#eab308")
SPATIAL = ("00", "01", "02", "10", "11", "12", "20", "21", "22")
@dataclass(frozen=True)
class Tile:
slug: str
owner: str
title: str
subtitle: str
scene: str
formula: str
algorithm: str
TILES = (
Tile("classic-reference", "REFERENCE", "Classic Conv + exact weight unfolding",
"Original OIHW weights; the two implementations choose different K orders.",
"reference", "Y[p,o] = Σc,kh,kw Xpatch[p,c,kh,kw] · W[o,c,kh,kw]",
"At every output position, multiply the patch by one filter and add every product."),
Tile("pimcomp-element", "PIMCOMP", "Element pipeline",
"One patch vector per input cycle; mapped weights stay fixed.",
"pimcomp_element", "patchPIM[1×K] · WflatPIM[K×O] → Yp[1×O]",
"Keep Wflat in the arrays; stream one patch each cycle to produce all O outputs."),
Tile("pimcomp-batch", "PIMCOMP", "Batch / replicated pipeline",
"Complete Wflat copies divide patches or input samples.",
"pimcomp_batch", "for replica r: Yr = patchr[1×K] · WflatPIM[K×O]",
"Copy all weights R times and send different patches to the copies in parallel."),
Tile("raptor-legacy-im2col", "RAPTOR", "Legacy explicit im2col",
"Every patch becomes one row of a global P×K matrix.",
"legacy", "Y[P×O] = im2col(X)[P×K] · WflatR[K×O]",
"Write every image patch as one matrix row, then multiply the two large matrices."),
Tile("raptor-packed-im2col", "RAPTOR", "Packed im2col",
"Pack q patch rows and repeat Wflat on a block diagonal.",
"packed", "packedY[1×qO] = [patch0|…|patchq1] · diag(WflatR,…,WflatR)",
"Join q patches and use diagonal weight copies so one multiply computes q independent outputs."),
Tile("raptor-streamed-patch", "RAPTOR", "Streamed patch",
"Gather one patch into bounded scratch; avoid global im2col.",
"streamed_patch", "Yp[1×O] = scratchPatchp[1×K] · WflatR[K×O]",
"Gather one patch, multiply it, write its output, and reuse scratch for the next patch."),
Tile("raptor-streamed-packed", "RAPTOR", "Streamed packed",
"Gather q patch rows in bounded scratch, then block-diagonal pack them.",
"streamed_packed", "packedY = packedScratch[1×qK] · diag(WflatR×q)[qK×qO]",
"Gather q patches in small scratch, join them, multiply by diagonal weights, then unpack q outputs."),
Tile("raptor-depthwise", "RAPTOR", "Depthwise special case",
"Each channel owns one row-major 3×3 kernel; channels never reduce together.",
"depthwise", "Y[p,c] = Σkh,kw Xpatch[p,c,kh,kw] · W[c,kh,kw]",
"For each channel separately, multiply its nine patch values by its nine weights and add."),
Tile("raptor-output-channel-tiled", "RAPTOR", "Output-channel tiled",
"Every O tile retains all channel-major K rows and selects output columns.",
"c_tiled", "Y[:,Oj] = patch[1×K] · Wflat[:,Oj][K×|Oj|]; concat j",
"Reuse the full patch for each output-filter group, then join the output groups."),
Tile("raptor-input-k-tiled", "RAPTOR", "Input-K tiled",
"Split matching K ranges; add their partial output vectors.",
"k_tiled", "Y[1×O] = Σi patch[Ki] · Wflat[Ki,:]",
"Multiply matching K slices independently, then add their partial output vectors."),
Tile("raptor-tiled-2d", "RAPTOR", "Two-dimensional tiled",
"Partition both K rows and output-filter columns.",
"tiled_2d", "Y[:,Oj] = Σi patch[Ki] · Wflat[Ki,Oj]; concat j",
"Split both directions: add results down K and join results across output groups."),
Tile("raptor-row-strip", "RAPTOR", "Pixel-major row-strip",
"A lane forms patches across one output row and slices K.",
"row_strip", "for x: Y[r,x,:] = Σi patch[r,x,Ki] · Wflat[Ki,:]",
"Move across one output row; at each x form a patch, multiply its K slices, and add."),
Tile("raptor-row-strip-c-tiled", "RAPTOR", "Row-strip + output tiling",
"Each row lane is duplicated across disjoint output-column tiles.",
"row_strip_c", "for x,j: Y[r,x,Oj] = patch[r,x,:] · Wflat[:,Oj]",
"Give each output-filter group a copy of the row lane, then join their output columns."),
)
class Drawio:
def __init__(self) -> None:
rows = (len(TILES) + COLS - 1) // COLS
self.mxfile = ET.Element("mxfile", host="app.diagrams.net", compressed="false")
diagram = ET.SubElement(self.mxfile, "diagram", id="conv-lowering-library",
name="Conv lowering tile library")
model = ET.SubElement(
diagram, "mxGraphModel", dx="1200", dy="900", grid="1", gridSize="10",
guides="1", tooltips="1", connect="1", arrows="1", fold="1",
page="0", pageScale="1", pageWidth=str(COLS * (TILE + GAP)),
pageHeight=str(rows * (TILE + GAP)), math="0", shadow="0",
)
self.root = ET.SubElement(model, "root")
ET.SubElement(self.root, "mxCell", id="0")
ET.SubElement(self.root, "mxCell", id="1", parent="0")
self.counter = 2
def _id(self, prefix: str = "c") -> str:
value = f"{prefix}-{self.counter}"
self.counter += 1
return value
def vertex(self, parent: str, x: float, y: float, w: float, h: float,
value: str = "", style: str = "", *, cell_id: str | None = None) -> str:
cell_id = cell_id or self._id()
cell = ET.SubElement(self.root, "mxCell", id=cell_id, value=value,
style=style, vertex="1", parent=parent)
ET.SubElement(cell, "mxGeometry", x=str(x), y=str(y), width=str(w),
height=str(h), **{"as": "geometry"})
return cell_id
def group(self, x: float, y: float, name: str) -> str:
return self.vertex("1", x, y, TILE, TILE, name,
"group;connectable=0;", cell_id=f"tile-{name}")
def edge(self, parent: str, source: str, target: str, value: str = "") -> str:
style = (
"edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;"
f"html=1;endArrow=block;endFill=1;strokeWidth=2;strokeColor={INK};"
f"fontSize=10;fontColor={INK};labelBackgroundColor=#ffffff;"
)
cell_id = self._id("e")
cell = ET.SubElement(self.root, "mxCell", id=cell_id, value=value,
style=style, edge="1", parent=parent,
source=source, target=target)
ET.SubElement(cell, "mxGeometry", relative="1", **{"as": "geometry"})
return cell_id
def write(self, path: Path) -> None:
ET.indent(self.mxfile, space=" ")
path.parent.mkdir(parents=True, exist_ok=True)
ET.ElementTree(self.mxfile).write(path, encoding="utf-8", xml_declaration=True)
def rect_style(fill: str = "#ffffff", stroke: str = GRID, *, rounded: bool = True,
font_size: int = 11, font_color: str = INK, align: str = "center",
stroke_width: int = 1) -> str:
return (
f"rounded={int(rounded)};whiteSpace=wrap;html=1;fillColor={fill};"
f"strokeColor={stroke};strokeWidth={stroke_width};fontColor={font_color};"
f"fontSize={font_size};fontFamily=Helvetica;align={align};verticalAlign=middle;"
)
def text_style(size: int, *, color: str = INK, align: str = "left", bold: bool = False) -> str:
return (
"text;html=1;strokeColor=none;fillColor=none;whiteSpace=wrap;"
f"fontSize={size};fontColor={color};fontFamily=Helvetica;align={align};"
f"verticalAlign=middle;fontStyle={1 if bold else 0};"
)
def add_text(d: Drawio, parent: str, x: float, y: float, w: float, h: float,
value: str, size: int = 11, *, color: str = INK,
align: str = "left", bold: bool = False) -> str:
return d.vertex(parent, x, y, w, h, value,
text_style(size, color=color, align=align, bold=bold))
def add_box(d: Drawio, parent: str, x: float, y: float, w: float, h: float,
value: str, *, fill: str = "#ffffff", stroke: str = GRID,
size: int = 11, rounded: bool = True, stroke_width: int = 1) -> str:
return d.vertex(parent, x, y, w, h, value,
rect_style(fill, stroke, rounded=rounded, font_size=size,
stroke_width=stroke_width))
def matrix3(d: Drawio, parent: str, x: float, y: float, size: float,
color: str, *, labels: bool = True) -> None:
cell = size / 3
for row in range(3):
for col in range(3):
value = SPATIAL[row * 3 + col] if labels else ""
d.vertex(parent, x + col * cell, y + row * cell, cell, cell, value,
rect_style(color, "#ffffff", rounded=False,
font_size=7, stroke_width=1))
d.vertex(parent, x, y, size, size, "",
"rounded=0;whiteSpace=wrap;html=1;fillColor=none;"
f"strokeColor={INK};strokeWidth=1;")
def source_panel(d: Drawio, parent: str) -> None:
add_text(d, parent, 34, 132, 230, 24, "Input patch X[:,C,3,3]", 12, bold=True)
for channel, color in enumerate(INPUT):
x = 34 + channel * 70
matrix3(d, parent, x, 166, 48, color)
add_text(d, parent, x, 216, 48, 18, f"C{channel}", 9,
color=color, align="center", bold=True)
add_text(d, parent, 340, 132, 380, 24, "Original W[O,C,3,3] (OIHW)",
12, bold=True, align="center")
for output, outline in enumerate(OUTPUT):
x = 340 + output * 96
add_box(d, parent, x, 158, 88, 78, "", fill="#ffffff",
stroke=outline, stroke_width=2)
for channel, color in enumerate(WEIGHT):
gx = x + 7 + channel * 25
matrix3(d, parent, gx, 181, 19, color, labels=False)
add_text(d, parent, gx, 163, 19, 16, f"C{channel}", 7,
color=color, align="center", bold=True)
add_text(d, parent, x, 216, 88, 18, f"filter O{output}", 9,
color=outline, align="center", bold=True)
def order_sequence(order: str) -> list[tuple[int, str]]:
if order == "raptor":
return [(channel, spatial) for channel in range(3) for spatial in SPATIAL]
if order == "pimcomp":
return [(channel, spatial) for spatial in SPATIAL for channel in range(3)]
raise ValueError(order)
def order_vector(d: Drawio, parent: str, y: float, order: str, *,
weight: bool, label: str) -> None:
x, width, height = 125, 595, 28
sequence = order_sequence(order)
cell_w = width / len(sequence)
colors = WEIGHT if weight else INPUT
for index, (channel, spatial) in enumerate(sequence):
value = f"w{spatial}" if weight else (spatial if order == "raptor" else f"C{channel}")
font_color = "#ffffff" if weight else INK
d.vertex(parent, x + index * cell_w, y, cell_w, height, value,
rect_style(colors[channel], "#ffffff", rounded=False,
font_size=7, font_color=font_color))
d.vertex(parent, x, y, width, height, "",
f"rounded=0;fillColor=none;strokeColor={INK};strokeWidth=1;")
add_text(d, parent, 35, y, 82, height, label, 8, color=MUTED,
align="right", bold=True)
if order == "raptor":
for channel in range(3):
add_text(d, parent, x + channel * width / 3, y - 18, width / 3, 16,
f"C{channel}: row-major 00→01→02→10→…→22", 8,
color=colors[channel], align="center", bold=True)
else:
for spatial_index, spatial in enumerate(SPATIAL):
add_text(d, parent, x + spatial_index * width / 9, y - 18,
width / 9, 16, f"({spatial}) C0,C1,C2", 7,
color=MUTED, align="center", bold=True)
def node(d: Drawio, parent: str, x: float, y: float, w: float, h: float,
label: str, *, fill: str = PALE, stroke: str = RAPTOR) -> str:
return add_box(d, parent, x, y, w, h, label, fill=fill, stroke=stroke,
size=10, stroke_width=2)
def layout_strip(d: Drawio, parent: str, x: float, y: float, w: float, h: float,
order: str, colors: tuple[str, ...], *, channel: int | None = None,
repeat: int = 1) -> None:
sequence = ([channel] * 9 if channel is not None
else [item[0] for item in order_sequence(order)])
row_h = h / repeat
for copy in range(repeat):
cell_w = w / len(sequence)
for index, color_index in enumerate(sequence):
d.vertex(parent, x + index * cell_w, y + copy * row_h,
cell_w, row_h, "",
rect_style(colors[color_index], "#ffffff", rounded=False,
font_size=1))
d.vertex(parent, x, y, w, h, "",
f"rounded=0;fillColor=none;strokeColor={INK};strokeWidth=1;")
def layout_node(d: Drawio, parent: str, x: float, y: float, w: float, h: float,
label: str, *, order: str | None = None,
colors: tuple[str, ...] = INPUT, channel: int | None = None,
repeat: int = 1, stroke: str = RAPTOR) -> str:
result = node(d, parent, x, y, w, h, "", fill="#ffffff", stroke=stroke)
label_height = h - (28 if order is not None else 10)
add_text(d, parent, x + 6, y + 5, w - 12, label_height, label, 10,
align="center", bold=True)
if order is not None:
layout_strip(d, parent, x + 8, y + h - 19, w - 16, 12, order, colors,
channel=channel, repeat=repeat)
return result
def scene_linear(d: Drawio, parent: str,
items: tuple[tuple[str, dict | None], ...], *,
y: float = 320) -> None:
margin, gap = 42, 34
width = (TILE - 2 * margin - gap * (len(items) - 1)) / len(items)
ids = []
for index, (label, layout) in enumerate(items):
x = margin + index * (width + gap)
ids.append(layout_node(d, parent, x, y, width, 92, label,
**(layout or {})))
for left, right in zip(ids, ids[1:]):
d.edge(parent, left, right)
def scene_batch(d: Drawio, parent: str) -> None:
for row in range(3):
y = 260 + row * 110
patch = layout_node(d, parent, 42, y, 210, 72,
f"patches p{row}, p{row + 3}, … [1×K]",
order="pimcomp", colors=INPUT, stroke=PIMCOMP)
weights = layout_node(d, parent, 302, y, 220, 72,
f"replica R{row}: Wflat [K×O]",
order="pimcomp", colors=WEIGHT, stroke=PIMCOMP)
result = node(d, parent, 610, y, 106, 72, f"Yp\n[1×O]",
fill=OUTPUT[row], stroke=PIMCOMP)
d.edge(parent, patch, weights, "×")
d.edge(parent, weights, result)
def scene_depthwise(d: Drawio, parent: str) -> None:
for channel in range(3):
y = 260 + channel * 110
patch = layout_node(d, parent, 46, y, 190, 72,
f"patch C{channel} [1×9]", order="raptor",
colors=INPUT, channel=channel)
kernel = layout_node(d, parent, 300, y, 210, 72,
f"W[C{channel},0,:,:] [9×1]", order="raptor",
colors=WEIGHT, channel=channel)
result = node(d, parent, 578, y, 136, 72,
f"Y channel {channel}", fill=OUTPUT[channel])
d.edge(parent, patch, kernel, "×")
d.edge(parent, kernel, result)
def scene_c_tiled(d: Drawio, parent: str) -> None:
for row, outputs in enumerate(((0, 1), (2, 3))):
y = 280 + row * 130
patch = layout_node(d, parent, 42, y, 190, 84,
"same full patch [1×K]", order="raptor",
colors=INPUT)
weights = layout_node(
d, parent, 292, y, 244, 84,
f"Wflat[:,O{outputs[0]}:O{outputs[-1] + 1}]\n[K×2], all K rows",
order="raptor", colors=WEIGHT, stroke=OUTPUT[outputs[0]])
result = node(d, parent, 610, y, 106, 84,
f"Y tile {row}\n[1×2]", fill=OUTPUT[outputs[0]])
d.edge(parent, patch, weights, "×")
d.edge(parent, weights, result)
add_text(d, parent, 250, 552, 260, 22, "concatenate tile 0 | tile 1 along O",
10, color=MUTED, align="center", bold=True)
def scene_k_tiled(d: Drawio, parent: str) -> None:
for row in range(3):
y = 250 + row * 100
patch = layout_node(d, parent, 42, y, 188, 70,
f"patch Ki{row}: C{row} [1×9]", order="raptor",
colors=INPUT, channel=row)
weights = layout_node(d, parent, 298, y, 224, 70,
f"Wflat[Ki{row},:] [9×O]", order="raptor",
colors=WEIGHT, channel=row)
partial = node(d, parent, 596, y, 120, 70,
f"partial {row}\n[1×O]", fill=PALE)
d.edge(parent, patch, weights, "×")
d.edge(parent, weights, partial)
node(d, parent, 300, 570, 160, 46, "VADD Σ → Y [1×O]", fill="#ffffff")
add_text(d, parent, 470, 578, 238, 28,
"partial 0 + partial 1 + partial 2", 9,
color=MUTED, align="center", bold=True)
def scene_2d(d: Drawio, parent: str) -> None:
for row in range(3):
y = 250 + row * 100
patch = layout_node(d, parent, 38, y, 160, 70,
f"patch Ki{row} [1×9]", order="raptor",
colors=INPUT, channel=row)
for col in range(2):
layout_node(d, parent, 270 + col * 230, y, 180, 70,
f"× Wflat[Ki{row},Oj{col}] [9×2]",
order="raptor", colors=WEIGHT, channel=row,
stroke=OUTPUT[col * 2])
add_text(d, parent, 270, 566, 440, 24,
"Σ tile rows along K; concatenate tile columns along O", 10,
color=MUTED, align="center", bold=True)
def scene_row_strip_c(d: Drawio, parent: str) -> None:
patch = layout_node(d, parent, 42, 330, 206, 88,
"row-window patch for x [1×K]", order="raptor",
colors=INPUT)
for col, outputs in enumerate(((0, 1), (2, 3))):
y = 270 + col * 150
weights = layout_node(
d, parent, 310, y, 238, 88,
f"lane r × O{outputs[0]}:O{outputs[-1] + 1}\nWflat[:,Oj] [K×2]",
order="raptor", colors=WEIGHT, stroke=OUTPUT[outputs[0]])
result = node(d, parent, 614, y, 104, 88,
f"Yj\n[1×2]", fill=OUTPUT[outputs[0]])
d.edge(parent, patch, weights, "reuse ×")
d.edge(parent, weights, result)
node(d, parent, 500, 570, 190, 46, "concat O → output row [1×4]")
def operation_scene(d: Drawio, parent: str, scene: str) -> None:
add_text(d, parent, 30, 204, 700, 22, "LOWERED COMPUTE", 11,
color=MUTED, align="center", bold=True)
if scene == "pimcomp_element":
scene_linear(d, parent, (
("input cycle p\npatchPIM [1×K]",
{"order": "pimcomp", "colors": INPUT, "stroke": PIMCOMP}),
("mapped Array Group\nWflatPIM [K×O]",
{"order": "pimcomp", "colors": WEIGHT, "stroke": PIMCOMP}),
("Yp [1×O]", {"stroke": PIMCOMP}),
))
elif scene == "pimcomp_batch":
scene_batch(d, parent)
elif scene == "legacy":
scene_linear(d, parent, (
("global im2col\n[P×K]", {"order": "raptor", "colors": INPUT}),
("WflatR\n[K×O]", {"order": "raptor", "colors": WEIGHT}),
("Y rows\n[P×O]", None),
))
elif scene == "packed":
scene_linear(d, parent, (
("q patch rows\n[q×K]",
{"order": "raptor", "colors": INPUT, "repeat": 2}),
("pack → [1×qK]",
{"order": "raptor", "colors": INPUT, "repeat": 2}),
("diag(WflatR×q)\n[qK×qO]",
{"order": "raptor", "colors": WEIGHT, "repeat": 2}),
("packed Y\n[1×qO]", None),
))
elif scene == "streamed_patch":
scene_linear(d, parent, (
("gather one patch", None),
("bounded scratch\n[1×K]", {"order": "raptor", "colors": INPUT}),
("WflatR\n[K×O]", {"order": "raptor", "colors": WEIGHT}),
("Yp\n[1×O]", None),
))
elif scene == "streamed_packed":
scene_linear(d, parent, (
("q patches\n[q×K]",
{"order": "raptor", "colors": INPUT, "repeat": 2}),
("packedScratch\n[1×qK]",
{"order": "raptor", "colors": INPUT, "repeat": 2}),
("diag(WflatR×q)\n[qK×qO]",
{"order": "raptor", "colors": WEIGHT, "repeat": 2}),
("packed Y\n[1×qO]", None),
))
elif scene == "depthwise":
scene_depthwise(d, parent)
elif scene == "c_tiled":
scene_c_tiled(d, parent)
elif scene == "k_tiled":
scene_k_tiled(d, parent)
elif scene == "tiled_2d":
scene_2d(d, parent)
elif scene == "row_strip":
scene_linear(d, parent, (
("lane r row windows", None),
("for x: patch\n[1×K]", {"order": "raptor", "colors": INPUT}),
("K-sliced Wflat\n[Ki×O]", {"order": "raptor", "colors": WEIGHT}),
("Σ partials →\noutput row", None),
))
elif scene == "row_strip_c":
scene_row_strip_c(d, parent)
else:
raise ValueError(scene)
def reference_body(d: Drawio, parent: str) -> None:
add_box(d, parent, 24, 252, 712, 158, "", fill="#ffffff", stroke=PIMCOMP)
add_text(d, parent, 40, 260, 680, 22,
"PIMCOMP: spatial-major, row-wise positions; C interleaved", 11,
color=PIMCOMP, align="center", bold=True)
order_vector(d, parent, 306, "pimcomp", weight=False, label="Input patch")
order_vector(d, parent, 366, "pimcomp", weight=True, label="Matching W")
add_text(d, parent, 40, 394, 680, 16,
"k=((kh·Kw)+kw)·Cin+c — c changes fastest", 9,
color=PIMCOMP, align="center", bold=True)
add_box(d, parent, 24, 424, 712, 158, "", fill="#ffffff", stroke=RAPTOR)
add_text(d, parent, 40, 432, 680, 22,
"RAPTOR: channel-major; each 3×3 plane is row-major", 11,
color=RAPTOR, align="center", bold=True)
order_vector(d, parent, 478, "raptor", weight=False, label="Input patch")
order_vector(d, parent, 538, "raptor", weight=True, label="Matching W")
add_text(d, parent, 40, 566, 680, 16,
"k=((c·Kh)+kh)·Kw+kw — kw changes fastest", 9,
color=RAPTOR, align="center", bold=True)
add_box(d, parent, 120, 590, 520, 42, "", fill=PALE, stroke=GRID)
add_text(d, parent, 132, 594, 496, 34,
"Shade key: light = activation; dark = matching weight row. "
"Depthwise: independent C0/C1/C2 row-major Kc=9 vectors.",
9, color=MUTED, align="center", bold=True)
def layout_reference(d: Drawio, parent: str, tile: Tile, accent: str) -> None:
if tile.scene == "depthwise":
layout = "independent row-major Kc=9 per channel"
elif tile.owner == "PIMCOMP":
layout = "PIMCOMP spatial-major K order"
else:
layout = "RAPTOR channel-major K order"
add_box(d, parent, 24, 140, 712, 42, "", fill=PALE, stroke=accent)
add_text(d, parent, 38, 146, 684, 30,
f"LAYOUT → see REFERENCE tile: {layout}", 10,
color=accent, align="center", bold=True)
def algorithm_card(d: Drawio, parent: str, tile: Tile, accent: str) -> None:
add_box(d, parent, 24, 638, 712, 102, "", fill="#ffffff", stroke=accent)
add_text(d, parent, 40, 646, 90, 34, "ALGORITHM", 9,
color=accent, bold=True)
add_text(d, parent, 132, 644, 588, 38, tile.algorithm, 10)
add_text(d, parent, 40, 690, 90, 34, "MATH", 9,
color=accent, bold=True)
add_text(d, parent, 132, 686, 588, 42, tile.formula, 10,
color=accent, bold=True)
def render_tile(d: Drawio, tile: Tile, index: int) -> None:
col, row = index % COLS, index // COLS
parent = d.group(col * (TILE + GAP), row * (TILE + GAP), tile.slug)
accent = {"REFERENCE": REFERENCE, "PIMCOMP": PIMCOMP, "RAPTOR": RAPTOR}[tile.owner]
add_box(d, parent, 0, 0, TILE, TILE, "", fill="#fbfcff", stroke=accent,
stroke_width=3)
add_box(d, parent, 24, 20, 106, 28, tile.owner, fill=accent, stroke=accent,
size=10)
add_text(d, parent, 24, 56, 712, 36, tile.title, 22, bold=True)
add_text(d, parent, 24, 92, 712, 30, tile.subtitle, 11, color=MUTED)
if tile.scene == "reference":
source_panel(d, parent)
reference_body(d, parent)
else:
layout_reference(d, parent, tile, accent)
operation_scene(d, parent, tile.scene)
algorithm_card(d, parent, tile, accent)
def validate(root: ET.Element) -> None:
ids = [cell.get("id") for cell in root.findall(".//mxCell")]
assert len(ids) == len(set(ids)), "draw.io cell IDs must be unique"
assert {"0", "1"}.issubset(ids), "draw.io root cells are required"
groups = [cell for cell in root.findall(".//mxCell")
if cell.get("style") == "group;connectable=0;"]
assert len(groups) == len(TILES), "one editable group is required per tile"
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("-o", "--output", type=Path,
default=Path("conv_lowering_tiles.drawio"))
args = parser.parse_args()
diagram = Drawio()
for index, tile in enumerate(TILES):
render_tile(diagram, tile, index)
validate(diagram.mxfile)
diagram.write(args.output)
print(args.output)
return 0
if __name__ == "__main__":
raise SystemExit(main())