Skip to content

Highlight graph changes per rewrite step in proof view #190#448

Open
dorakingx wants to merge 21 commits intozxcalc:masterfrom
dorakingx:feature/issue-190-highlight-rewrites
Open

Highlight graph changes per rewrite step in proof view #190#448
dorakingx wants to merge 21 commits intozxcalc:masterfrom
dorakingx:feature/issue-190-highlight-rewrites

Conversation

@dorakingx
Copy link

This PR introduces visual highlighting of nodes and edges that change between consecutive proof steps, making it easier to see what each rewrite did.

  • Rewrite diff highlighting

    • When a proof step is selected, the app now computes a GraphDiff between that step’s graph and the previous one (ProofStepView.move_to_step in proof.py).
    • Newly added or modified vertices (new_verts, changed_vertex_types, changed_phases, changed_vdata, changed_pos) and edges (new_edges, changed_edata, changed_edge_types) are collected and passed to the scene as “highlight sets”.
  • Rendering changes

    • GraphScene now maintains sets of highlighted vertices and edges and exposes:
      • set_rewrite_highlight(verts, edges)
      • clear_rewrite_highlight()
      • is_vertex_highlighted(v) / is_edge_highlighted(e)
    • VItem.refresh uses these flags to draw highlighted vertices with a thicker, accent-colored outline (different colors for light/dark mode).
    • EItem.refresh similarly draws highlighted edges with a thicker, accent-colored pen while preserving existing styling (e.g. Hadamard dash patterns).
  • Behavior

    • Selecting the first step (START) clears all highlighting.
    • Selecting a later step highlights exactly the changes between that step and the previous one.
    • As new rewrite steps are added or the user navigates through the proof, the highlight automatically updates to match the currently selected step.

You can verify the behavior by opening a proof, applying a few rewrite steps, and moving up and down the step list: only the nodes and edges affected by the selected rewrite should be emphasized.

@boldar99
Copy link
Collaborator

Thanks for the PR,
I have done some basic testing and this doesn't seem to be working properly; see the video below.
Also the lint checks are failing.

Screen.Recording.2026-02-26.at.17.47.29.mov

@dorakingx
Copy link
Author

Thanks for the detailed feedback and the video! It was incredibly helpful.
I have thoroughly investigated the issues and made the following updates to ensure the highlighting works perfectly:

  1. Fixed the Undo bug:
    The highlights now clear immediately and correctly when performing an Undo. I explicitly wired the undo stack to trigger the UI refresh (move_to_step), ensuring the visual state always matches the internal proof state.

  2. Fixed semantic highlighting (Option C: wrong nodes lighting up):
    Previously, pyzx aggressively shuffled vertex IDs during rewrites, which tricked the naive ID-based diffing into highlighting unrelated wires. To fix this, I abandoned ID diffing and implemented a robust coordinate-based tracking (qubit, row).
    Now, ONLY the semantically changed nodes and their incident edges light up. I have ensured this robust logic is applied across all interaction paths:

    • Manual rule application from the menu
    • Drag-and-drop operations
    • The Magic Wand tool
  3. Fixed lint checks:
    All linting and formatting errors have been fully resolved.

Note on the temporary layout overlap (Drag-and-Drop):
During testing, I noticed that immediately after applying a complex rewrite (like Pivot) via drag-and-drop, the nodes temporarily overlap. However, if you trigger an Undo and Redo, the graph layout is restored perfectly. Based on the logs, the underlying graph state is completely correct; the UI just isn't fully recalculating the layout immediately after the initial drag action. Since this seems to be a pre-existing structural issue with how the scene refreshes after complex rewrites, I left it as-is to avoid expanding the scope of Issue #190. I'd be happy to look into this in a separate PR if needed!

I have attached a new video showing the corrected highlighting behavior across different operations. Could you please take another look when you have time?

Screen.Recording.2026-02-28.at.3.32.50.mov

@lia-approves
Copy link
Collaborator

First, we would like the rewrites to highlight the next change in each proof step, rather than the previous. This is because the person doing the proof knows what they've just changed, but looking back at the proof it's helpful to see what was changed between proof steps.

Instead of highlighting all edges of spiders involved in the latest rewrite rule, the intended behavior is to only highlighted the subset of those spiders' edges that were changed. For example, for spider fusion, the only edge highlighted should be the edge between the two spiders being fused, not their other edges.

Lastly, it should be possible to toggle the highlighting on and off in the GUI settings.

…dent_edges

- Add highlight_match_pairs to Rewrite/AddRewriteStep for MATCH_DOUBLE rules
- In move_to_step, when match pairs exist, find fusion edge via incident_edges(v1) instead of g.edges(v1,v2)
- Pass match pairs from rewrite_action (tree) and proof_panel (drag-drop) for fuse
- Add highlight toggle in settings; update test for next-change semantics

Made-with: Cursor
…tices

- Add highlight_unfuse_verts to Rewrite and AddRewriteStep for unfuse steps
- Unfuse branch: highlight vertices from highlight_unfuse_verts that exist in current graph
- When both split vertices exist (viewing after unfuse), highlight edge between them
- Avoid coordinate-based path for unfuse so extra vertices at same coords are not highlighted

Made-with: Cursor
@dorakingx
Copy link
Author

Thanks again for the excellent feedback and the survey! The guidance on forward-looking highlights and precise edge selection was incredibly helpful and greatly improved the feature.

I have pushed a new update that fully implements your requested design changes, along with a final fix for the Magic Wand tool. Here is a summary of the latest updates:

  1. Forward Highlighting (Next Change): As requested, the UI now previews the next rewrite. When viewing step i (e.g., START), the graph now highlights the specific vertices and edges that will be modified or consumed in the transition to step i+1.

  2. Precise Edge & Vertex Highlighting: I updated the logic to strictly highlight the affected spiders AND ONLY the subset of edges actually involved in the rewrite. For example, during a Spider Fusion, only the two spiders and the single edge connecting them are highlighted. All external incident edges are left untouched.

  3. GUI Toggle Setting: I have added a checkable action to the application's top menu bar (e.g., under the View menu). Users can now easily toggle the rewrite highlighting on and off whenever they want.

  4. Magic Wand (Unfuse) Fix: I fixed a lingering issue where using the Magic Wand for unfusion occasionally highlighted unrelated nodes due to pyzx vertex ID shuffling. It now accurately tracks and highlights only the selected/split vertices and their specific edges.

I have attached a short video/screenshots demonstrating the refined highlighting across manual operations, drag-and-drop, the Magic Wand, and the new toggle setting in action.

Could you please take another look when you have a chance? Let me know if there's anything else you'd like me to adjust!

Screen.Recording.2026-02-28.at.10.10.58.mov

@RazinShaikh
Copy link
Collaborator

Thanks, it looks better. Before we go ahead and review this further, could you please make your PR as minimal as possible and it changes only what's necessary? Currently it has over 800 new lines and I am not sure if all of those are necessary. Such big PRs are hard to review especially since we are reaching the end of the hackathon.

… comp/remove id; optimize (merge unfuse into highlight_verts, add _edges_between)

Made-with: Cursor
@dorakingx
Copy link
Author

dorakingx commented Feb 28, 2026

Thanks for the feedback. I've reduced the PR so it only includes what's needed for the feature.

What I changed:

  • Removed all coordinate-based tracking (highlight_coords), the GraphDiff-based fallback, and any layout_coords logic.
  • Removed all temporary debug print/logging used during development.
  • Simplified the data model: dropped highlight_unfuse_verts and now use a single highlight_verts path for unfuse, color change, strong complementarity, remove identity, etc. Match-based rules (e.g. Spider Fusion) still use highlight_match_pairs only.
  • Refactored duplicate "edges between two vertices" logic into a single _edges_between helper and removed an unused g_next graph copy.

Current size:

The branch is now ~455 insertions and ~61 deletions vs master (net +394 lines), not 800+. The diff is limited to the files that need to change for forward-looking highlights, the GUI toggle, and the above cleanups.

If you’d like it even smaller, I can trim further (e.g. fewer comments or a more minimal test). Tell me which parts you’d prefer to see reduced and I’ll adjust.

@RazinShaikh
Copy link
Collaborator

Thanks, the size looks more reasonable now. Will have a look at it soon

@RazinShaikh
Copy link
Collaborator

RazinShaikh commented Feb 28, 2026

@dorakingx I am still finding it difficult to understand the changes you have made in the code. Can you explain and summarize what you did? And the lint check is failing right now; can you please fix that?

…nds.py, type ignore setColorScheme in app.py

Made-with: Cursor
@dorakingx
Copy link
Author

dorakingx commented Feb 28, 2026

@RazinShaikh Here is the summary. And I fixed the lint check error.

Summary of changes

What this PR does

This PR adds forward-looking rewrite highlighting in proof mode: when you select a proof step, the graph highlights what will change in the next step (the vertices and edges affected by that rewrite), instead of what changed in the previous one. It also adds a GUI toggle to turn this highlighting on or off, and ensures only the relevant vertices/edges are highlighted (no coordinate drift or unrelated nodes).


1. Highlight behaviour

  • Forward-looking: Step i shows the graph at i and highlights the transition to step i+1 (the next rewrite).
  • Semantic, not structural: We no longer use a generic graph diff. We store a small amount of metadata when a rewrite is applied and use that to highlight:
    • Match-based (e.g. Spider Fusion): we store highlight_match_pairs — the vertex pairs (v1, v2) that were matched. When displaying the step, we highlight those two vertices and only the edge between them.
    • Vertex-based (unfuse, color change, strong complementarity, remove identity, etc.): we store highlight_verts — the vertex IDs involved. We highlight those vertices and, if there are exactly two, only the edge between them; otherwise all incident edges.

So the logic is: “when this step was created, we recorded which vertices (and for fuse, which pair) were involved; when we show this step, we highlight those in the current graph.”


2. Where metadata is set

  • Rewrite tree / drag-and-drop
    In rewrite_action.py, when the “Fuse spiders” rule is applied we set highlight_match_pairs from the match (v1, v2).
    In proof_panel.py, drag-and-drop and double-click handlers set highlight_match_pairs or highlight_verts (e.g. “Fuse spiders” → [(v,w)], “Strong complementarity” → [v,w], “Color change” / “Remove identity” → [v], “unfuse” → list of split vertex IDs).

  • Commands
    AddRewriteStep in commands.py takes optional highlight_match_pairs and highlight_verts and passes them into the new Rewrite stored in the proof model.

  • Proof model
    Each Rewrite in proof.py can carry highlight_match_pairs and highlight_verts. When the user selects a step, ProofStepView.move_to_step uses this metadata to compute the sets of vertices and edges to highlight and calls scene.set_rewrite_highlight(verts, edges).


3. GUI toggle

  • View menu: “Show rewrite highlights” checkable action; toggling it turns highlighting on/off and persists the setting.
  • Preferences: “Highlight rewrite steps” in the General tab (same setting).
  • When the setting is off, move_to_step clears the highlight and does not apply any of the above logic. When the setting is changed (from the menu or Preferences), the current step’s highlight is refreshed so the graph updates immediately.

4. Lint fixes (mypy)

  • proof.py: Resolved no-redef for the edge set used in the vertex-based branch by introducing a single edges_highlight variable and assigning it in the two branches (either from _edges_between or by building the set of incident edges).
  • commands.py: AddRewriteStep.step_view is now typed as ProofStepView instead of QListView, so mypy recognizes move_to_step.
  • app.py: Added # type: ignore[attr-defined] for setColorScheme on QStyleHints so that mypy zxlive passes (stubs may not define it yet).

Files touched (overview)

File Role
zxlive/proof.py Rewrite metadata; move_to_step highlight logic; _edges_between helper; settings check.
zxlive/commands.py AddRewriteStep carries and passes highlight metadata; typed as ProofStepView.
zxlive/proof_panel.py Sets highlight_match_pairs / highlight_verts when creating rewrite steps (drag-drop, double-click, Magic Wand, unfuse).
zxlive/rewrite_action.py Sets highlight_match_pairs for “Fuse spiders” from the rewrite tree.
zxlive/graphscene.py set_rewrite_highlight / clear_rewrite_highlight; scene stores and uses highlighted verts/edges.
zxlive/vitem.py / zxlive/eitem.py Drawing uses scene’s highlight state for vertices and edges.
zxlive/mainwindow.py View menu toggle and refresh_rewrite_highlight().
zxlive/settings.py / zxlive/settings_dialog.py “Highlight rewrite steps” setting and persistence.
test/test_highlight.py Test that step selection and toggle affect highlights correctly.

Copy link
Collaborator

@RazinShaikh RazinShaikh left a comment

Choose a reason for hiding this comment

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

  1. MatchType can be single, double or compound. Your code does not deal with compound. You can look at rewrite_data.py to see which rewrites are compound.
  2. The highlights do not work for custom rewrites
custom-rule.mp4
  1. The highlights should look pretty. It should have better colours and it should look good in light mode, dark mode and all the color schemes.

zxlive/proof.py Outdated
Comment on lines +19 to +27
def _edges_between(g: GraphT, v1: int, v2: int) -> set[ET]:
"""Return the set of edges between v1 and v2 in g."""
out: set[ET] = set()
for e in g.incident_edges(v1):
s, t = g.edge_st(e)
if (s == v1 and t == v2) or (s == v2 and t == v1):
out.add(e)
return out

Copy link
Collaborator

Choose a reason for hiding this comment

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

Isn't something like this already in PyZX?

Copy link
Author

Choose a reason for hiding this comment

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

You’re right, PyZX already provides this functionality.

BaseGraph.edges(s, t) returns exactly the edges between two vertices, so my original implementation was effectively re‑implementing that logic. I’ve updated _edges_between to be a thin wrapper around g.edges(v1, v2) instead of manually iterating over incident_edges, and kept the helper only to make the intent at the call site (“edges between v1 and v2”) explicit.

Copy link
Collaborator

Choose a reason for hiding this comment

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

You don't need a wrapper around the PyZX method. Please just use it directly and delete this method.

Copy link
Author

Choose a reason for hiding this comment

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

I changed it to use PyZX's methods directly.

"""Helper method to apply animation and push the unfuse command to the undo stack.
"""
def _finalize_unfuse(
self, v: VT, new_g: GraphT, extra_vertices: Optional[list[VT]] = None
Copy link
Collaborator

Choose a reason for hiding this comment

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

what is extra vertices and what does it do?

Copy link
Author

@dorakingx dorakingx Mar 2, 2026

Choose a reason for hiding this comment

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

extra_vertices is an optional list of additional vertices created during the unfuse operation (for example, the new W-input/W-output pair in _unfuse_w).

_finalize_unfuse always highlights the original vertex v, and, if extra_vertices is provided, it appends those vertex IDs so that all vertices involved in the unfuse are included in highlight_verts:

verts = [v] + (extra_vertices or [])
cmd = AddRewriteStep(
self.graph_view, new_g, self.step_view, "unfuse",
highlight_verts=verts,
)

So extra_vertices doesn’t affect the rewrite logic itself; it only controls which newly created vertices are highlighted together with v in the proof step UI.

Comment on lines 432 to 437
if self._it is None and self.scene is not None and self.v is not None:
self._it = self.scene.vertex_map[self.v]
assert self._it is not None
self._it = self.scene.vertex_map.get(self.v)
if self._it is None:
raise RuntimeError(f"VItemAnimation: missing target item for vertex {self.v}")
return self._it

Copy link
Collaborator

Choose a reason for hiding this comment

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

What are all the changes in this file starting from line 430 to the end of file for? What's their purpose?

Copy link
Author

Choose a reason for hiding this comment

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

Those changes all live in the VItemAnimation / PhaseItem area and were made for robustness and UI clarity, not to change the semantics of the underlying graph.

Concretely:

  • VItemAnimation.it property (lines ~426–432)

    • Lazily resolves the VItem from scene.vertex_map using get(self.v) and raises a RuntimeError if the vertex has disappeared.
    • This avoids KeyError / AttributeError when an animation is still referenced but the corresponding vertex was removed by a rewrite.
  • _on_state_changed (lines ~434–459)

    • Wraps access to self.it in a try/except and cleanly stops the animation if the target item no longer exists, instead of crashing.
    • Maintains the active_animations set on the target VItem:
      • When starting, it stops any other animations on the same property before adding itself.
      • When stopping, it uses discard so we don’t get KeyError if we’re already gone.
    • This guarantees that at most one animation per property is active and prevents stale animations from lingering.
  • updateCurrentValue (lines ~460–481)

    • Early‑returns if the animation is not in Running state.
    • Again guards self.it with try/except and stops quietly if the target item vanished.
    • Applies the interpolated value to Position / Scale / Rect and calls target.refresh() when appropriate.
    • This is the core of making vertex animations safe even when the underlying graph is being mutated by rewrites.
  • PhaseItem changes (lines ~484–509)

    • Adjusts the phase‑label text color based on dark mode (display_setting.dark_mode) so that phases remain readable on both light and dark themes.
    • The rest of PhaseItem.refresh is unchanged; it still renders the phase label above the vertex.
  • rotate_point and get_w_partner_vitem helpers (lines ~511–526)

    • Small utility functions used by other code paths (e.g. W‑node unfuse) to rotate points and to safely look up the VItem corresponding to a W‑partner vertex, returning None instead of throwing if it’s missing.

In summary, everything from ~430 to the end of the file is about:

  • making vertex animations resilient when rewrites delete or replace vertices, and
  • improving the visual appearance (especially phase labels) across light/dark color schemes,
    without changing the underlying graph semantics.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The explanations you gave here have nothing to do with highlighting diffs which is what this PR is about. Please undo these irrelevant changes

Copy link
Author

Choose a reason for hiding this comment

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

I've restored it to its original state.

@dorakingx dorakingx requested a review from RazinShaikh March 2, 2026 03:59
- Custom rewrite highlights: use last_rewrite_verts for post-apply vertex IDs
- Add identity: forward-looking highlight_edge_pairs for magic wand edge
- Add identity last-step fix reverted (forward-looking semantics)
- highlight_edge_pairs for edge-only highlighting
- Scheme-aware highlight colors (vertex/edge) in settings
- Fixed colors #FFB74D/#FFC107 for all modes (light/dark, all schemes)

Made-with: Cursor
@dorakingx
Copy link
Author

@RazinShaikh

  1. MatchType compound support – Resolved. The code now handles MATCH_COMPOUND rules and passes highlight_verts from the flattened matches_list in rewrite_action.py.
  2. Custom rewrite highlights – Could you share the .zxr file used in your video? That would let us reproduce and debug the issue.
  3. Highlight colors – Resolved. Vertex and edge highlights use fixed colors #FFB74D and #FFC107 across all modes (light/dark) and color schemes for consistent visibility.

@RazinShaikh
Copy link
Collaborator

For custom rewrite, you can create any rewrite and use it but it won't highlight the changes. This is because you need to handle the custom rewrite case when the add rewrites is called with highlight verts

@dorakingx
Copy link
Author

@RazinShaikh
I tried to reproduce the behaviour with a custom rule for phase-gadget-fusion as in your video, but I still cannot get the rule to become selectable in the Custom rules section on the left. Because of that, I have not yet been able to fully verify the highlighting behaviour for custom rewrites.

If you could share the exact .zxr custom rule file you are using (the one from your video), I would be able to load it directly and move on to checking and adjusting the highlighting logic much more quickly.

test.zxg

 {"version": 2, "backend": "multigraph", "variable_types": {}, "scalar": {"power2": 0, "phase": "0"}, "inputs": [24, 25, 0, 1, 2, 3], "outputs": [26, 27, 20, 21, 22, 23], "edata": {}, "auto_simplify": false, "vertices": [{"id": 0, "t": 0, "pos": [1, 0]}, {"id": 1, "t": 0, "pos": [1, 1]}, {"id": 2, "t": 0, "pos": [1, 2]}, {"id": 3, "t": 0, "pos": [1, 3]}, {"id": 4, "t": 1, "pos": [2, 0]}, {"id": 5, "t": 2, "pos": [2, 1]}, {"id": 6, "t": 1, "pos": [2, 2]}, {"id": 7, "t": 1, "pos": [2, 3]}, {"id": 8, "t": 1, "pos": [3, 0]}, {"id": 9, "t": 1, "pos": [3, 1]}, {"id": 10, "t": 2, "pos": [3, 2]}, {"id": 11, "t": 1, "pos": [3, 3]}, {"id": 12, "t": 1, "pos": [4, 0]}, {"id": 13, "t": 2, "pos": [4, 1]}, {"id": 14, "t": 1, "pos": [4, 2]}, {"id": 15, "t": 1, "pos": [4, 3]}, {"id": 16, "t": 2, "pos": [5, 0]}, {"id": 17, "t": 2, "pos": [5, 1]}, {"id": 18, "t": 1, "pos": [5, 2]}, {"id": 19, "t": 2, "pos": [5, 3]}, {"id": 20, "t": 0, "pos": [6, 0]}, {"id": 21, "t": 0, "pos": [6, 1]}, {"id": 22, "t": 0, "pos": [6, 2]}, {"id": 23, "t": 0, "pos": [6, 3]}, {"id": 24, "t": 0, "pos": [0.0, -3.5]}, {"id": 25, "t": 0, "pos": [0.0, -2.0]}, {"id": 26, "t": 0, "pos": [5.0, -3.5]}, {"id": 27, "t": 0, "pos": [5.0, -2.0]}, {"id": 28, "t": 1, "pos": [1.0, -3.5]}, {"id": 29, "t": 1, "pos": [1.0, -2.0]}, {"id": 30, "t": 1, "pos": [3.25, -3.5]}, {"id": 31, "t": 1, "pos": [3.25, -2.0]}, {"id": 32, "t": 2, "pos": [1.0, -2.75]}, {"id": 33, "t": 2, "pos": [3.25, -2.75]}, {"id": 34, "t": 1, "pos": [1.75, -2.75]}, {"id": 35, "t": 1, "pos": [4.0, -2.75]}], "edges": [[0, 4, 1], [1, 5, 1], [2, 6, 1], [3, 7, 1], [4, 5, 1], [4, 8, 1], [4, 29, 1], [5, 9, 1], [5, 10, 1], [6, 10, 1], [7, 11, 1], [8, 12, 1], [8, 31, 1], [9, 13, 2], [10, 14, 1], [11, 15, 1], [12, 16, 1], [12, 17, 1], [12, 31, 1], [13, 17, 2], [13, 18, 2], [14, 17, 1], [14, 18, 1], [15, 18, 1], [15, 19, 1], [16, 20, 1], [17, 21, 1], [18, 22, 1], [19, 23, 1], [24, 28, 1], [25, 29, 1], [26, 30, 1], [27, 31, 1], [28, 30, 1], [28, 32, 1], [29, 31, 1], [29, 32, 1], [30, 33, 1], [31, 33, 1], [32, 34, 1], [33, 35, 1]]}

phase-gadget-fusion.zxr

{"lhs_graph": "{\"version\": 2, \"backend\": \"multigraph\", \"variable_types\": {}, \"scalar\": {\"power2\": 0, \"phase\": \"0\"}, \"inputs\": [1, 2], \"outputs\": [3, 4], \"edata\": {\"(2, 4, 1)\": {\"curve_0\": 0.03125}, \"(1, 3, 1)\": {\"curve_0\": 0.0}}, \"auto_simplify\": false, \"vertices\": [{\"id\": 1, \"t\": 0, \"pos\": [-4.0, -2.0]}, {\"id\": 2, \"t\": 0, \"pos\": [-4.0, -0.5]}, {\"id\": 3, \"t\": 0, \"pos\": [0.5, -2.0]}, {\"id\": 4, \"t\": 0, \"pos\": [0.5, -0.5]}, {\"id\": 5, \"t\": 1, \"pos\": [-3.25, -2.0]}, {\"id\": 6, \"t\": 1, \"pos\": [-3.25, -0.5]}, {\"id\": 7, \"t\": 1, \"pos\": [-1.25, -2.0]}, {\"id\": 8, \"t\": 1, \"pos\": [-1.25, -0.5]}, {\"id\": 9, \"t\": 2, \"pos\": [-3.25, -1.25]}, {\"id\": 10, \"t\": 2, \"pos\": [-1.25, -1.25]}, {\"id\": 11, \"t\": 1, \"pos\": [-2.5, -1.25]}, {\"id\": 12, \"t\": 1, \"pos\": [-0.5, -1.25]}], \"edges\": [[1, 3, 1], [2, 4, 1], [5, 6, 1], [7, 8, 1], [9, 11, 1], [10, 12, 1]]}", "rhs_graph": "{\"version\": 2, \"backend\": \"multigraph\", \"variable_types\": {}, \"scalar\": {\"power2\": 0, \"phase\": \"0\"}, \"inputs\": [0, 1], \"outputs\": [10, 4], \"edata\": {\"(0, 10, 1)\": {\"curve_0\": 0.0}, \"(1, 4, 1)\": {\"curve_0\": 0.03125}}, \"auto_simplify\": false, \"vertices\": [{\"id\": 0, \"t\": 0, \"pos\": [-3.5, -1.5]}, {\"id\": 1, \"t\": 0, \"pos\": [-3.5, 0.0]}, {\"id\": 4, \"t\": 0, \"pos\": [1.0, 0.0]}, {\"id\": 5, \"t\": 1, \"pos\": [-1.75, -1.5]}, {\"id\": 7, \"t\": 1, \"pos\": [-1.75, 0.0]}, {\"id\": 8, \"t\": 1, \"pos\": [-1.0, -0.75]}, {\"id\": 10, \"t\": 0, \"pos\": [1.0, -1.5]}, {\"id\": 11, \"t\": 2, \"pos\": [-1.75, -0.75]}], \"edges\": [[0, 10, 1], [1, 4, 1], [5, 7, 1], [8, 11, 1]]}", "name": "phase-gadget-fusion", "description": ""}
Screen.Recording.2026-03-03.at.0.48.59.mov

@RazinShaikh
Copy link
Collaborator

Here's the phase gadget fusion rule file. But note that no custom rewrites work in general. Also, I get the impression that instead of generically adding highlight verts, etc. based on match_single, match_double and match_compound, you have actually hard-coded this for certain rules. As such, the rules that you have not hard-coded do not highlight. For instance, the remove self loops rule does not highlight among many.

phase-gadget-fusion.zip

@dorakingx
Copy link
Author

I’ve now implemented highlighting in a generic way for all custom rules, not just for specific ones.

As you can see in the video, this works both for:

  • the collaborator’s custom rule rules/phase-gadget-fusion.zxr, and
  • my own custom rule rules/change-z-to-x.zxr.

In both cases the selected LHS region is correctly highlighted after the rewrite is applied, confirming that the new logic handles arbitrary custom rules as intended.

Screen.Recording.2026-03-03.at.15.20.43.mov

@boldar99
Copy link
Collaborator

boldar99 commented Mar 3, 2026

Thanks for participating in the hackathon. We liked the implementation you proposed for this PR, but unfortunately there are still certain issues that need to be resolved before merging. In particular, edges are not highlighted when a rewrite only affects edges (e.g. self-loop removal, removing parallel edges). Also when we save a proof and reopen it, the highlights are lost, which needs to be addressed somehow (e.g. by saving the highlights as well).
While the deadline for the hackathon has passed, we think your implementation has good foundations, and would be happy if you wanted to fix the issues it so that the PR could be merged.

@dorakingx
Copy link
Author

dorakingx commented Mar 3, 2026

I tried self-loop removal and removing parallel edges, but as shown in the video, the highlights appear to be correct. Also, in my environment, the highlights are preserved even when I save the proof and reopen it. Please verify this.

Screen.Recording.2026-03-04.at.0.02.52.mov

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.

4 participants