Skip to content

Commit

Permalink
Merge pull request #161 from dlyongemallo/enable_zxlive_from_notebook
Browse files Browse the repository at this point in the history
Enable ZX Live to be run from inside of a notebook.
  • Loading branch information
jvdwetering authored Nov 15, 2023
2 parents 2237125 + 3fa4f73 commit 06fbad8
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ dist
*.qasm
.env
*.zxr
.ipynb_checkpoints
77 changes: 77 additions & 0 deletions embedded_zxlive_demo.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "dc0b153f-dc2f-447e-82de-6e71b9d389d2",
"metadata": {},
"source": [
"# Demo of embedded ZXLive running inside Jupyter Notebook\n",
"\n",
"First, run the cell below. An instance of ZXLive will open, with two identical graphs."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0e45e8da-f33b-4d13-8327-e394ae096ed9",
"metadata": {},
"outputs": [],
"source": [
"%gui qt6\n",
"from zxlive import app\n",
"\n",
"import pyzx as zx\n",
"\n",
"g = zx.Graph()\n",
"g.add_vertex(zx.VertexType.Z, 0, 0)\n",
"g.add_vertex(zx.VertexType.X, 0, 1)\n",
"g.add_edge((0, 1))\n",
"zx.draw(g)\n",
"\n",
"zxl = app.get_embedded_app()\n",
"zxl.edit_graph(g, 'g1')\n",
"zxl.edit_graph(g, 'g2')"
]
},
{
"cell_type": "markdown",
"id": "88f425a0-d50d-40e0-86cf-eecbc3a27277",
"metadata": {},
"source": [
"After making some edits and saving them from within ZXLive, run the following cell to see the changes."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0a0a8a17-1795-4b13-aedf-30b346388e23",
"metadata": {},
"outputs": [],
"source": [
"zx.draw(zxl.get_copy_of_graph('g1'))\n",
"zx.draw(zxl.get_copy_of_graph('g2'))"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
26 changes: 23 additions & 3 deletions zxlive/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
from PySide6.QtGui import QIcon
import sys
from .mainwindow import MainWindow
from .common import get_data
from .common import get_data, GraphT
from typing import Optional

#sys.path.insert(0, '../pyzx') # So that it can find a local copy of pyzx

Expand All @@ -39,6 +40,8 @@ class ZXLive(QApplication):
...
"""

main_window: Optional[MainWindow] = None

def __init__(self) -> None:
super().__init__(sys.argv)
self.setApplicationName('ZXLive')
Expand All @@ -62,9 +65,26 @@ def __init__(self) -> None:
for f in parser.positionalArguments():
self.main_window.open_file_from_path(f)

def edit_graph(self, g: GraphT, name: str) -> None:
"""Opens a ZXLive window from within a notebook to edit a graph."""
if not self.main_window:
self.main_window = MainWindow()
self.main_window.show()
self.main_window.open_graph_from_notebook(g, name)

def get_copy_of_graph(self, name: str) -> GraphT:
"""Returns a copy of the graph which has the given name."""
return self.main_window.get_copy_of_graph(name)

def main() -> None:
"""Main entry point for ZXLive"""

def get_embedded_app() -> ZXLive:
"""Main entry point for ZXLive as an embedded app inside a jupyter notebook."""
app = QApplication.instance() or ZXLive()
app.__class__ = ZXLive
return app


def main() -> None:
"""Main entry point for ZXLive as a standalone app."""
zxl = ZXLive()
zxl.exec_()
4 changes: 4 additions & 0 deletions zxlive/base_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ def clear_graph(self) -> None:
cmd = SetGraph(self.graph_view, empty_graph)
self.undo_stack.push(cmd)

def replace_graph(self, graph: GraphT) -> None:
cmd = SetGraph(self.graph_view, graph)
self.undo_stack.push(cmd)

def select_all(self) -> None:
self.graph_scene.select_all()

Expand Down
30 changes: 23 additions & 7 deletions zxlive/mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,7 @@


class MainWindow(QMainWindow):
"""A simple window containing a single `GraphView`
This is just an example, and should be replaced with
something more sophisticated.
"""
"""The main window of the ZXLive application."""

edit_panel: GraphEditPanel
proof_panel: ProofPanel
Expand Down Expand Up @@ -94,13 +91,13 @@ def __init__(self) -> None:
menu = self.menuBar()

new_graph = self._new_action("&New", self.new_graph, QKeySequence.StandardKey.New,
"Create a new tab with an empty graph", alt_shortcut = QKeySequence.StandardKey.AddTab)
"Create a new tab with an empty graph", alt_shortcut=QKeySequence.StandardKey.AddTab)
open_file = self._new_action("&Open...", self.open_file, QKeySequence.StandardKey.Open,
"Open a file-picker dialog to choose a new diagram")
self.close_action = self._new_action("Close", self.handle_close_action, QKeySequence.StandardKey.Close,
"Closes the window", alt_shortcut = QKeySequence("Ctrl+W"))
"Closes the window", alt_shortcut=QKeySequence("Ctrl+W"))
# TODO: We should remember if we have saved the diagram before,
# and give an open to overwrite this file with a Save action
# and give an option to overwrite this file with a Save action.
self.save_file = self._new_action("&Save", self.handle_save_file_action, QKeySequence.StandardKey.Save,
"Save the diagram by overwriting the previous loaded file.")
self.save_as = self._new_action("Save &as...", self.handle_save_as_action, QKeySequence.StandardKey.SaveAs,
Expand Down Expand Up @@ -449,6 +446,25 @@ def new_graph(self, graph: Optional[GraphT] = None, name: Optional[str] = None)
if name is None: name = "New Graph"
self._new_panel(panel, name)

def open_graph_from_notebook(self, graph: GraphT, name: str = None) -> None:
"""Opens a ZXLive window from within a notebook to edit a graph.
Replaces the graph in an existing tab if it has the same name."""
# TODO: handle multiple tabs with the same name somehow
for i in range(self.tab_widget.count()):
if self.tab_widget.tabText(i) == name or self.tab_widget.tabText(i) == name + "*":
self.tab_widget.setCurrentIndex(i)
self.active_panel.replace_graph(graph)
return
self.new_graph(copy.deepcopy(graph), name)

def get_copy_of_graph(self, name: str):
# TODO: handle multiple tabs with the same name somehow
for i in range(self.tab_widget.count()):
if self.tab_widget.tabText(i) == name or self.tab_widget.tabText(i) == name + "*":
return copy.deepcopy(self.tab_widget.widget(i).graph_scene.g)
return None

def new_rule_editor(self, rule: Optional[CustomRule] = None, name: Optional[str] = None) -> None:
if rule is None:
graph1 = GraphT()
Expand Down

0 comments on commit 06fbad8

Please sign in to comment.