From f6522d2483e9ff25c117c4718b9292da2d7fadbd Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 16 Sep 2026 08:05:00 -0400 Subject: [PATCH 1/5] Use PyGraphviz for documentation diagrams Assisted-by: GPT-6 Astra via Codex --- .readthedocs.yaml | 1 - docs/simulators/CircuitSimulator.md | 14 +++++++++----- docs/simulators/UnitarySimulator.md | 21 +++++++++++++------- pyproject.toml | 1 + uv.lock | 30 +++++++++++++++++++++++++++++ 5 files changed, 54 insertions(+), 13 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index a94a028e..9dca6e07 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -13,7 +13,6 @@ build: apt_packages: - cmake - doxygen - - graphviz jobs: pre_build: # Build the C++ API docs before Sphinx starts diff --git a/docs/simulators/CircuitSimulator.md b/docs/simulators/CircuitSimulator.md index f68383de..90023e7d 100644 --- a/docs/simulators/CircuitSimulator.md +++ b/docs/simulators/CircuitSimulator.md @@ -98,15 +98,19 @@ However, due to the nature of decision diagrams, the simulator can generally sample from the output distribution of much larger circuits than can be fully represented in memory. -If [Graphviz](https://www.graphviz.org/) is installed, use -{py:meth}`~mqt.core.dd.VectorDD.to_svg` to export the final decision diagram as -SVG. IPython can display the resulting file in a notebook. The following shows -the default configuration options for the export. +Use {py:meth}`~mqt.core.dd.VectorDD.to_dot` to export the final decision diagram +as DOT, then render it as SVG with [PyGraphviz](https://pygraphviz.github.io/). +PyGraphviz 2 or later wheels bundle Graphviz; source installations require +[Graphviz to be installed separately](https://pygraphviz.github.io/documentation/stable/install.html). +IPython can display the resulting file in a notebook. The following shows the +default configuration options for the export. ```{code-cell} ipython3 from IPython.display import SVG +from pygraphviz import AGraph -dd.to_svg("bell_state.svg", colored=True, edge_labels=False, classic=False, memory=False, format_as_polar=True) +dot = dd.to_dot(colored=True, edge_labels=False, classic=False, memory=False, format_as_polar=True) +AGraph(dot).draw("bell_state.svg", prog="dot", format="svg") SVG(filename="bell_state.svg") ``` diff --git a/docs/simulators/UnitarySimulator.md b/docs/simulators/UnitarySimulator.md index fdb8858c..06cf9841 100644 --- a/docs/simulators/UnitarySimulator.md +++ b/docs/simulators/UnitarySimulator.md @@ -53,6 +53,7 @@ qc.draw(output="mpl", style="iqp") ```{code-cell} ipython3 from IPython.display import SVG +from pygraphviz import AGraph from mqt.core import load from mqt.ddsim import UnitarySimulator @@ -66,7 +67,7 @@ sim.construct() # Get the decision diagram representation of the unitary dd = sim.get_constructed_dd() -dd.to_svg("unitary.svg", colored=True, edge_labels=True, classic=False) +AGraph(dd.to_dot(colored=True, edge_labels=True, classic=False)).draw("unitary.svg", prog="dot", format="svg") SVG(filename="unitary.svg") ``` @@ -99,6 +100,7 @@ qc.draw(output="mpl", style="iqp", wire_order=[1, 0]) ```{code-cell} ipython3 from IPython.display import SVG +from pygraphviz import AGraph from mqt.core import load from mqt.ddsim import UnitarySimulator @@ -112,7 +114,7 @@ sim.construct() # Get the decision diagram representation of the unitary dd = sim.get_constructed_dd() -dd.to_svg("unitary.svg", colored=True, edge_labels=True, classic=False) +AGraph(dd.to_dot(colored=True, edge_labels=True, classic=False)).draw("unitary.svg", prog="dot", format="svg") SVG(filename="unitary.svg") ``` @@ -139,6 +141,7 @@ qc.draw(output="mpl", style="iqp", wire_order=[1, 0]) ```{code-cell} ipython3 from IPython.display import SVG +from pygraphviz import AGraph from mqt.core import load from mqt.ddsim import UnitarySimulator @@ -152,7 +155,7 @@ sim.construct() # Get the decision diagram representation of the unitary dd = sim.get_constructed_dd() -dd.to_svg("unitary.svg", colored=True, edge_labels=True, classic=False) +AGraph(dd.to_dot(colored=True, edge_labels=True, classic=False)).draw("unitary.svg", prog="dot", format="svg") SVG(filename="unitary.svg") ``` @@ -183,6 +186,7 @@ qc.draw(output="mpl", style="iqp", wire_order=list(reversed(range(num_qubits)))) ```{code-cell} ipython3 from IPython.display import SVG +from pygraphviz import AGraph from mqt.core import load from mqt.ddsim import UnitarySimulator @@ -196,7 +200,7 @@ sim.construct() # Get the decision diagram representation of the unitary dd = sim.get_constructed_dd() -dd.to_svg("unitary.svg", colored=True, edge_labels=True, classic=False) +AGraph(dd.to_dot(colored=True, edge_labels=True, classic=False)).draw("unitary.svg", prog="dot", format="svg") SVG(filename="unitary.svg") ``` @@ -228,6 +232,7 @@ qc.draw(output="mpl", style="iqp", wire_order=list(reversed(range(num_qubits)))) ```{code-cell} ipython3 from IPython.display import SVG +from pygraphviz import AGraph from mqt.core import load from mqt.ddsim import UnitarySimulator @@ -241,7 +246,7 @@ sim.construct() # Get the decision diagram representation of the unitary dd = sim.get_constructed_dd() -dd.to_svg("unitary.svg", colored=True, edge_labels=True, classic=False) +AGraph(dd.to_dot(colored=True, edge_labels=True, classic=False)).draw("unitary.svg", prog="dot", format="svg") SVG(filename="unitary.svg") ``` @@ -280,6 +285,7 @@ qc.draw(output="mpl", style="iqp", wire_order=[2, 1, 0]) ```{code-cell} ipython3 from IPython.display import SVG +from pygraphviz import AGraph from mqt.core import load from mqt.ddsim import UnitarySimulator @@ -293,7 +299,7 @@ sim.construct() # Get the decision diagram representation of the unitary dd = sim.get_constructed_dd() -dd.to_svg("unitary.svg", colored=True, edge_labels=True, classic=False) +AGraph(dd.to_dot(colored=True, edge_labels=True, classic=False)).draw("unitary.svg", prog="dot", format="svg") SVG(filename="unitary.svg") ``` @@ -357,6 +363,7 @@ the `backend.run` method when using the Qiskit backend. ```{code-cell} ipython3 from IPython.display import SVG +from pygraphviz import AGraph from mqt.core import load from qiskit import QuantumCircuit @@ -377,7 +384,7 @@ sim.construct() # Get the decision diagram representation of the unitary dd = sim.get_constructed_dd() -dd.to_svg("unitary.svg", colored=True, edge_labels=True, classic=False) +AGraph(dd.to_dot(colored=True, edge_labels=True, classic=False)).draw("unitary.svg", prog="dot", format="svg") SVG(filename="unitary.svg") ``` diff --git a/pyproject.toml b/pyproject.toml index 71363d2c..bc8e21f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,6 +88,7 @@ docs = [ "breathe>=4.36", "furo>=2025.12.19", "myst-nb>=1.4", + "pygraphviz>=2", "qiskit[visualization]>=2.1", "sphinx>=9", "sphinx-autoapi>=3.6", diff --git a/uv.lock b/uv.lock index bd2deaa6..89c9214f 100644 --- a/uv.lock +++ b/uv.lock @@ -1610,6 +1610,7 @@ docs = [ { name = "breathe" }, { name = "furo" }, { name = "myst-nb" }, + { name = "pygraphviz" }, { name = "qiskit", extra = ["visualization"] }, { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, @@ -1666,6 +1667,7 @@ docs = [ { name = "breathe", specifier = ">=4.36" }, { name = "furo", specifier = ">=2025.12.19" }, { name = "myst-nb", specifier = ">=1.4" }, + { name = "pygraphviz", specifier = ">=2" }, { name = "qiskit", extras = ["visualization"], specifier = ">=2.1" }, { name = "sphinx", specifier = ">=9" }, { name = "sphinx-autoapi", specifier = ">=3.6" }, @@ -2340,6 +2342,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, ] +[[package]] +name = "pygraphviz" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/08/44/efd880d59b94ef3b8c881a4c8ddcaa6f30036997b0ae94710c6f32aaa24c/pygraphviz-2.0.1.tar.gz", hash = "sha256:6f9850617f9444517641d2e14dfdb99ae8294cf7657be86c031fee8f7c54f863", size = 81998, upload-time = "2026-07-20T09:59:35.444Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/96/cd843664b7f5886ed0a6abe2d1ece5096226d4a761649ce68f61d5e6e63f/pygraphviz-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9cb8eefde7d02eed48012142b8d0d39ff96d79f4e649263894b52012eb8fadd5", size = 1412294, upload-time = "2026-07-20T09:59:03.883Z" }, + { url = "https://files.pythonhosted.org/packages/69/6c/b7034ad130739964d3909ca289ec75d2fed2771a117df366c9e37e77c14d/pygraphviz-2.0.1-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:3a8773b52f71f1d97b186af7975e8a82f4bdc1353e562d6e55b1f729b855a996", size = 1584744, upload-time = "2026-07-20T09:59:05.378Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b8/59883fb062ab0e222cbcb769a5df037e315e26390ee082110700ef711d66/pygraphviz-2.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:c639182f160986a7fdfad9e6946ab97fb90bc0d658cbacd4feada139e4db91e5", size = 5131332, upload-time = "2026-07-20T09:59:07.106Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8e/ba68c0aaf34f4e12b218c8d52be3e7639e40938ce9e80a5e41e5bf84c9c1/pygraphviz-2.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:db453fccc8d8c0e830dd81e37725a24e31a003e56d21b2af41c4e613b16e820d", size = 5357204, upload-time = "2026-07-20T09:59:08.761Z" }, + { url = "https://files.pythonhosted.org/packages/00/43/e4694b8e925deee427bb2116e826839bc8a63c6e01c1f98671fc80774949/pygraphviz-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:db4ed383701b10c9490131420a5dc9afe2b22e7b40b52795657fca1d7f9a151a", size = 6842379, upload-time = "2026-07-20T09:59:10.336Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ba/e140a189f55ed2f400ab1582ba27ec15eb7325538d35354d0eb858dfb97f/pygraphviz-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9df0588e2db8356eeadcec55edb1cd32cc642dd8b0b0ebfe169bf0567e6cd31a", size = 1412494, upload-time = "2026-07-20T09:59:11.817Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3d/3e6f2f970742289acf09f123f07a8f1168bf5ad61dca3025e678d31c4434/pygraphviz-2.0.1-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:a8b6283075874bf4553081180e59e72470b43c59da5fd7530e9bc4f04a648d51", size = 1584971, upload-time = "2026-07-20T09:59:13.737Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ff/94293746355f27cb8675fb442de52da1bb79e07fca07ebb06be7b397d1e1/pygraphviz-2.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:afb25def99ac094e7b733e9913071217ffc0b3d739ee72e2f3b465241ea64224", size = 5136518, upload-time = "2026-07-20T09:59:15.279Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a4/18cb081de99c495b0270399124da2f23b7f77471ca93d9651297d169f035/pygraphviz-2.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:62c3d76165b283cf70a220de0e22889baee56f8fa3403590840d9eb841f627d2", size = 5362205, upload-time = "2026-07-20T09:59:16.955Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2d/082fdd9b0b2c87a8da0cb280caa68e36808d424fe24a6f47d6928af28049/pygraphviz-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:55d40e8362e5096b9d02f35007caab3246da4a14b72abaf131281dcac24c3dfa", size = 6842560, upload-time = "2026-07-20T09:59:18.466Z" }, + { url = "https://files.pythonhosted.org/packages/a5/0d/35cd48c3ab3494b92f64629df173fb3e12b814d86fcc43c9ff39ec0410c6/pygraphviz-2.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a393abac0af2f427698f98873b19631abf6311aba8eb90a01764c0904d1c842b", size = 1412167, upload-time = "2026-07-20T09:59:20.143Z" }, + { url = "https://files.pythonhosted.org/packages/ff/ee/44b0e01d38c0b5fc727ad30b5e55bc2d3cfb0ece2b7c1696003efbc0e7f8/pygraphviz-2.0.1-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:76802fe84d687e842371e27ff68a3d8015055cf4f3b141ae635ac05b1f7cccd2", size = 1584696, upload-time = "2026-07-20T09:59:21.567Z" }, + { url = "https://files.pythonhosted.org/packages/f7/8e/5ea7c33e0d6ecf6a08166a77da94d1058fa178eac7d73eeab2d4417bb8c3/pygraphviz-2.0.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:5367adcae805c8adbc4f2f0f0f63ba51810674077ca8acf3255f71ce107287cd", size = 5134392, upload-time = "2026-07-20T09:59:23.013Z" }, + { url = "https://files.pythonhosted.org/packages/39/aa/2acddf77d9527c40ac2429114198afc0fca39821b059713ea6953816d733/pygraphviz-2.0.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:bca869f7894fe95cc33db210e7420bce67a3535a9329cb258856977a808350d9", size = 5360045, upload-time = "2026-07-20T09:59:24.544Z" }, + { url = "https://files.pythonhosted.org/packages/23/cd/98a3c1e70bc56455fc14c808db7d38e388f4e772ad38e8dbdb51eca7590e/pygraphviz-2.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:721d9d46c1167d9044a00063374b9e34ebca637dab858b523da38e95188b666e", size = 6842392, upload-time = "2026-07-20T09:59:26.18Z" }, + { url = "https://files.pythonhosted.org/packages/2f/ae/24d1c0984fbb1c2f6231db7e031f7ed22420235575a93f489fc19b143797/pygraphviz-2.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2969794de5128a5d5d2f9213d92ebdc724c60ffccae683c0b4141d1e4b160d78", size = 1412189, upload-time = "2026-07-20T09:59:27.702Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/d1e3c3817e22ae6caaf119b8cecd579b3bcd7718a9fef4176779a1648dd6/pygraphviz-2.0.1-cp314-cp314-macosx_11_0_x86_64.whl", hash = "sha256:fad64151a38dfd3eaf3ab01e3e9d193ab4e7c60cc8e7bc00f6a338e00f69a5d9", size = 1584772, upload-time = "2026-07-20T09:59:29.189Z" }, + { url = "https://files.pythonhosted.org/packages/bc/cf/b22dac556161adf1b90d87c2474eb4259df0f52a7d74f5b6c50b0faf4275/pygraphviz-2.0.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:19092a42b570e579231d6c897476aed8d24b6c0b5a966d4d50ccb470c9778fe8", size = 5133963, upload-time = "2026-07-20T09:59:30.568Z" }, + { url = "https://files.pythonhosted.org/packages/78/d3/9a728b882805db71eb687fe8440df7b2912800856d4619ffe79b1824fe10/pygraphviz-2.0.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:71b13cfacb5ed6e4f5a966bf679e1d383ac70e0274cc74b29ce4b8e7e2709b46", size = 5359616, upload-time = "2026-07-20T09:59:32.24Z" }, + { url = "https://files.pythonhosted.org/packages/f3/7e/e2e90c36770fc7830c1eae81c91c653bbb41cc69c5e9b0af7ae0590f9cc2/pygraphviz-2.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:8f3a37f5abf0873396243634f30f46dcae118a585cbef13554dd3eed0f9c67e0", size = 7063195, upload-time = "2026-07-20T09:59:33.825Z" }, +] + [[package]] name = "pylatexenc" version = "2.11" From b08b43e9e4994c86f571f00f2dd1780bf0bea2d4 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 16 Sep 2026 08:31:00 -0400 Subject: [PATCH 2/5] Remove PyGraphviz installation note Assisted-by: GPT-6 Astra via Codex --- docs/simulators/CircuitSimulator.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/simulators/CircuitSimulator.md b/docs/simulators/CircuitSimulator.md index 90023e7d..db3d9307 100644 --- a/docs/simulators/CircuitSimulator.md +++ b/docs/simulators/CircuitSimulator.md @@ -100,8 +100,6 @@ represented in memory. Use {py:meth}`~mqt.core.dd.VectorDD.to_dot` to export the final decision diagram as DOT, then render it as SVG with [PyGraphviz](https://pygraphviz.github.io/). -PyGraphviz 2 or later wheels bundle Graphviz; source installations require -[Graphviz to be installed separately](https://pygraphviz.github.io/documentation/stable/install.html). IPython can display the resulting file in a notebook. The following shows the default configuration options for the export. From 63303fd761fb30155a7190a4d2b5be61450d196e Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 16 Sep 2026 16:33:09 -0400 Subject: [PATCH 3/5] Add temporary SVG export helpers for documentation Assisted-by: GPT-6 Astra via Codex --- docs/simulators/CircuitSimulator.md | 23 ++++++++++++++---- docs/simulators/UnitarySimulator.md | 37 ++++++++++++++++++----------- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/docs/simulators/CircuitSimulator.md b/docs/simulators/CircuitSimulator.md index db3d9307..499c75e1 100644 --- a/docs/simulators/CircuitSimulator.md +++ b/docs/simulators/CircuitSimulator.md @@ -98,17 +98,30 @@ However, due to the nature of decision diagrams, the simulator can generally sample from the output distribution of much larger circuits than can be fully represented in memory. -Use {py:meth}`~mqt.core.dd.VectorDD.to_dot` to export the final decision diagram -as DOT, then render it as SVG with [PyGraphviz](https://pygraphviz.github.io/). +The following helper uses {py:meth}`~mqt.core.dd.VectorDD.to_dot` and +[PyGraphviz](https://pygraphviz.github.io/) to render the final decision diagram +as SVG. + +```{code-cell} ipython3 +from pygraphviz import AGraph + + +def to_svg(dd, filename, **options): + """Render a decision diagram as SVG with PyGraphviz.""" + AGraph(dd.to_dot(**options)).draw(filename, prog="dot", format="svg") +``` + +This temporary helper will be replaced by MQT Core's `to_svg()` method once +DDSIM requires a version with PyGraphviz support (see +[the tracking issue](https://github.com/munich-quantum-toolkit/ddsim/issues/1013)). + IPython can display the resulting file in a notebook. The following shows the default configuration options for the export. ```{code-cell} ipython3 from IPython.display import SVG -from pygraphviz import AGraph -dot = dd.to_dot(colored=True, edge_labels=False, classic=False, memory=False, format_as_polar=True) -AGraph(dot).draw("bell_state.svg", prog="dot", format="svg") +to_svg(dd, "bell_state.svg", colored=True, edge_labels=False, classic=False, memory=False, format_as_polar=True) SVG(filename="bell_state.svg") ``` diff --git a/docs/simulators/UnitarySimulator.md b/docs/simulators/UnitarySimulator.md index 06cf9841..fd338fa4 100644 --- a/docs/simulators/UnitarySimulator.md +++ b/docs/simulators/UnitarySimulator.md @@ -51,9 +51,24 @@ qc.x(0) qc.draw(output="mpl", style="iqp") ``` +The following helper uses {py:meth}`~mqt.core.dd.MatrixDD.to_dot` and +[PyGraphviz](https://pygraphviz.github.io/) to render decision diagrams as SVG. + ```{code-cell} ipython3 -from IPython.display import SVG from pygraphviz import AGraph + + +def to_svg(dd, filename, **options): + """Render a decision diagram as SVG with PyGraphviz.""" + AGraph(dd.to_dot(**options)).draw(filename, prog="dot", format="svg") +``` + +This temporary helper will be replaced by MQT Core's `to_svg()` method once +DDSIM requires a version with PyGraphviz support (see +[the tracking issue](https://github.com/munich-quantum-toolkit/ddsim/issues/1013)). + +```{code-cell} ipython3 +from IPython.display import SVG from mqt.core import load from mqt.ddsim import UnitarySimulator @@ -67,7 +82,7 @@ sim.construct() # Get the decision diagram representation of the unitary dd = sim.get_constructed_dd() -AGraph(dd.to_dot(colored=True, edge_labels=True, classic=False)).draw("unitary.svg", prog="dot", format="svg") +to_svg(dd, "unitary.svg", colored=True, edge_labels=True, classic=False) SVG(filename="unitary.svg") ``` @@ -100,7 +115,6 @@ qc.draw(output="mpl", style="iqp", wire_order=[1, 0]) ```{code-cell} ipython3 from IPython.display import SVG -from pygraphviz import AGraph from mqt.core import load from mqt.ddsim import UnitarySimulator @@ -114,7 +128,7 @@ sim.construct() # Get the decision diagram representation of the unitary dd = sim.get_constructed_dd() -AGraph(dd.to_dot(colored=True, edge_labels=True, classic=False)).draw("unitary.svg", prog="dot", format="svg") +to_svg(dd, "unitary.svg", colored=True, edge_labels=True, classic=False) SVG(filename="unitary.svg") ``` @@ -141,7 +155,6 @@ qc.draw(output="mpl", style="iqp", wire_order=[1, 0]) ```{code-cell} ipython3 from IPython.display import SVG -from pygraphviz import AGraph from mqt.core import load from mqt.ddsim import UnitarySimulator @@ -155,7 +168,7 @@ sim.construct() # Get the decision diagram representation of the unitary dd = sim.get_constructed_dd() -AGraph(dd.to_dot(colored=True, edge_labels=True, classic=False)).draw("unitary.svg", prog="dot", format="svg") +to_svg(dd, "unitary.svg", colored=True, edge_labels=True, classic=False) SVG(filename="unitary.svg") ``` @@ -186,7 +199,6 @@ qc.draw(output="mpl", style="iqp", wire_order=list(reversed(range(num_qubits)))) ```{code-cell} ipython3 from IPython.display import SVG -from pygraphviz import AGraph from mqt.core import load from mqt.ddsim import UnitarySimulator @@ -200,7 +212,7 @@ sim.construct() # Get the decision diagram representation of the unitary dd = sim.get_constructed_dd() -AGraph(dd.to_dot(colored=True, edge_labels=True, classic=False)).draw("unitary.svg", prog="dot", format="svg") +to_svg(dd, "unitary.svg", colored=True, edge_labels=True, classic=False) SVG(filename="unitary.svg") ``` @@ -232,7 +244,6 @@ qc.draw(output="mpl", style="iqp", wire_order=list(reversed(range(num_qubits)))) ```{code-cell} ipython3 from IPython.display import SVG -from pygraphviz import AGraph from mqt.core import load from mqt.ddsim import UnitarySimulator @@ -246,7 +257,7 @@ sim.construct() # Get the decision diagram representation of the unitary dd = sim.get_constructed_dd() -AGraph(dd.to_dot(colored=True, edge_labels=True, classic=False)).draw("unitary.svg", prog="dot", format="svg") +to_svg(dd, "unitary.svg", colored=True, edge_labels=True, classic=False) SVG(filename="unitary.svg") ``` @@ -285,7 +296,6 @@ qc.draw(output="mpl", style="iqp", wire_order=[2, 1, 0]) ```{code-cell} ipython3 from IPython.display import SVG -from pygraphviz import AGraph from mqt.core import load from mqt.ddsim import UnitarySimulator @@ -299,7 +309,7 @@ sim.construct() # Get the decision diagram representation of the unitary dd = sim.get_constructed_dd() -AGraph(dd.to_dot(colored=True, edge_labels=True, classic=False)).draw("unitary.svg", prog="dot", format="svg") +to_svg(dd, "unitary.svg", colored=True, edge_labels=True, classic=False) SVG(filename="unitary.svg") ``` @@ -363,7 +373,6 @@ the `backend.run` method when using the Qiskit backend. ```{code-cell} ipython3 from IPython.display import SVG -from pygraphviz import AGraph from mqt.core import load from qiskit import QuantumCircuit @@ -384,7 +393,7 @@ sim.construct() # Get the decision diagram representation of the unitary dd = sim.get_constructed_dd() -AGraph(dd.to_dot(colored=True, edge_labels=True, classic=False)).draw("unitary.svg", prog="dot", format="svg") +to_svg(dd, "unitary.svg", colored=True, edge_labels=True, classic=False) SVG(filename="unitary.svg") ``` From 2b375491bae0171afc20980a3eaf9218a7b9a57e Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 17 Sep 2026 00:12:53 -0400 Subject: [PATCH 4/5] Move documentation SVG helper into utils module Assisted-by: GPT-6 Astra via Codex --- docs/conf.py | 1 + docs/simulators/CircuitSimulator.md | 22 ++----------------- docs/simulators/UnitarySimulator.md | 23 ++++++-------------- python/mqt/ddsim/utils.py | 33 +++++++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 36 deletions(-) create mode 100644 python/mqt/ddsim/utils.py diff --git a/docs/conf.py b/docs/conf.py index 68fe6f92..03c26bae 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -130,6 +130,7 @@ def format_url(self, _e: Entry) -> HRef: # ruff:ignore[no-self-use] autoapi_add_toctree_entry = False autoapi_ignore = [ "*/**/_version.py", + "*/mqt/ddsim/utils.py", ] autoapi_options = [ "members", diff --git a/docs/simulators/CircuitSimulator.md b/docs/simulators/CircuitSimulator.md index 499c75e1..eb51432f 100644 --- a/docs/simulators/CircuitSimulator.md +++ b/docs/simulators/CircuitSimulator.md @@ -98,29 +98,11 @@ However, due to the nature of decision diagrams, the simulator can generally sample from the output distribution of much larger circuits than can be fully represented in memory. -The following helper uses {py:meth}`~mqt.core.dd.VectorDD.to_dot` and -[PyGraphviz](https://pygraphviz.github.io/) to render the final decision diagram -as SVG. - -```{code-cell} ipython3 -from pygraphviz import AGraph - - -def to_svg(dd, filename, **options): - """Render a decision diagram as SVG with PyGraphviz.""" - AGraph(dd.to_dot(**options)).draw(filename, prog="dot", format="svg") -``` - -This temporary helper will be replaced by MQT Core's `to_svg()` method once -DDSIM requires a version with PyGraphviz support (see -[the tracking issue](https://github.com/munich-quantum-toolkit/ddsim/issues/1013)). - -IPython can display the resulting file in a notebook. The following shows the -default configuration options for the export. - ```{code-cell} ipython3 from IPython.display import SVG +from mqt.ddsim.utils import to_svg + to_svg(dd, "bell_state.svg", colored=True, edge_labels=False, classic=False, memory=False, format_as_polar=True) SVG(filename="bell_state.svg") diff --git a/docs/simulators/UnitarySimulator.md b/docs/simulators/UnitarySimulator.md index fd338fa4..d973daf0 100644 --- a/docs/simulators/UnitarySimulator.md +++ b/docs/simulators/UnitarySimulator.md @@ -51,27 +51,12 @@ qc.x(0) qc.draw(output="mpl", style="iqp") ``` -The following helper uses {py:meth}`~mqt.core.dd.MatrixDD.to_dot` and -[PyGraphviz](https://pygraphviz.github.io/) to render decision diagrams as SVG. - -```{code-cell} ipython3 -from pygraphviz import AGraph - - -def to_svg(dd, filename, **options): - """Render a decision diagram as SVG with PyGraphviz.""" - AGraph(dd.to_dot(**options)).draw(filename, prog="dot", format="svg") -``` - -This temporary helper will be replaced by MQT Core's `to_svg()` method once -DDSIM requires a version with PyGraphviz support (see -[the tracking issue](https://github.com/munich-quantum-toolkit/ddsim/issues/1013)). - ```{code-cell} ipython3 from IPython.display import SVG from mqt.core import load from mqt.ddsim import UnitarySimulator +from mqt.ddsim.utils import to_svg # Create the simulator circ = load(qc) @@ -118,6 +103,7 @@ from IPython.display import SVG from mqt.core import load from mqt.ddsim import UnitarySimulator +from mqt.ddsim.utils import to_svg # Create the simulator circ = load(qc) @@ -158,6 +144,7 @@ from IPython.display import SVG from mqt.core import load from mqt.ddsim import UnitarySimulator +from mqt.ddsim.utils import to_svg # Create the simulator circ = load(qc) @@ -202,6 +189,7 @@ from IPython.display import SVG from mqt.core import load from mqt.ddsim import UnitarySimulator +from mqt.ddsim.utils import to_svg # Create the simulator circ = load(qc) @@ -247,6 +235,7 @@ from IPython.display import SVG from mqt.core import load from mqt.ddsim import UnitarySimulator +from mqt.ddsim.utils import to_svg # Create the simulator circ = load(qc) @@ -299,6 +288,7 @@ from IPython.display import SVG from mqt.core import load from mqt.ddsim import UnitarySimulator +from mqt.ddsim.utils import to_svg # Create the simulator circ = load(qc) @@ -377,6 +367,7 @@ from mqt.core import load from qiskit import QuantumCircuit from mqt.ddsim import UnitarySimulatorMode, UnitarySimulator +from mqt.ddsim.utils import to_svg qc = QuantumCircuit(3) qc.h(2) diff --git a/python/mqt/ddsim/utils.py b/python/mqt/ddsim/utils.py new file mode 100644 index 00000000..5ea56688 --- /dev/null +++ b/python/mqt/ddsim/utils.py @@ -0,0 +1,33 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Utilities for documentation examples.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from mqt.core.dd import MatrixDD, VectorDD + + +def to_svg(dd: VectorDD | MatrixDD, filename: str, **options: bool) -> None: + """Render a decision diagram as SVG with PyGraphviz. + + Remove once the minimum MQT Core version includes PyGraphviz-backed `to_svg()`. + See https://github.com/munich-quantum-toolkit/ddsim/issues/1013. + + Args: + dd: Decision diagram to render. + filename: Output SVG filename. + **options: Options passed to `dd.to_dot()`. + """ + # PyGraphviz is an optional documentation dependency. + from pygraphviz import AGraph # ruff: ignore[import-outside-top-level] # ty: ignore[unresolved-import] + + AGraph(dd.to_dot(**options)).draw(filename, prog="dot", format="svg") From 4ed3baa6c0280aa978bb7b13fd1c77c4562c0b7a Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 17 Sep 2026 00:43:06 -0400 Subject: [PATCH 5/5] Align SVG rendering notes with Core Assisted-by: GPT-6 Astra via Codex --- docs/simulators/CircuitSimulator.md | 5 +++++ docs/simulators/UnitarySimulator.md | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/docs/simulators/CircuitSimulator.md b/docs/simulators/CircuitSimulator.md index eb51432f..f950b436 100644 --- a/docs/simulators/CircuitSimulator.md +++ b/docs/simulators/CircuitSimulator.md @@ -98,6 +98,11 @@ However, due to the nature of decision diagrams, the simulator can generally sample from the output distribution of much larger circuits than can be fully represented in memory. +Use `to_svg` to render a decision diagram as SVG. It uses +[PyGraphviz](https://pygraphviz.github.io/documentation/stable/install.html) 2 +or later when installed, or the `dot` command otherwise. IPython can display the +resulting file in a notebook. + ```{code-cell} ipython3 from IPython.display import SVG diff --git a/docs/simulators/UnitarySimulator.md b/docs/simulators/UnitarySimulator.md index d973daf0..22306bf6 100644 --- a/docs/simulators/UnitarySimulator.md +++ b/docs/simulators/UnitarySimulator.md @@ -51,6 +51,11 @@ qc.x(0) qc.draw(output="mpl", style="iqp") ``` +Use `to_svg` to render a decision diagram as SVG. It uses +[PyGraphviz](https://pygraphviz.github.io/documentation/stable/install.html) 2 +or later when installed, or the `dot` command otherwise. IPython can display the +resulting file in a notebook. + ```{code-cell} ipython3 from IPython.display import SVG from mqt.core import load