Implement rich debugprint format via file="rich"#2042
Implement rich debugprint format via file="rich"#2042ricardoV94 merged 18 commits intopymc-devs:v3from
rich debugprint format via file="rich"#2042Conversation
|
The Mermaid renderer sketchimport re
import pytensor.tensor as pt
from pytensor.printing import _build_label, _iter_graph_nodes
def build_mermaid(var) -> str:
done: dict = {}
used_ids: dict = {}
node_defs: dict[str, str] = {}
edges: list[tuple[str, str]] = []
apply_to_mid: dict[int, str] = {}
for gnode in _iter_graph_nodes(var, done=done):
label = _build_label(
gnode, done=done, used_ids=used_ids, id_type="CHAR",
print_type=False, print_shape=False, print_destroy_map=False,
print_view_map=False, print_op_info=False, op_information={},
)
mid = re.search(r"\[id ([^\]]+)\]", label).group(1)
if mid not in node_defs:
node_defs[mid] = label
if gnode.parent_node is not None:
parent_mid = apply_to_mid.get(id(gnode.parent_node))
if parent_mid is not None:
edges.append((parent_mid, mid))
if gnode.var.owner is not None:
apply_to_mid[id(gnode.var.owner)] = mid
lines = ["graph TD"]
for mid, label in node_defs.items():
lines.append(f' {mid}["{label.replace(chr(34), "#quot;")}"]')
for src, dst in edges:
lines.append(f" {src} --> {dst}")
return "\n".join(lines)
x = pt.dvector("x")
print(build_mermaid((x * 2).sum()))Output: graph TD
A["Sum{axes=None} [id A]"]
B["Mul [id B]"]
C["x [id C]"]
D["ExpandDims{axis=0} [id D]"]
E["2 [id E]"]
A --> B
B --> C
B --> D
D --> E
This would also address #1488. |
|
Another one from the mermaid POC x = pt.dvector("x")
y = (x * 2).sum()
z = pt.stack([y.mean(), y.std()])graph TD
A["MakeVector{dtype='float64'} [id A]"]
B["True_div [id B] 'mean'"]
C["Sum{axes=None} [id C]"]
D["Sum{axes=None} [id D]"]
E["Mul [id E]"]
F["x [id F]"]
G["ExpandDims{axis=0} [id G]"]
H["2 [id H]"]
I["Cast{float64} [id I]"]
J["1 [id J]"]
K["Sqrt [id K] 'std'"]
L["True_div [id L] 'var'"]
M["Sum{axes=[]} [id M]"]
N["Pow [id N]"]
O["Sub [id O]"]
P["True_div [id P] 'mean'"]
Q["Sum{axes=[]} [id Q]"]
R["Cast{float64} [id R]"]
S["1 [id S]"]
T["2.0 [id T]"]
U["Cast{float64} [id U]"]
V["1 [id V]"]
A --> B
B --> C
C --> D
D --> E
E --> F
E --> G
G --> H
B --> I
I --> J
A --> K
K --> L
L --> M
M --> N
N --> O
O --> D
D --> D
O --> P
P --> Q
Q --> D
D --> D
P --> R
R --> S
N --> T
L --> U
U --> V
|
|
Can I see how the rich ones look like :D ? |
from rich import print as rprint
x = pt.dvector("x")
y = (x * 2).sum()
z = pt.stack([y.mean(), y.std()])
tree = debugprint(z, file="rich")
rprint(tree)Result: |
|
That's just text to me, how is it different than the default? |
|
One difference it shows the nested one grayed out... but it's duplicated? |
|
Would be nice to do some more styling (now that we can). Like coloring these repeated nodes to more easly see where they come from. Pow [id D] would have a unique color |
|
Also no inner graphs :( ? In [10]: pytensor.OpFromGraph([x], [x.std()])(x).dprint(file="rich")
Out[10]:
OpFromGraph{inline=False} [id A]
└── x [id B] |
|
That's good but is the duplication unavoidable? Should be just seen node then ... |
|
Pushing up. Cycles through list of colors. Personally find the red looks like an error. Thoughts / preferences? Reference |
|
I like it, feel free to skip red, but it doesn't bother me. I'm a bit OCD on the ellipsis not being it's own line, but may be just habit. OTOH we have a lot of attributes we write inline like destroy map, names, op_info, type, shape that can become long and hide the ellipsis or make it harder to spot |
Was this some additional feature? Something to follow up on? |
This already exists in dprint: dprint(print_type=True, print_memory_map=True, print_op_info=True). It's fine if the rich version doesn't currently handle them, but my point was I think I want Does it make sense? If making the ellipsis an item on next line is too hard, let's just go with what we have now. |
|
One last question, since we have so much color control, can we render the inner graphs a bit lighter (lower contrast?). To make it easier to scroll into the original graph |
54b9d2e to
de712b4
Compare
|
gorgeous. Does ellipsis color have any meaning? I would leave then gray (or same color as parent) if not Let's not make the inner graphs less contrast, use exactly the same styling /rules as outer graph. It's something I went back on after first asking. |
ellipsis color is same as the parent. Would also be dimmed if part of the InnerGraph
So not dimmed anymore? How about just bolding the Inner Graph header? |
|
So no to : and consolidate to: Is that your preference? |
|
Yes! That's the same thing regular dprint does |
col_bars was iterating over all of ancestor_is_last, producing one extra 3-char column segment per node. The root-level ancestor entry should be skipped (ancestor_is_last[1:]) because the root contributes an empty prefix_child and column bar accumulation starts from depth-1 onwards. This restores byte-for-byte compatibility with the pre-refactor text output.
The old approach marked a repeat/stop_on_name node itself as is_repeat=True, replacing its label with ···. The new approach yields the node normally (label visible) and then yields a separate sentinel child with is_repeat=True one level deeper, so ··· appears indented below the node. Also moves done[node] = "" to before yield gnode so DAG-diamond marking is unconditional regardless of early generator abandonment.
… from color palette
5deb752 to
284b52b
Compare
|
file="str" wouldn't have the ... so that needs to change. However, should color still be used in this case? |
Yeah, if it's a root variable don't need to use ellipsis below, although the color is still nice imo to see the root variable is used in multiple places (root variables only used in one place can stay regular gray). Let me know if this complicates things a lot, if yes don't bother with the color for this case |
|
Fixed with 76d5878 |
|
Tested it locally, it's awesome. I think the only thing not supported is |
|
@williambdean ready to merge? |
|
Got this bot review if you care to look (I know you could get it for yourself): PR ReviewOverviewThe PR does two things:
The text renderer now delegates to the generator, so the refactor is exercised by every pre-existing debugprint test. Strengths
Issues / SuggestionsDuplication in
|
|
ready to merge 🚀 |
rich debugprint format via file="rich"
|
Thanks @williambdean |












Closes #1034
Adds
file="rich"as a new sentinel todebugprint, returning arich.tree.Treeinstead of printing text.richis added as a hard dependency. The refactor also extractsGraphNode,_assign_id,_build_label, and_iter_graph_nodesfromthe monolithic
_debugprintfunction, which both the text and richrenderers now consume.
The
"rich"sentinel slots in alongside"str"andNone— the textoutput path is unchanged.