From 5e181a6ada78db6d0e615ffa4fea0aa016d83438 Mon Sep 17 00:00:00 2001 From: Theodor Isacsson Date: Tue, 6 Jun 2023 16:53:37 -0700 Subject: [PATCH 1/6] Add first demos page --- .gitignore | 1 + docs/Makefile | 26 +++++++ docs/conf.py | 1 + docs/demos/demos/intro_demo.py | 134 +++++++++++++++++++++++++++++++++ docs/demos/index.rst | 20 +++++ docs/index.rst | 1 + docs/requirements.txt | 2 + docs/sdk_index.rst | 1 + make.bat | 1 + makefile | 2 + 10 files changed, 189 insertions(+) create mode 100644 docs/demos/demos/intro_demo.py create mode 100644 docs/demos/index.rst diff --git a/.gitignore b/.gitignore index eaad989..1c15b68 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,7 @@ cov.xml # Sphinx documentation docs/_build/ +docs/demos/out # Jupyter Notebook .ipynb_checkpoints diff --git a/docs/Makefile b/docs/Makefile index d4bb2cb..d387b3a 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -8,10 +8,36 @@ SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = _build +JUPYTEXT ?= jupytext +NBCONVERT ?= jupyter nbconvert +DEMOFOLDER = demos/demos +OUTFOLDER = demos/out + +ADD_DOWNLOAD_BUTTONS ="$\ +\$$a$\ +:download:\`Download Python source code \<$$(basename $$1 .rst).py\>\`\n\n$\ +:download:\`Download as Jupyter notebook \<$$(basename $$1 .rst).ipynb\>\`\n" + # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) +# 0. create the output folder and copy all Python source files over to it +# 1. find all Python demos and convert them to Jupyter notebooks +# 2. find all Jupyter notebooks and convert them into Sphinx rst files +# 3. find all rst files and add the file name as a label at the top +# 4. find all rst files and add the download links to the bottom +.PHONY: demos +demos: + mkdir -p $(OUTFOLDER) + cp -a $(DEMOFOLDER)/. $(OUTFOLDER) + + find $(DEMOFOLDER) -name '*.py' -exec sh -c '$(JUPYTEXT) --to ipynb $$1 --output "$(OUTFOLDER)/$$(basename $$1 .py).ipynb"' sh {} \; + find $(OUTFOLDER) -name '*.ipynb' -exec $(NBCONVERT) {} --to rst --output-dir=$(OUTFOLDER) --execute \; + find $(OUTFOLDER) -name '*.rst' -exec sh -c 'sed -i "1s/^/.. _$$(basename $$1 .rst):\n\n/" $$1' sh {} \; + + find $(OUTFOLDER) -name '*.rst' -exec sh -c 'sed -i -e $(ADD_DOWNLOAD_BUTTONS) $$1' sh {} \; + .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new diff --git a/docs/conf.py b/docs/conf.py index 81c8e46..b467150 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -21,6 +21,7 @@ 'sphinx_autodoc_typehints', 'sphinx.ext.autosummary', 'sphinx.ext.doctest', + 'sphinx_design', ] templates_path = ['_templates'] diff --git a/docs/demos/demos/intro_demo.py b/docs/demos/demos/intro_demo.py new file mode 100644 index 0000000..d262012 --- /dev/null +++ b/docs/demos/demos/intro_demo.py @@ -0,0 +1,134 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: sphinx +# format_version: '1.1' +# jupytext_version: 1.14.5 +# kernelspec: +# display_name: dwave-gate +# language: python +# name: python3 +# --- + +""" +# Beginner's guide to `dwave-gate` + +With `dwave-gate` you can easily contruct and simulate quantum circuits. This tutorial will guide +you through how to use the `dwave-gate` library to inspect different quantum gates, construct +circuits out of them, and then simulate them using our performant state-vector simulator. + +We begin by importing the necessary modules. +""" + +from dwave.gate.circuit import Circuit +import dwave.gate.operations as ops + +############################################################################### +# The `Circuit` object contains the full quantum circuit, with all the operations, measurements, +# qubits and bits. Operations, or gates, are objects that contain information related to that +# specific operation, including its matrix representation, potential decompositions, and how to +# apply it to a circuit. Via the `operations` module, here shortened to `ops`, a variety of quantum +# gates can be accessed. + +ops.X() + +############################################################################### +# As we can see, any operation can be instantiated without declaring which qubits it should be +# applied to. We can also call the matrix property on either the gate class itself or on an instance +# (i.e., an instantiated operation, which can contain additional parameters and/or qubit +# information). + +ops.X.matrix + +############################################################################### +# If the matrix representation is dependent on parameters (e.g., the X-rotation operation) it can +# only be retrieved from an instance. + + +ops.RZ(4.2).matrix + +############################################################################### +# Operations are applied when either the class, or an instance of the class, is called within a +# circuit's context. They can be applied to the circuit in several different ways, as detailed +# below. Both qubits and parameters can be passed either as single values (if supported by the gate) +# or as sequences. Note that different types of gates accept slightly different argument, although +# the qubits can _always_ be passed as sequences via the keyword argument `qubits`. +# +# When instantiated within a circuit context, operations must always be applied to specific qubits +# which in turn must be part of the circuits qubit register. The qubit register can be accessed via +# the named tuple returned by the context manager as `q`, indexing into it to retrieve the +# corresponding qubit. +# +# Let's start by creating a circuit object with 2 qubits in its register, and apply a single X-gate +# to the first qubit. + +circuit = Circuit(2) + +with circuit.context as reg: + ops.X(reg.q[0]) + +############################################################################### +# We can access the qubit register via the `Circuit.qregisters` attribute, which currently should +# contain a single qubit register with 2 qubits in it, but could contain any number of qubit +# registers. If we'd want to, we could add another register with the `Circuit.add_qregister(n)` +# method, where `n` would be the number of qubits in the new register, or add a qubit with +# `Circuit.add_qubit()`, optinally passing a qubit object and/or a register to which to add it. +# +# The registers tuple can also be unwrapped directly into a qubit register `q` (and a classical +# register `c`, but we'll get into that later). +# +# Below follows a few examples for how to apply different gates to the circuit. + + +circuit = Circuit(3) + +with circuit.context as (q, c): + # apply single-qubit gate + ops.Z(q[0]) + # apply single-qubit gate using kwarg + ops.Hadamard(qubits=q[0]) + # apply a parametric gate + ops.RY(4.2, q[1]) + # apply a parametric gate using kwargs + ops.Rotation(parameters=[4.2, 3.1, 2.3], qubits=q[1]) + # apply controlled gate + ops.CNOT(q[0], q[1]) + # apply controlled gate using kwargs + ops.CX(control=q[0], target=q[1]) + # apply controlled qubit gates using slicing (must unpack) + ops.CZ(*q[:2]) + # apply multi-qubit (non-controlled) gates (note tuple) + ops.SWAP((q[0], q[1])) + # apply multi-qubit (non-controlled) gates using kwargs + ops.CSWAP(qubits=(q[0], q[1], q[2])) + # apply multi-qubit (non-controlled) gates using slicing + ops.SWAP(q[:2]) + # apply gate on all qubits in the circuit + ops.Toffoli(q) + +############################################################################### +# Printing the above circuit gives us some general information about the circuit: the type of +# circuit, the number of qubits/bits and the number of operations. + +print(circuit) + +############################################################################### +# We can also access the operations in the circuit via the `Circuit.circuit` attribute. This will +# return a list of all operations which we can iterate over and print in the console. + + +for op in circuit.circuit: + print(op) + +############################################################################### +# Note that e.g., CNOT and CX apply the exact same gate. CNOT is only an alias for CX (so they both +# are labelled as CX in the circuit). There are also other aliases which you can spot either in the +# source code for the operations module or read more about in the documentation. +# + +############################################################################### +# ## Simulating a circuit + +"" diff --git a/docs/demos/index.rst b/docs/demos/index.rst new file mode 100644 index 0000000..4a96bf6 --- /dev/null +++ b/docs/demos/index.rst @@ -0,0 +1,20 @@ +Demos and examples +================== + +``dwave-gate`` demonstrations and examples. + + +Tutorials +--------- + +.. card:: Beginner's guide to ``dwave-gate`` + :link: intro_demo + :link-type: ref + + Tutorial on how to use ``dwave-gate`` to construct and simulate quantum circuits. + +.. toctree:: + :maxdepth: 1 + :hidden: + + out/intro_demo diff --git a/docs/index.rst b/docs/index.rst index c645ca4..acd81c3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -18,6 +18,7 @@ Documentation :maxdepth: 1 reference/index + demos/index release_notes .. toctree:: diff --git a/docs/requirements.txt b/docs/requirements.txt index 4406344..c3de4a0 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,3 +2,5 @@ docutils==0.17.1 sphinx==5.3.0 sphinx-rtd-theme==1.1.1 sphinx_autodoc_typehints==1.19.5 +nbconvert==7.4.0 +jupytext==1.14.5 \ No newline at end of file diff --git a/docs/sdk_index.rst b/docs/sdk_index.rst index 842c3b1..239bfc9 100644 --- a/docs/sdk_index.rst +++ b/docs/sdk_index.rst @@ -17,6 +17,7 @@ dwave-gate :maxdepth: 1 reference/index + demos/index release_notes .. toctree:: diff --git a/make.bat b/make.bat index 8f98f5f..b9ecfdf 100644 --- a/make.bat +++ b/make.bat @@ -41,6 +41,7 @@ GOTO :EOF :clean-docs CALL .\docs\make.bat clean + IF EXIST "docs/demos/out/" RMDIR /S /Q "docs/demos/out/" GOTO :EOF :format diff --git a/makefile b/makefile index 27ae0f1..a87e2f7 100644 --- a/makefile +++ b/makefile @@ -57,11 +57,13 @@ coverage: .PHONY: docs docs: + make -C docs/ demos make -C docs/ html .PHONY: clean-docs clean-docs: make -C docs/ clean + rm -rf docs/demos/out/ .PHONY: format format: From 693f3c2b64e1207d5a0955bac2be2ced4f19f07a Mon Sep 17 00:00:00 2001 From: Theodor Isacsson Date: Mon, 12 Jun 2023 15:20:00 -0700 Subject: [PATCH 2/6] Apply suggestions from code review Co-authored-by: Joel Pasvolsky <34041130+JoelPasvolsky@users.noreply.github.com> --- docs/Makefile | 4 +- docs/demos/demos/intro_demo.py | 103 +++++++++++++++++++-------------- docs/demos/index.rst | 4 +- docs/requirements.txt | 6 +- 4 files changed, 69 insertions(+), 48 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index d387b3a..9e5cd9f 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -16,7 +16,7 @@ OUTFOLDER = demos/out ADD_DOWNLOAD_BUTTONS ="$\ \$$a$\ :download:\`Download Python source code \<$$(basename $$1 .rst).py\>\`\n\n$\ -:download:\`Download as Jupyter notebook \<$$(basename $$1 .rst).ipynb\>\`\n" +:download:\`Download as Jupyter Notebook \<$$(basename $$1 .rst).ipynb\>\`\n" # Put it first so that "make" without argument is like "make help". help: @@ -33,7 +33,7 @@ demos: cp -a $(DEMOFOLDER)/. $(OUTFOLDER) find $(DEMOFOLDER) -name '*.py' -exec sh -c '$(JUPYTEXT) --to ipynb $$1 --output "$(OUTFOLDER)/$$(basename $$1 .py).ipynb"' sh {} \; - find $(OUTFOLDER) -name '*.ipynb' -exec $(NBCONVERT) {} --to rst --output-dir=$(OUTFOLDER) --execute \; + find $(OUTFOLDER) -name '*.ipynb' -exec $(NBCONVERT) {} --to rst --output-dir=$(OUTFOLDER) --execute --RegexRemovePreprocessor.patterns="^%" \; find $(OUTFOLDER) -name '*.rst' -exec sh -c 'sed -i "1s/^/.. _$$(basename $$1 .rst):\n\n/" $$1' sh {} \; find $(OUTFOLDER) -name '*.rst' -exec sh -c 'sed -i -e $(ADD_DOWNLOAD_BUTTONS) $$1' sh {} \; diff --git a/docs/demos/demos/intro_demo.py b/docs/demos/demos/intro_demo.py index d262012..a6f57a0 100644 --- a/docs/demos/demos/intro_demo.py +++ b/docs/demos/demos/intro_demo.py @@ -13,32 +13,40 @@ # --- """ -# Beginner's guide to `dwave-gate` +# Beginner's Guide to `dwave-gate` -With `dwave-gate` you can easily contruct and simulate quantum circuits. This tutorial will guide -you through how to use the `dwave-gate` library to inspect different quantum gates, construct -circuits out of them, and then simulate them using our performant state-vector simulator. +`dwave-gate` lets you easily construct and simulate quantum circuits. -We begin by importing the necessary modules. +This tutorial guides you through using the `dwave-gate` library to inspect, +construct circuits from, and simulate quantum gates using a performant +state-vector simulator. + +## Circuits and Operations +Begin by importing the necessary modules. + +* `Circuit` objects contain the full quantum circuit, with all the operations, + measurements, qubits and bits. +* Operations, or gates, are objects that contain information related to a + particular operation, including its matrix representation, potential + decompositions, and how to apply it to a circuit. """ from dwave.gate.circuit import Circuit import dwave.gate.operations as ops ############################################################################### -# The `Circuit` object contains the full quantum circuit, with all the operations, measurements, -# qubits and bits. Operations, or gates, are objects that contain information related to that -# specific operation, including its matrix representation, potential decompositions, and how to -# apply it to a circuit. Via the `operations` module, here shortened to `ops`, a variety of quantum -# gates can be accessed. +# You can use the `operations` module (shortened above to `ops`) to access a +# variety of quantum gates; for example, a Pauli X operator. ops.X() ############################################################################### -# As we can see, any operation can be instantiated without declaring which qubits it should be -# applied to. We can also call the matrix property on either the gate class itself or on an instance -# (i.e., an instantiated operation, which can contain additional parameters and/or qubit -# information). +# Notice above that an operation can be instantiated without declaring which qubits +# it should be applied to. +# +# The matrix property can be accessed either via the gate class itself or an +# instance (i.e., an instantiated operation, which can contain additional +# parameters and qubit information). ops.X.matrix @@ -50,19 +58,25 @@ ops.RZ(4.2).matrix ############################################################################### -# Operations are applied when either the class, or an instance of the class, is called within a -# circuit's context. They can be applied to the circuit in several different ways, as detailed -# below. Both qubits and parameters can be passed either as single values (if supported by the gate) -# or as sequences. Note that different types of gates accept slightly different argument, although -# the qubits can _always_ be passed as sequences via the keyword argument `qubits`. +# ## Circuit Context +# Operations are applied by calling, within the context of a circuit, either a +# class or an instance of a class. # -# When instantiated within a circuit context, operations must always be applied to specific qubits -# which in turn must be part of the circuits qubit register. The qubit register can be accessed via -# the named tuple returned by the context manager as `q`, indexing into it to retrieve the -# corresponding qubit. +# You can apply operations to a circuit in several different ways, as demonstrated +# below. You can pass both qubits and parameters as either single values (when +# supported by the gate) or sequences. Note that different types of gates accept +# slightly different arguments, although you can _always_ pass the qubits as +# sequences via the keyword argument `qubits`. # -# Let's start by creating a circuit object with 2 qubits in its register, and apply a single X-gate -# to the first qubit. +# Always apply any operations you instantiate within a circuit context to +# specific qubits in the circuit's qubit register. You can access the qubit +# register via the named tuple returned by the context manager as `q`, indexing +# into it to retrieve the corresponding qubit. + +############################################################################### +# ## Registers +# This example starts by creating a circuit object with two qubits in its +# register and applying a single X gate to the first qubit. circuit = Circuit(2) @@ -70,16 +84,22 @@ ops.X(reg.q[0]) ############################################################################### -# We can access the qubit register via the `Circuit.qregisters` attribute, which currently should -# contain a single qubit register with 2 qubits in it, but could contain any number of qubit -# registers. If we'd want to, we could add another register with the `Circuit.add_qregister(n)` -# method, where `n` would be the number of qubits in the new register, or add a qubit with -# `Circuit.add_qubit()`, optinally passing a qubit object and/or a register to which to add it. +# You can access the qubit register via the `Circuit.qregisters` attribute. +# +# In the current example, this attribute contains a single qubit register with +# two qubits; it could contain any number of qubit registers. You can # -# The registers tuple can also be unwrapped directly into a qubit register `q` (and a classical -# register `c`, but we'll get into that later). +# * add another register with the `Circuit.add_qregister(n)` method, where `n` +# is the number of qubits in the new register +# * add a qubit with `Circuit.add_qubit()`, optionally passing a qubit object +# and/or a register to which to add it. # -# Below follows a few examples for how to apply different gates to the circuit. +# The registers tuple can also be unwrapped directly into a qubit register `q` (and a +# classical register `c`). + + +############################################################################### +# ## Example: Applying Various gates to a Circuit circuit = Circuit(3) @@ -109,26 +129,23 @@ ops.Toffoli(q) ############################################################################### -# Printing the above circuit gives us some general information about the circuit: the type of -# circuit, the number of qubits/bits and the number of operations. +# Print the circuit above to see general information about it: type of circuit, +# number of qubits/bits, and number of operations. print(circuit) ############################################################################### -# We can also access the operations in the circuit via the `Circuit.circuit` attribute. This will -# return a list of all operations which we can iterate over and print in the console. +# You can also access operations in a circuit using the `Circuit.circuit` +# attribute. The code below iterates over the returned list of all operations. for op in circuit.circuit: print(op) ############################################################################### -# Note that e.g., CNOT and CX apply the exact same gate. CNOT is only an alias for CX (so they both -# are labelled as CX in the circuit). There are also other aliases which you can spot either in the -# source code for the operations module or read more about in the documentation. -# +# Note that the CNOT alias for the controlled NOT gate is labelled CX in the +# circuit. You can find all operation aliases in the source code and documentation +# for the operations module. ############################################################################### # ## Simulating a circuit - -"" diff --git a/docs/demos/index.rst b/docs/demos/index.rst index 4a96bf6..055403c 100644 --- a/docs/demos/index.rst +++ b/docs/demos/index.rst @@ -1,4 +1,4 @@ -Demos and examples +Demos and Examples ================== ``dwave-gate`` demonstrations and examples. @@ -7,7 +7,7 @@ Demos and examples Tutorials --------- -.. card:: Beginner's guide to ``dwave-gate`` +.. card:: Beginner's Guide to ``dwave-gate`` :link: intro_demo :link-type: ref diff --git a/docs/requirements.txt b/docs/requirements.txt index c3de4a0..425ba2d 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,5 +2,9 @@ docutils==0.17.1 sphinx==5.3.0 sphinx-rtd-theme==1.1.1 sphinx_autodoc_typehints==1.19.5 +sphinx-design==0.4.1 +ipykernel==6.23.1 +matplotlib==3.71 nbconvert==7.4.0 -jupytext==1.14.5 \ No newline at end of file +jupytext==1.14.5 +reno[sphinx]==3.5.0 \ No newline at end of file From c3fb20ffbf6225d63d3a0c07c9ccd5e455c4cbff Mon Sep 17 00:00:00 2001 From: Theodor Isacsson Date: Fri, 16 Jun 2023 14:03:23 -0700 Subject: [PATCH 3/6] Add Sphinx hyperlink replacement --- docs/Makefile | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index 9e5cd9f..deb9488 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -13,30 +13,40 @@ NBCONVERT ?= jupyter nbconvert DEMOFOLDER = demos/demos OUTFOLDER = demos/out +# Script to add download buttons to the bottom of the rst files ADD_DOWNLOAD_BUTTONS ="$\ \$$a$\ :download:\`Download Python source code \<$$(basename $$1 .rst).py\>\`\n\n$\ :download:\`Download as Jupyter Notebook \<$$(basename $$1 .rst).ipynb\>\`\n" +# Script to add file names as labels to the top of the rst files +ADD_FILE_NAME_LABEL = "1s/^/.. _$$(basename $$1 .rst):\n\n/" + # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -# 0. create the output folder and copy all Python source files over to it -# 1. find all Python demos and convert them to Jupyter notebooks -# 2. find all Jupyter notebooks and convert them into Sphinx rst files -# 3. find all rst files and add the file name as a label at the top -# 4. find all rst files and add the download links to the bottom .PHONY: demos demos: + # 0. create the output folder and copy all Python source files over to it mkdir -p $(OUTFOLDER) cp -a $(DEMOFOLDER)/. $(OUTFOLDER) + # 1. find all Python demos and convert them to Jupyter notebooks find $(DEMOFOLDER) -name '*.py' -exec sh -c '$(JUPYTEXT) --to ipynb $$1 --output "$(OUTFOLDER)/$$(basename $$1 .py).ipynb"' sh {} \; + + # 2. find all Jupyter notebooks and convert them into Sphinx rst files find $(OUTFOLDER) -name '*.ipynb' -exec $(NBCONVERT) {} --to rst --output-dir=$(OUTFOLDER) --execute --RegexRemovePreprocessor.patterns="^%" \; - find $(OUTFOLDER) -name '*.rst' -exec sh -c 'sed -i "1s/^/.. _$$(basename $$1 .rst):\n\n/" $$1' sh {} \; - find $(OUTFOLDER) -name '*.rst' -exec sh -c 'sed -i -e $(ADD_DOWNLOAD_BUTTONS) $$1' sh {} \; + # 3. find all rst files and, add file name labels, replace major classes/methods with Sphinx hyperlinks, and add download links + find $(OUTFOLDER) -name '*.rst' -exec sh -c 'sed --in-place \ + -e $(ADD_FILE_NAME_LABEL) \ + -e "s/\`\`Circuit\`\`/:class:\`~dwave.gate.circuit.Circuit\`/g" \ + -e "s/\`\`Circuit\.\(\w*\)\`\`/:meth:\`~dwave.gate.circuit.Circuit.\1\`/g" \ + -e "s/\`\`operations\`\`/:mod:\`~dwave.gate.operations\`/g" \ + -e "s/\`\`ops\.\(\w*\)\`\`/:class:\`~dwave.gate.operations.operations.\1\`/g" \ + -e $(ADD_DOWNLOAD_BUTTONS) \ + $$1' sh {} \; \ .PHONY: help Makefile From 0718763110d055f28b59edc90f2c488b6b091a04 Mon Sep 17 00:00:00 2001 From: Theodor Isacsson Date: Thu, 22 Jun 2023 11:09:43 -0700 Subject: [PATCH 4/6] Update demo build --- docs/Makefile | 8 ++ docs/demos/demos/intro_demo.py | 219 ++++++++++++++++++++++++--------- 2 files changed, 170 insertions(+), 57 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index deb9488..ca1fbb9 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -43,8 +43,16 @@ demos: -e $(ADD_FILE_NAME_LABEL) \ -e "s/\`\`Circuit\`\`/:class:\`~dwave.gate.circuit.Circuit\`/g" \ -e "s/\`\`Circuit\.\(\w*\)\`\`/:meth:\`~dwave.gate.circuit.Circuit.\1\`/g" \ + -e "s/\`\`ParametricCircuit\`\`/:class:\`~dwave.gate.circuit.ParametricCircuit\`/g" \ + -e "s/\`\`Operation\`\`/:class:\`~dwave.gate.operations.base.Operation\`/g" \ -e "s/\`\`operations\`\`/:mod:\`~dwave.gate.operations\`/g" \ -e "s/\`\`ops\.\(\w*\)\`\`/:class:\`~dwave.gate.operations.operations.\1\`/g" \ + -e "s/\`\`Measurement\`\`/:mod:\`~dwave.gate.operations.base.Measurement\`/g" \ + -e "s/\`\`ParametricOperation\`\`/:mod:\`~dwave.gate.operations.base.ParametricOperation\`/g" \ + -e "s/\`\`ControlledOperation\`\`/:mod:\`~dwave.gate.operations.base.ControlledOperation\`/g" \ + -e "s/\`\`ParametricControlledOperation\`\`/:mod:\`~dwave.gate.operations.base.ParametricControlledOperation\`/g" \ + -e "s/\`\`simulate\`\`/:mod:\`~dwave.gate.simulator.simulate\`/g" \ + -e "s/\`\`simulator\.\(\w*\)\`\`/:class:\`~dwave.gate.simulator.\1\`/g" \ -e $(ADD_DOWNLOAD_BUTTONS) \ $$1' sh {} \; \ diff --git a/docs/demos/demos/intro_demo.py b/docs/demos/demos/intro_demo.py index a6f57a0..dbc6258 100644 --- a/docs/demos/demos/intro_demo.py +++ b/docs/demos/demos/intro_demo.py @@ -18,22 +18,31 @@ `dwave-gate` lets you easily construct and simulate quantum circuits. This tutorial guides you through using the `dwave-gate` library to inspect, -construct circuits from, and simulate quantum gates using a performant -state-vector simulator. +construct, and simulate quantum circuits using a performant state-vector +simulator. -## Circuits and Operations -Begin by importing the necessary modules. - -* `Circuit` objects contain the full quantum circuit, with all the operations, - measurements, qubits and bits. -* Operations, or gates, are objects that contain information related to a - particular operation, including its matrix representation, potential - decompositions, and how to apply it to a circuit. +## Circuits and operations +Begin with two necessary imports: The `Circuit` class, which will contain the +full quantum circuit with all the operations, measurements, qubits and bits, and +the operations module. The operations, or gates, contain information related to +a particular operation, including its matrix representation, potential +decompositions, and how to apply it to a circuit. """ from dwave.gate.circuit import Circuit import dwave.gate.operations as ops +############################################################################### +# The `Circuit` keeps track of the operations and the logical flow of their +# excecutions. It also stores the qubits, bits and measurments. +# +# When initializing a circuit, the number of qubits and (optionally) the number of +# bits, i.e., classical measurement results containers, need to be declared. More +# qubits and bits can be added using the `Circuit.add_qubit` and `Circuit.add_bit` +# methods. + +circuit = Circuit(num_qubits=2, num_bits=2) + ############################################################################### # You can use the `operations` module (shortened above to `ops`) to access a # variety of quantum gates; for example, a Pauli X operator. @@ -41,42 +50,46 @@ ops.X() ############################################################################### -# Notice above that an operation can be instantiated without declaring which qubits -# it should be applied to. +# Notice above that an operation can be instantiated without declaring which +# qubits it should be applied to. # -# The matrix property can be accessed either via the gate class itself or an -# instance (i.e., an instantiated operation, which can contain additional -# parameters and qubit information). +# The matrix property can be accessed either via the operation class itself or an +# instance of the operation, which additionally can contain parameters and qubit +# information. ops.X.matrix ############################################################################### -# If the matrix representation is dependent on parameters (e.g., the X-rotation operation) it can -# only be retrieved from an instance. - +# If the matrix representation is dependent on parameters (e.g., the rotation operation `ops.RX`) +# it can only be retrieved from an instance. ops.RZ(4.2).matrix ############################################################################### -# ## Circuit Context -# Operations are applied by calling, within the context of a circuit, either a -# class or an instance of a class. +# ## The circuit context # -# You can apply operations to a circuit in several different ways, as demonstrated -# below. You can pass both qubits and parameters as either single values (when -# supported by the gate) or sequences. Note that different types of gates accept -# slightly different arguments, although you can _always_ pass the qubits as -# sequences via the keyword argument `qubits`. +# Operations are applied by calling either a class or an instance of a class +# within the context of the circuit. +# +# ```python +# with circuit.context: +# # apply operations here +# ``` # -# Always apply any operations you instantiate within a circuit context to -# specific qubits in the circuit's qubit register. You can access the qubit -# register via the named tuple returned by the context manager as `q`, indexing -# into it to retrieve the corresponding qubit. ############################################################################### -# ## Registers -# This example starts by creating a circuit object with two qubits in its -# register and applying a single X gate to the first qubit. +# When activating the context, a named tuple containing reference registers to the +# circuit's qubits and classical bits is returned. You can also access the qubit +# registers directly via the `Circuit.qregisters` property, or the reference +# registers containing all the qubits via `Circuit.qubits`. +# +# In the example below, the circuit contains a single qubit register with two +# qubits; it could contain any number of qubit registers. You can +# +# * add another register with the `Circuit.add_qregister` method, where an argument `n` +# is the number of qubits in the new register +# * add a qubit with `Circuit.add_qubit`, optionally passing a qubit object +# and/or a register to which to add it. circuit = Circuit(2) @@ -84,23 +97,27 @@ ops.X(reg.q[0]) ############################################################################### -# You can access the qubit register via the `Circuit.qregisters` attribute. -# -# In the current example, this attribute contains a single qubit register with -# two qubits; it could contain any number of qubit registers. You can -# -# * add another register with the `Circuit.add_qregister(n)` method, where `n` -# is the number of qubits in the new register -# * add a qubit with `Circuit.add_qubit()`, optionally passing a qubit object -# and/or a register to which to add it. -# -# The registers tuple can also be unwrapped directly into a qubit register `q` (and a -# classical register `c`). +# This example created a circuit object with two qubits in its register, applying +# a single X gate to the first qubit. Print the circuit to see general information +# about it: type of circuit, number of qubits/bits, and number of operations. +print(circuit) ############################################################################### -# ## Example: Applying Various gates to a Circuit - +# ## Applying gates to circuits +# +# You can apply operations to a circuit in several different ways, as demonstrated +# in the example below. You can pass both qubits and parameters as either single +# values (when supported by the gate) or sequences. Note that different types of +# gates accept slightly different arguments, although you can _always_ pass the +# qubits as sequences via the keyword argument `qubits`. +# +# :::note +# Always apply any operations you instantiate within a circuit context to +# specific qubits in the circuit's qubit register. You can access the qubit +# register via the named tuple returned by the context manager as `q`, indexing +# into it to retrieve the corresponding qubit. +# ::: circuit = Circuit(3) @@ -129,23 +146,111 @@ ops.Toffoli(q) ############################################################################### -# Print the circuit above to see general information about it: type of circuit, -# number of qubits/bits, and number of operations. +# You can access all the operations in a circuit using the `Circuit.circuit` +# property. The code below iterates over the returned list of all operations that +# have been applied to the circuit. + +for op in circuit.circuit: + print(op) + +############################################################################### +# :::note +# The CNOT alias for the controlled NOT gate is labelled CX in the circuit. You +# can find all operation aliases in the source code and documentation for the +# operations module. +# ::: + +############################################################################### +# ## Simulating a circuit +# +# `dwave-gate` comes with a performant state-vector simulator. It can be called by passing a circuit to the `simulate` method, which will update the quatum state stored in the circuit, accessible via `Circuit.state`. + +from dwave.gate.simulator import simulate + +############################################################################### +# We create a circuit object with 2 qubits and 1 bit in a quantum and classical registers respectively --- the bit is required to store a single qubit measurement --- and then apply a Hadamard gate and a CNOT gate to the circuit. + +circuit = Circuit(2, 1) + +with circuit.context as (q, c): + ops.Hadamard(q[0]) + ops.CNOT(q[0], q[1]) + +############################################################################### +# We can now simulate the circuit, which will update its stored quantum state. + +simulate(circuit) + +############################################################################### +# Printing the state reveals the expected state-vector $\frac{1}{\sqrt{2}}\left[1, +# 0, 0, 1\right]$ corresponding to the state: +# +# $$\vert\psi\rangle = \frac{1}{\sqrt{2}}\left(\vert00\rangle + \vert11\rangle\right)$$ + +circuit.state + +############################################################################### +# ## Measurements +# +# Measurements work like any other operation in dwave-gate. The main difference is that the operation generates a measurement value when simulated which can be stored in the classical register by piping it into a classical bit. +# +# We can reuse the circuit from above by simply unlocking it and appending a `Measurement` to it. + +circuit.unlock() +with circuit.context as (q, c): + m = ops.Measurement(q[1]) | c[0] + +############################################################################### +# :::note +# We stored the measurement instance as `m`, which we can use for post-processing. It's also possible to do this with all other operations in the same way, allowing for multiple identical operation applications. +# ```python +# with circuit.context as q, _: +# single_x_op = ops.X(q[0]) +# # apply the X-gate again to the second qubit +# # using the previously stored operation +# single_x_op(q[1]) +# ``` +# This procedure can also be shortened into a single line for further convience. +# ```python +# ops.CNOT(q[0], q[1])(q[1], q[2])(q[2], q[3]) +# ``` +# ::: + +############################################################################### +# The circuit should now contain 3 operations: a Hadamard, a CNOT and a measurment. print(circuit) ############################################################################### -# You can also access operations in a circuit using the `Circuit.circuit` -# attribute. The code below iterates over the returned list of all operations. +# When simulating this circuit, the measurement will be applied and the measured value will be stored in the classical register. Since a measurement will affect the quantum state, the resulting state will have collapsed into the expected result dependent on the value which has been measured. +simulate(circuit) -for op in circuit.circuit: - print(op) +############################################################################### +# If the measurement result is 0 the state should collapse into $\vert00\rangle$, and if the measurement result is 1 the state should collapse into $\vert11\rangle$. Outputting the measurement value and the state reveals that this is indeed the case. + +print(circuit.bits[0].value) +print(circuit.state) ############################################################################### -# Note that the CNOT alias for the controlled NOT gate is labelled CX in the -# circuit. You can find all operation aliases in the source code and documentation -# for the operations module. +# ## Measurement post-access +# Since we stored the measurement operation in `m`, we can use it to access the state as it was before the measurement. +# +# :::note +# Accessing the state of the circuit along with any measurement post-sampling and state-access is only available for simulators. +# ::: + +m.state ############################################################################### -# ## Simulating a circuit +# We can also sample that same state again using the `Measurement.sample` method, which by default only samples the state once. Here, we request 10 samples. + +m.sample(num_samples=10) + +############################################################################### +# Finally, we can calculate the expected value of the measurment based on a specific number of samples. + +m.expval(num_samples=10000) + +"" + From 2d81f0e649ecb0d7e4dd8553b4690b85986163aa Mon Sep 17 00:00:00 2001 From: Theodor Isacsson Date: Mon, 10 Jul 2023 12:42:27 -0700 Subject: [PATCH 5/6] Apply suggestions from code review Co-authored-by: Joel Pasvolsky <34041130+JoelPasvolsky@users.noreply.github.com> --- docs/demos/demos/intro_demo.py | 115 ++++++++++++++++++++------------- 1 file changed, 69 insertions(+), 46 deletions(-) diff --git a/docs/demos/demos/intro_demo.py b/docs/demos/demos/intro_demo.py index dbc6258..96cf883 100644 --- a/docs/demos/demos/intro_demo.py +++ b/docs/demos/demos/intro_demo.py @@ -21,55 +21,57 @@ construct, and simulate quantum circuits using a performant state-vector simulator. -## Circuits and operations +## Circuits and Operations Begin with two necessary imports: The `Circuit` class, which will contain the full quantum circuit with all the operations, measurements, qubits and bits, and -the operations module. The operations, or gates, contain information related to -a particular operation, including its matrix representation, potential -decompositions, and how to apply it to a circuit. +the `operations` module. """ from dwave.gate.circuit import Circuit import dwave.gate.operations as ops ############################################################################### -# The `Circuit` keeps track of the operations and the logical flow of their -# excecutions. It also stores the qubits, bits and measurments. +# The operations, or gates, contain information related to a particular operation, +# including its matrix representation, potential decompositions, and how to apply +# it to a circuit. # -# When initializing a circuit, the number of qubits and (optionally) the number of -# bits, i.e., classical measurement results containers, need to be declared. More -# qubits and bits can be added using the `Circuit.add_qubit` and `Circuit.add_bit` +# The `Circuit` keeps track of the operations and the logical +# flow of their executions. It also stores the qubits, bits and measurements. +# +# When initializing a circuit, you need to declare the number of qubits and, +# optionally, the number of bits (i.e., classical measurement results containers). +# You can add more qubits and bits using the `Circuit.add_qubit` and `Circuit.add_bit` # methods. circuit = Circuit(num_qubits=2, num_bits=2) ############################################################################### # You can use the `operations` module (shortened above to `ops`) to access a -# variety of quantum gates; for example, a Pauli X operator. +# variety of quantum gates; for example, the Pauli X operator. Note that you can +# instantiate an operation without declaring which qubits it should be applied +# to. ops.X() ############################################################################### -# Notice above that an operation can be instantiated without declaring which -# qubits it should be applied to. -# -# The matrix property can be accessed either via the operation class itself or an +# You can access the matrix property via either the operation class itself or an # instance of the operation, which additionally can contain parameters and qubit # information. ops.X.matrix ############################################################################### -# If the matrix representation is dependent on parameters (e.g., the rotation operation `ops.RX`) -# it can only be retrieved from an instance. +# If the matrix representation of an operator is dependent on parameters (e.g., +# the rotation operation `ops.RX`) it can only be retrieved from an instance. ops.RZ(4.2).matrix ############################################################################### -# ## The circuit context +# ## The Circuit Context # -# Operations are applied by calling either a class or an instance of a class -# within the context of the circuit. +# Operations are applied by calling either an operation class, or an instance of +# an operation class, within the context of the circuit, passing along the +# qubits on which the operation is applied. # # ```python # with circuit.context: @@ -78,16 +80,16 @@ # ############################################################################### -# When activating the context, a named tuple containing reference registers to the -# circuit's qubits and classical bits is returned. You can also access the qubit -# registers directly via the `Circuit.qregisters` property, or the reference -# registers containing all the qubits via `Circuit.qubits`. +# When you activate a context, a named tuple containing reference registers to +# the circuit's qubits and classical bits is returned. You can also access the +# qubit registers directly, via the `Circuit.qregisters` property, or the +# reference registers containing all the qubits, via `Circuit.qubits`. # # In the example below, the circuit contains a single qubit register with two # qubits; it could contain any number of qubit registers. You can # # * add another register with the `Circuit.add_qregister` method, where an argument `n` -# is the number of qubits in the new register +# is the number of qubits in the new register. # * add a qubit with `Circuit.add_qubit`, optionally passing a qubit object # and/or a register to which to add it. @@ -97,25 +99,25 @@ ops.X(reg.q[0]) ############################################################################### -# This example created a circuit object with two qubits in its register, applying +# This has created a circuit object with two qubits in its register, applying # a single X gate to the first qubit. Print the circuit to see general information # about it: type of circuit, number of qubits/bits, and number of operations. print(circuit) ############################################################################### -# ## Applying gates to circuits +# ## Applying Gates to Circuits # # You can apply operations to a circuit in several different ways, as demonstrated # in the example below. You can pass both qubits and parameters as either single -# values (when supported by the gate) or sequences. Note that different types of +# values (when supported by the gate) or as sequences. Note that different types of # gates accept slightly different arguments, although you can _always_ pass the # qubits as sequences via the keyword argument `qubits`. # # :::note # Always apply any operations you instantiate within a circuit context to # specific qubits in the circuit's qubit register. You can access the qubit -# register via the named tuple returned by the context manager as `q`, indexing +# register via the named tuple, returned by the context manager as `q`, indexing # into it to retrieve the corresponding qubit. # ::: @@ -147,8 +149,8 @@ ############################################################################### # You can access all the operations in a circuit using the `Circuit.circuit` -# property. The code below iterates over the returned list of all operations that -# have been applied to the circuit. +# property. The code below iterates over a list of all operations that +# have been applied to the circuit and prints each one separately. for op in circuit.circuit: print(op) @@ -161,14 +163,19 @@ # ::: ############################################################################### -# ## Simulating a circuit +# ## Simulating a Circuit # -# `dwave-gate` comes with a performant state-vector simulator. It can be called by passing a circuit to the `simulate` method, which will update the quatum state stored in the circuit, accessible via `Circuit.state`. +# `dwave-gate` comes with a performant state-vector simulator. You can call it by +# passing a circuit to the `simulate` method, which will update the quantum state +# stored in the circuit, accessible via `Circuit.state`. from dwave.gate.simulator import simulate ############################################################################### -# We create a circuit object with 2 qubits and 1 bit in a quantum and classical registers respectively --- the bit is required to store a single qubit measurement --- and then apply a Hadamard gate and a CNOT gate to the circuit. +# The following example creates a circuit object with 2 qubits and 1 bit in a +# quantum and a classical register respectively---the bit is required to store a +# single qubit measurement---and then applies a Hadamard gate and a CNOT gate to +# the circuit. circuit = Circuit(2, 1) @@ -177,7 +184,7 @@ ops.CNOT(q[0], q[1]) ############################################################################### -# We can now simulate the circuit, which will update its stored quantum state. +# You can now simulate the circuit, updating its stored quantum state. simulate(circuit) @@ -192,9 +199,11 @@ ############################################################################### # ## Measurements # -# Measurements work like any other operation in dwave-gate. The main difference is that the operation generates a measurement value when simulated which can be stored in the classical register by piping it into a classical bit. +# Measurements work like any other operation in `dwave-gate`. The main difference +# is that the operation generates a measurement value when simulated, which can be +# stored in the classical register by piping it into a classical bit. # -# We can reuse the circuit from above by simply unlocking it and appending a `Measurement` to it. +# The circuit above can be reused by simply unlocking it and appending a `Measurement` to it. circuit.unlock() with circuit.context as (q, c): @@ -202,7 +211,10 @@ ############################################################################### # :::note -# We stored the measurement instance as `m`, which we can use for post-processing. It's also possible to do this with all other operations in the same way, allowing for multiple identical operation applications. +# This example stored the measurement instance as `m`, which you can use for +# post-processing. It's also possible to do this with all other operations in +# the same way, allowing for multiple identical operation applications. +# # ```python # with circuit.context as q, _: # single_x_op = ops.X(q[0]) @@ -210,45 +222,56 @@ # # using the previously stored operation # single_x_op(q[1]) # ``` -# This procedure can also be shortened into a single line for further convience. +# +# This procedure can also be shortened into a single line for further convenience. +# # ```python # ops.CNOT(q[0], q[1])(q[1], q[2])(q[2], q[3]) # ``` # ::: ############################################################################### -# The circuit should now contain 3 operations: a Hadamard, a CNOT and a measurment. +# The circuit should now contain 3 operations: a Hadamard, a CNOT and a measurement. print(circuit) ############################################################################### -# When simulating this circuit, the measurement will be applied and the measured value will be stored in the classical register. Since a measurement will affect the quantum state, the resulting state will have collapsed into the expected result dependent on the value which has been measured. +# When simulating this circuit, the measurement is applied and the measured +# value is stored in the classical register. Since a measurement affects the +# quantum state, the resulting state has collapsed into the expected result +# dependent on the value which has been measured. simulate(circuit) ############################################################################### -# If the measurement result is 0 the state should collapse into $\vert00\rangle$, and if the measurement result is 1 the state should collapse into $\vert11\rangle$. Outputting the measurement value and the state reveals that this is indeed the case. +# If the measurement result is 0 the state should have collapsed into $\vert00\rangle$, +# and if the measurement result is 1 the state should have collapsed into $\vert11\rangle$. +# Outputting the measurement value and the state reveals that this is indeed the case. print(circuit.bits[0].value) print(circuit.state) ############################################################################### -# ## Measurement post-access -# Since we stored the measurement operation in `m`, we can use it to access the state as it was before the measurement. +# ## Measurement Post-Access +# Since the measurement operation has been stored in `m`, you can use it to access the +# state as it was before the measurement. # # :::note -# Accessing the state of the circuit along with any measurement post-sampling and state-access is only available for simulators. +# Accessing the state of the circuit, along with any measurement post-sampling and +# state-access, is only available for simulators. # ::: m.state ############################################################################### -# We can also sample that same state again using the `Measurement.sample` method, which by default only samples the state once. Here, we request 10 samples. +# You can also sample that same state again using the `Measurement.sample` method, +# which by default only samples the state once. Here, request 10 samples. m.sample(num_samples=10) ############################################################################### -# Finally, we can calculate the expected value of the measurment based on a specific number of samples. +# Finally, you can calculate the expected value of the measurement based on a +# specific number of samples. m.expval(num_samples=10000) From 9f50c555beb2709ccfd8ef2dbe939fc4988f8f30 Mon Sep 17 00:00:00 2001 From: Theodor Isacsson Date: Mon, 23 Oct 2023 11:49:21 -0700 Subject: [PATCH 6/6] Fix requirements --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 425ba2d..d81bcdf 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -4,7 +4,7 @@ sphinx-rtd-theme==1.1.1 sphinx_autodoc_typehints==1.19.5 sphinx-design==0.4.1 ipykernel==6.23.1 -matplotlib==3.71 +matplotlib==3.7.3 nbconvert==7.4.0 jupytext==1.14.5 reno[sphinx]==3.5.0 \ No newline at end of file