Files
Raptor/validation/tools/conv_lowering_tiles_drawio.py
T
2026-08-06 14:34:22 +02:00

568 lines
26 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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())