Skip to content
Open
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 @@ -47,6 +47,7 @@ cov.xml

# Sphinx documentation
docs/_build/
docs/demos/out

# Jupyter Notebook
.ipynb_checkpoints
Expand Down
44 changes: 44 additions & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,54 @@ SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build

JUPYTEXT ?= jupytext
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/"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also use sed's insert command to prepend text (slightly simpler expression). When combined with parameter expansion switch as suggested below:

Suggested change
ADD_FILE_NAME_LABEL = "1s/^/.. _$$(basename $$1 .rst):\n\n/"
ADD_FILE_NAME_LABEL = "1i .. _$${1%.rst}:\n"


# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: demos
demos:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This build rule is sufficiently complex to warrant a stand-alone shell script.

Or, given how shell scripts are generally fragile, unmaintainable and unscalable, perhaps switch to something more portable and extensible, like Python? 🙂

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought about it, and it's a good idea. I don't necessarily mind the simplicity of having it like this though. Perhaps leaving it like this for the time being and potentially reevaluate later.

# 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 {} \;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use shell's built-in parameter expansion to avoid calling (external) baseline.


# 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="^%" \;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you'll want to skip processing notebooks under .ipynb_checkpoints/.


# 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/\`\`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 {} \; \
Comment on lines +42 to +57
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you prefer sed, I'd extract these substitution commands in a stand-alone sed script, just to avoid the escape hell.


.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
Expand Down
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
'sphinx_autodoc_typehints',
'sphinx.ext.autosummary',
'sphinx.ext.doctest',
'sphinx_design',
]

templates_path = ['_templates']
Expand Down
279 changes: 279 additions & 0 deletions docs/demos/demos/intro_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
# ---
# 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`

`dwave-gate` lets you easily construct and simulate quantum circuits.

This tutorial guides you through using the `dwave-gate` library to inspect,
construct, and simulate quantum circuits using a performant state-vector
simulator.

## 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.
"""

from dwave.gate.circuit import Circuit
import dwave.gate.operations as ops

###############################################################################
# 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 `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, the Pauli X operator. Note that you can
# instantiate an operation without declaring which qubits it should be applied
# to.

ops.X()

###############################################################################
# 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 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
#
# 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:
# # apply operations here
# ```
#

###############################################################################
# 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.
# * add a qubit with `Circuit.add_qubit`, optionally passing a qubit object
# and/or a register to which to add it.

circuit = Circuit(2)

with circuit.context as reg:
ops.X(reg.q[0])

###############################################################################
# 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
#
# 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 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not working:
image

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wonder why it seemingly works for me but not for you. How are you building the docs? 🤔
image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make demos
make html

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you looking at the rendered docs (i.e., the rst file) or the jupyter notebook? The latter will not render the notebox, but the former should be displayed as above, rendering the docs nicely.

Copy link
Contributor

@JoelPasvolsky JoelPasvolsky Jun 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By "rendered docs" I mean the HTML. I get the same whether I use make html or I do sphinx-build -b html after running make demos.

image

If you don't see this too, I will try looking into it either tomorrow or early next week

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not working for me as well (in the jupyter notebook).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strange. @JoelPasvolsky seemed to get it to work when building everything in Codespaces and suspected it had to do with his Ubuntu version. Since it seems to be causing issues I'll look into it, but as long as the demos are built correctly in the live docs I'd think it's fine.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recently updated my Ubuntu so if it's needed to help debug I can run locally again and see if it now works

# 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.
# :::
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# :::
#

image


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)

###############################################################################
# You can access all the operations in a circuit using the `Circuit.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)

###############################################################################
# :::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. 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

###############################################################################
# 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)

with circuit.context as (q, c):
ops.Hadamard(q[0])
ops.CNOT(q[0], q[1])

###############################################################################
# You can now simulate the circuit, updating 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.
#
# The circuit above can be reused 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
# 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])
# # 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 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 measurement.

print(circuit)

###############################################################################
# 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 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 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.
# :::

m.state

###############################################################################
# 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, you can calculate the expected value of the measurement based on a
# specific number of samples.

m.expval(num_samples=10000)

""

20 changes: 20 additions & 0 deletions docs/demos/index.rst
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Documentation
:maxdepth: 1

reference/index
demos/index
release_notes

.. toctree::
Expand Down
6 changes: 6 additions & 0 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +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.7.3
nbconvert==7.4.0
jupytext==1.14.5
reno[sphinx]==3.5.0
1 change: 1 addition & 0 deletions docs/sdk_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ dwave-gate
:maxdepth: 1

reference/index
demos/index
release_notes

.. toctree::
Expand Down
Loading