Skip to content

Commit

Permalink
Enable ZX Live to be run from inside of a notebook.
Browse files Browse the repository at this point in the history
  • Loading branch information
dlyongemallo committed Nov 1, 2023
1 parent 9115e56 commit d443487
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 13 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
79 changes: 79 additions & 0 deletions embed_zxlive_demo.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "dc0b153f-dc2f-447e-82de-6e71b9d389d2",
"metadata": {},
"source": [
"# Demo of embedded ZX Live running inside Jupyter Notebook\n",
"\n",
"First, run the cell below. An instance of ZX Live 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",
"g1 = zx.Graph()\n",
"g1.add_vertex(zx.VertexType.Z, 0, 0)\n",
"g1.add_vertex(zx.VertexType.X, 0, 1)\n",
"g1.add_edge((0, 1))\n",
"g2 = g1.clone()\n",
"zx.draw(g1)\n",
"zx.draw(g2)\n",
"\n",
"zxl = app.get_embedded_app()\n",
"zxl.edit_graph(g1, 'g1')\n",
"zxl.edit_graph(g2, 'g2')"
]
},
{
"cell_type": "markdown",
"id": "88f425a0-d50d-40e0-86cf-eecbc3a27277",
"metadata": {},
"source": [
"After making some edits and saving them from within ZX Live, 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(g1)\n",
"zx.draw(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
}
24 changes: 22 additions & 2 deletions zxlive/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
from PySide6.QtCore import QCommandLineParser
import sys
from .mainwindow import MainWindow
from .common import GraphT
from typing import Optional

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

Expand All @@ -29,6 +31,9 @@ class ZXLive(QApplication):
...
"""

main_window: Optional[MainWindow] = None
is_embedded: bool = False

def __init__(self) -> None:
super().__init__(sys.argv)
self.setApplicationName('ZX Live')
Expand All @@ -47,9 +52,24 @@ def __init__(self) -> None:
for f in parser.positionalArguments():
self.main_window.open_file_from_path(f)

def edit_graph(self, g: GraphT, name: Optional[str] = "Embedded Graph") -> None:
"""Opens a ZX Live window from within a notebook to edit a graph."""
assert self.is_embedded
if not self.main_window:
self.main_window = MainWindow(True)
self.main_window.show()
self.main_window.new_graph(g, name)

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

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


def main() -> None:
"""Main entry point for ZX Live as a standalone app."""
zxl = ZXLive()
zxl.exec_()
40 changes: 29 additions & 11 deletions zxlive/mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,14 @@ class MainWindow(QMainWindow):
rewrite_form: QFormLayout
left_graph: Optional[GraphT]
right_graph: Optional[GraphT]
embedded_graph: Optional[GraphT]

def __init__(self) -> None:
def __init__(self, is_embedded: bool = False) -> None:
super().__init__()
self.settings = QSettings("zxlive", "zxlive")

self.setWindowTitle("zxlive")
self.is_embedded = is_embedded

w = QWidget(self)
w.setLayout(QVBoxLayout())
Expand Down Expand Up @@ -89,15 +91,21 @@ 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"))
# TODO: We should remember if we have saved the diagram before,
# and give an open 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.")
"Closes the window", alt_shortcut=QKeySequence("Ctrl+W"))
if not self.is_embedded:
# TODO: We should remember if we have saved the diagram before,
# and give an option to overwrite this file with a Save action.
self.save_diagram = self._new_action("&Save", self.handle_save_file_action,
QKeySequence.StandardKey.Save,
"Save the diagram by overwriting the previous loaded file.")
else:
self.save_diagram = self._new_action("&Save to notebook", self.handle_save_embedded_graph_action,
QKeySequence.StandardKey.Save,
"Save the diagram back to the notebook.")
self.save_as = self._new_action("Save &as...", self.handle_save_as_action, QKeySequence.StandardKey.SaveAs,
"Opens a file-picker dialog to save the diagram in a chosen file format")

Expand All @@ -106,7 +114,7 @@ def __init__(self) -> None:
file_menu.addAction(open_file)
file_menu.addSeparator()
file_menu.addAction(self.close_action)
file_menu.addAction(self.save_file)
file_menu.addAction(self.save_diagram)
file_menu.addAction(self.save_as)

self.undo_action = self._new_action("Undo", self.undo, QKeySequence.StandardKey.Undo,
Expand Down Expand Up @@ -170,11 +178,12 @@ def __init__(self) -> None:
self.simplify_menu.addAction(action)
self.simplify_menu.menuAction().setVisible(False)

graph = construct_circuit()
self.new_graph(graph)
if not self.is_embedded:
graph = construct_circuit()
self.new_graph(graph)

def _reset_menus(self, has_active_tab: bool) -> None:
self.save_file.setEnabled(has_active_tab)
self.save_diagram.setEnabled(has_active_tab)
self.save_as.setEnabled(has_active_tab)
self.cut_action.setEnabled(has_active_tab)
self.copy_action.setEnabled(has_active_tab)
Expand Down Expand Up @@ -376,6 +385,12 @@ def handle_save_as_action(self) -> bool:
self.tab_widget.setTabText(i,name)
return True

def handle_save_embedded_graph_action(self) -> bool:
assert self.is_embedded and self.embedded_graph is not None
assert self.active_panel is not None and isinstance(self.active_panel, GraphEditPanel)
self.embedded_graph.__dict__.update(self.active_panel.graph.__dict__)
self.active_panel.undo_stack.setClean()
return False

def cut_graph(self) -> None:
assert self.active_panel is not None
Expand Down Expand Up @@ -415,6 +430,9 @@ def new_graph(self, graph: Optional[GraphT] = None, name: Optional[str] = None)
panel = GraphEditPanel(_graph, self.undo_action, self.redo_action)
panel.start_derivation_signal.connect(self.new_deriv)
if name is None: name = "New Graph"
if self.is_embedded:
assert graph is not None
self.embedded_graph = graph
self._new_panel(panel, name)

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

0 comments on commit d443487

Please sign in to comment.