Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable ZX Live to be run from inside of a notebook. #161

Merged
merged 5 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading