Skip to content

feat: add pretty run report#416

Open
kaeun97 wants to merge 2 commits intoegraphs-good:mainfrom
kaeun97:kaeun97/pretty-report
Open

feat: add pretty run report#416
kaeun97 wants to merge 2 commits intoegraphs-good:mainfrom
kaeun97:kaeun97/pretty-report

Conversation

@kaeun97
Copy link
Copy Markdown

@kaeun97 kaeun97 commented May 5, 2026

Resolves #398.

Here is an example code:

from __future__ import annotations
from egglog import *

egraph = EGraph()

class Num(Expr):
    def __init__(self, n: i64Like) -> None: ...
    def __add__(self, other: Num) -> Num: ...
    def __mul__(self, other: Num) -> Num: ...

x, y = vars_("x y", Num)
egraph.register(rewrite(x + y).to(y + x))
egraph.register(Num(1) + Num(2))
report = egraph.run(10)
print(report)

Output before:

RunReport { iterations: [IterationReport { rule_set_report: RuleSetReport { changed: true, rule_reports: {"(rewrite (__main___Num___add__ _x _y) (__main___Num___add__ _y _x))": [RuleReport { plan: None, search_and_apply_time: 2.625µs, num_matches: 1 }]}, search_and_apply_time: 5.375µs, merge_time: 583ns }, rebuild_time: 1.125µs }, IterationReport { rule_set_report: RuleSetReport { changed: false, rule_reports: {"(rewrite (__main___Num___add__ _x _y) (__main___Num___add__ _y _x))": [RuleReport { plan: None, search_and_apply_time: 1.125µs, num_matches: 1 }]}, search_and_apply_time: 2.75µs, merge_time: 1.041µs }, rebuild_time: 0ns }], updated: true, search_and_apply_time_per_rule: {"(rewrite (__main___Num___add__ _x _y) (__main___Num___add__ _y _x))": 3.75µs}, num_matches_per_rule: {"(rewrite (__main___Num___add__ _x _y) (__main___Num___add__ _y _x))": 2}, search_and_apply_time_per_ruleset: {"": 8.125µs}, merge_time_per_ruleset: {"": 1.624µs}, rebuild_time_per_ruleset: {"": 1.125µs} }

Output after:

PrettyRunReport(iterations=[PrettyIterationReport(rule_set_report=PrettyRuleSetReport(changed=True, rule_reports={'rewrite(x + y).to(y + x)': [PrettyRuleReport(plan=None, search_and_apply_time=datetime.timedelta(0), num_matches=1)]}, search_and_apply_time=datetime.timedelta(0), merge_time=datetime.timedelta(0)), rebuild_time=datetime.timedelta(0)), PrettyIterationReport(rule_set_report=PrettyRuleSetReport(changed=False, rule_reports={'rewrite(x + y).to(y + x)': [PrettyRuleReport(plan=None, search_and_apply_time=datetime.timedelta(0), num_matches=1)]}, search_and_apply_time=datetime.timedelta(0), merge_time=datetime.timedelta(0)), rebuild_time=datetime.timedelta(0))], updated=True, search_and_apply_time_per_rule={'rewrite(x + y).to(y + x)': datetime.timedelta(0)}, num_matches_per_rule={'rewrite(x + y).to(y + x)': 2}, search_and_apply_time_per_ruleset={'': datetime.timedelta(0)}, merge_time_per_ruleset={'': datetime.timedelta(0)}, rebuild_time_per_ruleset={'': datetime.timedelta(0)})

@kaeun97 kaeun97 marked this pull request as ready for review May 5, 2026 23:09
@kaeun97 kaeun97 mentioned this pull request May 5, 2026
Copy link
Copy Markdown
Member

@saulshanabrook saulshanabrook left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this! Added a few comments. Could you also add this to the changelog file with a link to this PR?

Comment thread python/egglog/egraph.py

@overload
def run(self, limit: int, /, *until: Fact, ruleset: Ruleset | None = None) -> bindings.RunReport: ...
def run(self, limit: int, /, *until: Fact, ruleset: Ruleset | None = None) -> PrettyRunReport: ...
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can just call it RunReport. I have a few places I use the same name in the high level in bindings, such as EGraph itself. Since the hope is that users won't ever have to import from bindings, this shouldn't clobber the name.

"""
if egglog_key in self.egg_rule_to_command_decl:
return pretty_decl(self.__egg_decls__, self.egg_rule_to_command_decl[egglog_key])
return egglog_key
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any cases where it won't find the saved command? If possible seems better to raise an exception instead of falling back.

@dataclass
class PrettyRuleSetReport:
changed: bool
rule_reports: dict[str, list[PrettyRuleReport]]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we try storing them as actual rewrites/rules here instead of strings? That would allow users at runtime for example to extract the rule, run it on something, or compare equality without having to compare string equality. Also just in others forms I usually try to return runtime objects instead of strings.

This would also impact how they are stored in e-graph state.

Then also the pretty print won't show them as strings.

rebuild_time_per_ruleset: dict[str, timedelta]

@classmethod
def from_bindings(cls, report: bindings.RunReport, state: EGraphState) -> PrettyRunReport:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we make these private, prefixing with _? I don't imagine that users would need to be able to translate between bindings and this.

merge_time: timedelta

@classmethod
def from_bindings(cls, report: bindings.RuleSetReport, translate_key: callable) -> PrettyRuleSetReport:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this callable be more strongly typed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pretty Run Report

2 participants