diff --git a/doc/_static/templates/arithmetic/adder.png b/doc/_static/templates/arithmetic/adder.png
new file mode 100644
index 00000000000..bff56c84e7a
Binary files /dev/null and b/doc/_static/templates/arithmetic/adder.png differ
diff --git a/doc/_static/templates/arithmetic/integercomparator.png b/doc/_static/templates/arithmetic/integercomparator.png
new file mode 100644
index 00000000000..9ba2ec3df89
Binary files /dev/null and b/doc/_static/templates/arithmetic/integercomparator.png differ
diff --git a/doc/_static/templates/arithmetic/modexp.png b/doc/_static/templates/arithmetic/modexp.png
new file mode 100644
index 00000000000..862b79d10fb
Binary files /dev/null and b/doc/_static/templates/arithmetic/modexp.png differ
diff --git a/doc/_static/templates/arithmetic/multiplier.png b/doc/_static/templates/arithmetic/multiplier.png
new file mode 100644
index 00000000000..a2323e127c5
Binary files /dev/null and b/doc/_static/templates/arithmetic/multiplier.png differ
diff --git a/doc/_static/templates/arithmetic/outadder.png b/doc/_static/templates/arithmetic/outadder.png
new file mode 100644
index 00000000000..e1bdfff6c8e
Binary files /dev/null and b/doc/_static/templates/arithmetic/outadder.png differ
diff --git a/doc/_static/templates/arithmetic/outmultiplier.png b/doc/_static/templates/arithmetic/outmultiplier.png
new file mode 100644
index 00000000000..12a217f73b6
Binary files /dev/null and b/doc/_static/templates/arithmetic/outmultiplier.png differ
diff --git a/doc/_static/templates/arithmetic/phaseadder.png b/doc/_static/templates/arithmetic/phaseadder.png
new file mode 100644
index 00000000000..b7143dda261
Binary files /dev/null and b/doc/_static/templates/arithmetic/phaseadder.png differ
diff --git a/doc/code/qml_spin.rst b/doc/code/qml_spin.rst
new file mode 100644
index 00000000000..97cc407587d
--- /dev/null
+++ b/doc/code/qml_spin.rst
@@ -0,0 +1,15 @@
+qml.spin
+=========
+
+Overview
+--------
+
+This module contains functions and classes for creating and manipulating Hamiltonians for the spin models.
+
+.. currentmodule:: pennylane.spin
+
+.. automodapi:: pennylane.spin
+ :no-heading:
+ :no-main-docstr:
+ :skip: Lattice
+ :include-all-objects:
diff --git a/doc/development/deprecations.rst b/doc/development/deprecations.rst
index 2f05830d4b8..5bd83558324 100644
--- a/doc/development/deprecations.rst
+++ b/doc/development/deprecations.rst
@@ -16,7 +16,7 @@ Pending deprecations
- Will be removed in v0.39
* The logic for internally switching a device for a different backpropagation
- compatible device is now deprecated, as it was in place for the deprecated `default.qubit.legacy`.
+ compatible device is now deprecated, as it was in place for the deprecated ``default.qubit.legacy``.
- Deprecated in v0.38
- Will be removed in v0.39
diff --git a/doc/index.rst b/doc/index.rst
index be37298567b..85c20538942 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -216,7 +216,8 @@ PennyLane is **free** and **open source**, released under the Apache License, Ve
code/qml_qaoa
code/qml_qchem
code/qml_qnn
-
+ code/qml_spin
+
.. toctree::
:maxdepth: 1
:caption: Internals
diff --git a/doc/introduction/templates.rst b/doc/introduction/templates.rst
index f972735a51c..9593e203830 100644
--- a/doc/introduction/templates.rst
+++ b/doc/introduction/templates.rst
@@ -124,6 +124,45 @@ state preparation is typically used as the first operation.
.. _intro_ref_temp_subroutines:
+Arithmetic templates
+--------------------
+
+Quantum arithmetic templates enable in-place and out-place modular operations such
+as addition, multiplication and exponentiation.
+
+.. gallery-item::
+ :description: :doc:`PhaseAdder <../code/api/pennylane.PhaseAdder>`
+ :figure: _static/templates/arithmetic/phaseadder.png
+
+.. gallery-item::
+ :description: :doc:`Adder <../code/api/pennylane.Adder>`
+ :figure: _static/templates/arithmetic/adder.png
+
+.. gallery-item::
+ :description: :doc:`OutAdder <../code/api/pennylane.OutAdder>`
+ :figure: _static/templates/arithmetic/outadder.png
+
+.. gallery-item::
+ :description: :doc:`Multiplier <../code/api/pennylane.Multiplier>`
+ :figure: _static/templates/arithmetic/multiplier.png
+
+.. gallery-item::
+ :description: :doc:`OutMultiplier <../code/api/pennylane.OutMultiplier>`
+ :figure: _static/templates/arithmetic/outmultiplier.png
+
+.. gallery-item::
+ :description: :doc:`ModExp <../code/api/pennylane.ModExp>`
+ :figure: _static/templates/arithmetic/modexp.png
+
+.. gallery-item::
+ :description: :doc:`IntegerComparator <../code/api/pennylane.IntegerComparator>`
+ :figure: _static/templates/arithmetic/integercomparator.png
+
+
+.. raw:: html
+
+
+
Quantum Chemistry templates
---------------------------
diff --git a/doc/releases/changelog-0.38.0.md b/doc/releases/changelog-0.38.0.md
index 9729959b83a..391f9dbf36d 100644
--- a/doc/releases/changelog-0.38.0.md
+++ b/doc/releases/changelog-0.38.0.md
@@ -4,112 +4,396 @@
New features since last release
-Converting noise models from Qiskit ♻️
+Registers of wires 🧸
-* A new `qml.from_qiskit_noise` method now allows one to convert a Qiskit ``NoiseModel`` to a
- PennyLane ``NoiseModel`` via the Pennylane-Qiskit plugin.
- [(#5996)](https://github.com/PennyLaneAI/pennylane/pull/5996)
+* A new function called `qml.registers` has been added that lets you seamlessly create registers of
+ wires.
+ [(#5957)](https://github.com/PennyLaneAI/pennylane/pull/5957)
+ [(#6102)](https://github.com/PennyLaneAI/pennylane/pull/6102)
-Registers of wires 🌈
+ Using registers, it is easier to build large algorithms and circuits by applying gates and operations
+ to predefined collections of wires. With `qml.registers`, you can create registers of wires by providing
+ a dictionary whose keys are register names and whose values are the number of wires in each register.
-* Set operations are now supported by Wires.
- [(#5983)](https://github.com/PennyLaneAI/pennylane/pull/5983)
+ ```python
+ >>> wire_reg = qml.registers({"alice": 4, "bob": 3})
+ >>> wire_reg
+ {'alice': Wires([0, 1, 2, 3]), 'bob': Wires([4, 5, 6])}
+ ```
-* The representation for `Wires` has now changed to be more copy-paste friendly.
- [(#5958)](https://github.com/PennyLaneAI/pennylane/pull/5958)
+ The resulting data structure of `qml.registers` is a dictionary with the same register names as keys,
+ but the values are `qml.wires.Wires` instances.
-* A new function `qml.registers` has been added, enabling the creation of registers, which are implemented as a dictionary of `Wires` instances.
- [(#5957)](https://github.com/PennyLaneAI/pennylane/pull/5957)
- [(#6102)](https://github.com/PennyLaneAI/pennylane/pull/6102)
+ Nesting registers within other registers can be done by providing a nested dictionary, where the ordering
+ of wire labels is based on the order of appearance and nestedness.
+
+ ```python
+ >>> wire_reg = qml.registers({"alice": {"alice1": 1, "alice2": 2}, "bob": {"bob1": 2, "bob2": 1}})
+ >>> wire_reg
+ {'alice1': Wires([0]), 'alice2': Wires([1, 2]), 'alice': Wires([0, 1, 2]), 'bob1': Wires([3, 4]), 'bob2': Wires([5]), 'bob': Wires([3, 4, 5])}
+ ```
+
+ Since the values of the dictionary are `Wires` instances, their use within quantum circuits is very
+ similar to that of a `list` of integers.
+
+ ```python
+ dev = qml.device("default.qubit")
+
+ @qml.qnode(dev)
+ def circuit():
+ for w in wire_reg["alice"]:
+ qml.Hadamard(w)
+
+ for w in wire_reg["bob1"]:
+ qml.RX(0.1967, wires=w)
+
+ qml.CNOT(wires=[wire_reg["alice1"][0], wire_reg["bob2"][0]])
+
+ return [qml.expval(qml.Y(w)) for w in wire_reg["bob1"]]
+
+ print(qml.draw(circuit)())
+ ```
+
+ ```pycon
+ 0: ──H────────╭●─┤
+ 1: ──H────────│──┤
+ 2: ──H────────│──┤
+ 3: ──RX(0.20)─│──┤
+ 4: ──RX(0.20)─│──┤
+ 5: ───────────╰X─┤
+ ```
+
+ In tandem with `qml.registers`, we've also made the following improvements to `qml.wires.Wires`:
+
+ * `Wires` instances now have a more copy-paste friendly representation when printed.
+ [(#5958)](https://github.com/PennyLaneAI/pennylane/pull/5958)
+
+ ```python
+ >>> from pennylane.wires import Wires
+ >>> w = Wires([1, 2, 3])
+ >>> w
+ Wires([1, 2, 3])
+ ```
+
+ * Python set-based combinations are now supported by `Wires`.
+ [(#5983)](https://github.com/PennyLaneAI/pennylane/pull/5983)
+
+ This new feature unlocks the ability to combine `Wires` instances in the following ways:
+
+ * intersection with `&` or `intersection()`:
+
+ ```python
+ >>> wires1 = Wires([1, 2, 3])
+ >>> wires2 = Wires([2, 3, 4])
+ >>> wires1.intersection(wires2) # or wires1 & wires2
+ Wires([2, 3])
+ ```
+
+ * symmetric difference with `^` or `symmetric_difference()`:
+
+ ```python
+ >>> wires1.symmetric_difference(wires2) # or wires1 ^ wires2
+ Wires([1, 4])
+ ```
+
+ * union with `|` or `union()`:
+
+ ```python
+ >>> wires1.union(wires2) # or wires1 | wires2
+ Wires([1, 2, 3, 4])
+ ```
+
+ * difference with `-` or `difference()`:
+
+ ```python
+ >>> wires1.difference(wires2) # or wires1 - wires2
+ Wires([1])
+ ```
Quantum arithmetic operations 🧮
-* The `qml.Adder` and `qml.PhaseAdder` templates are added to perform in-place modular addition.
+* Several new operator templates have been added to PennyLane that let you perform quantum arithmetic
+ operations.
[(#6109)](https://github.com/PennyLaneAI/pennylane/pull/6109)
-
-* The `qml.Multiplier` and `qml.OutMultiplier` templates are added to perform modular multiplication.
[(#6112)](https://github.com/PennyLaneAI/pennylane/pull/6112)
-
-* The `qml.OutAdder` and `qml.ModExp` templates are added to perform out-of-place modular addition and modular exponentiation.
[(#6121)](https://github.com/PennyLaneAI/pennylane/pull/6121)
+ * `qml.Adder` performs in-place modular addition:
+ :math:`\text{Adder}(k, m)\vert x \rangle = \vert x + k \; \text{mod} \; m\rangle`.
-Creating spin Hamiltonians 🧑🎨
+ * `qml.PhaseAdder` is similar to `qml.Adder`, but it performs in-place modular addition in the Fourier
+ basis.
-* The function ``transverse_ising`` is added to generate transverse-field Ising Hamiltonian.
- [(#6106)](https://github.com/PennyLaneAI/pennylane/pull/6106)
+ * `qml.Multiplier` performs in-place multiplication:
+ :math:`\text{Multiplier}(k, m)\vert x \rangle = \vert x \times k \; \text{mod} \; m \rangle`.
-* The functions ``heisenberg`` and ``fermi_hubbard`` are added to generate Heisenberg and Fermi-Hubbard Hamiltonians respectively.
- [(#6128)](https://github.com/PennyLaneAI/pennylane/pull/6128)
+ * `qml.OutAdder` performs out-place modular addition:
+ :math:`\text{OutAdder}(m)\vert x \rangle \vert y \rangle \vert b \rangle = \vert x \rangle \vert y \rangle \vert b + x + y \; \text{mod} \; m \rangle`.
+
+ * `qml.OutMultiplier` performs out-place modular multiplication:
+ :math:`\text{OutMultiplier}(m)\vert x \rangle \vert y \rangle \vert b \rangle = \vert x \rangle \vert y \rangle \vert b + x \times y \; \text{mod} \; m \rangle`.
+
+ * `qml.ModExp` performs modular exponentiation:
+ :math:`\text{ModExp}(base, m) \vert x \rangle \vert k \rangle = \vert x \rangle \vert k \times base^x \; \text{mod} \; m \rangle`.
+
+ Here is a comprehensive example that performs the following calculation: `(2 + 1) * 3 mod 7 = 2` (or
+ `010` in binary).
+
+ ```python
+ dev = qml.device("default.qubit", shots=1)
+
+ wire_reg = qml.registers({
+ "x_wires": 2, # |x>: stores the result of 2 + 1 = 3
+ "y_wires": 2, # |y>: multiples x by 3
+ "output_wires": 3, # stores the result of (2 + 1) * 3 m 7 = 2
+ "work_wires": 2 # for qml.OutMultiplier
+ })
+
+ @qml.qnode(dev)
+ def circuit():
+ # In-place addition
+ qml.BasisEmbedding(2, wires=wire_reg["x_wires"])
+ qml.Adder(1, x_wires=wire_reg["x_wires"]) # add 1 to wires [0, 1]
+
+ # Out-place multiplication
+ qml.BasisEmbedding(3, wires=wire_reg["y_wires"])
+ qml.OutMultiplier(
+ wire_reg["x_wires"],
+ wire_reg["y_wires"],
+ wire_reg["output_wires"],
+ work_wires=wire_reg["work_wires"],
+ mod=7
+ )
+
+ return qml.sample(wires=wire_reg["output_wires"])
+ ```
+
+ ```
+ >>> circuit()
+ array([0, 1, 0])
+ ```
+
+Converting noise models from Qiskit ♻️
+
+* Convert Qiskit noise models into a PennyLane `NoiseModel` with `qml.from_qiskit_noise`.
+ [(#5996)](https://github.com/PennyLaneAI/pennylane/pull/5996)
+
+ In the last few releases, we've added substantial improvements and new features to the
+ [Pennylane-Qiskit plugin](https://docs.pennylane.ai/projects/qiskit/en/latest/installation.html).
+ With this release, a new `qml.from_qiskit_noise` function allows you to convert a Qiskit noise model
+ into a PennyLane `NoiseModel`. Here is a simple example with two quantum errors that add two different
+ depolarizing errors based on the presence of different gates in the circuit:
+
+ ```python
+ import pennylane as qml
+ import qiskit_aer.noise as noise
+
+ error_1 = noise.depolarizing_error(0.001, 1) # 1-qubit noise
+ error_2 = noise.depolarizing_error(0.01, 2) # 2-qubit noise
+
+ noise_model = noise.NoiseModel()
+
+ noise_model.add_all_qubit_quantum_error(error_1, ['rz', 'ry'])
+ noise_model.add_all_qubit_quantum_error(error_2, ['cx'])
+ ```
+
+ ```pycon
+ >>> qml.from_qiskit_noise(noise_model)
+ NoiseModel({
+ OpIn(['RZ', 'RY']): QubitChannel(num_kraus=4, num_wires=1)
+ OpIn(['CNOT']): QubitChannel(num_kraus=16, num_wires=2)
+ })
+ ```
+
+ Under the hood, PennyLane converts each quantum error in the Qiskit noise model into an equivalent
+ `qml.QubitChannel` operator with the same canonical
+ [Kraus representation](https://en.wikipedia.org/wiki/Quantum_operation#Kraus_operators). Currently,
+ noise models in PennyLane do not support readout errors. As such, those will be skipped during conversion
+ if they are present in the Qiskit noise model.
+
+ Make sure to `pip install pennylane-qiskit` to access this new feature!
+
+Substantial upgrades to mid-circuit measurements using tree-traversal 🌳
+
+* The `"tree-traversal"` algorithm for mid-circuit measurements (MCMs) on `default.qubit` has been internally redesigned for better
+ performance.
+ [(#5868)](https://github.com/PennyLaneAI/pennylane/pull/5868)
+
+ In the last release (v0.37), we introduced the tree-traversal MCM method, which was implemented in
+ a recursive way for simplicity. However, this had the unintended consequence of very deep [stack calls](https://en.wikipedia.org/wiki/Call_stack)
+ for circuits with many MCMs, resulting in [stack overflows](https://en.wikipedia.org/wiki/Stack_overflow)
+ in some cases. With this release, we've refactored the implementation of the tree-traversal method
+ into an iterative approach, which solves those inefficiencies when many MCMs are present in a circuit.
+
+* The `tree-traversal` algorithm is now compatible with analytic-mode execution (`shots=None`).
+ [(#5868)](https://github.com/PennyLaneAI/pennylane/pull/5868)
+
+ ```python
+ dev = qml.device("default.qubit")
+
+ n_qubits = 5
+
+ @qml.qnode(dev, mcm_method="tree-traversal")
+ def circuit():
+ for w in range(n_qubits):
+ qml.Hadamard(w)
+
+ for w in range(n_qubits - 1):
+ qml.CNOT(wires=[w, w+1])
+
+ for w in range(n_qubits):
+ m = qml.measure(w)
+ qml.cond(m == 1, qml.RX)(0.1967 * (w + 1), w)
+
+ return [qml.expval(qml.Z(w)) for w in range(n_qubits)]
+ ```
+
+ ```pycon
+ >>> circuit()
+ [tensor(0.00964158, requires_grad=True),
+ tensor(0.03819446, requires_grad=True),
+ tensor(0.08455748, requires_grad=True),
+ tensor(0.14694258, requires_grad=True),
+ tensor(0.2229438, requires_grad=True)]
+ ```
Improvements 🛠
-* Counts measurements with `all_outcomes=True` can now be used with jax jitting. Measurements
- broadcasted across all available wires (`qml.probs()`) can now be used with jit and devices that
- allow variable numbers of wires (`qml.device('default.qubit')`).
- [(#6108)](https://github.com/PennyLaneAI/pennylane/pull/6108/)
+Creating spin Hamiltonians
+
+* Three new functions are now available for creating commonly-used spin Hamiltonians in PennyLane:
+ [(#6106)](https://github.com/PennyLaneAI/pennylane/pull/6106)
+ [(#6128)](https://github.com/PennyLaneAI/pennylane/pull/6128)
+
+ * `qml.spin.transverse_ising` creates the [transverse-field Ising model](https://en.wikipedia.org/wiki/Transverse-field_Ising_model) Hamiltonian.
+ * `qml.spin.heisenberg` creates the [Heisenberg model](https://en.wikipedia.org/wiki/Quantum_Heisenberg_model) Hamiltonian.
+ * `qml.spin.fermi_hubbard` creates the [Fermi-Hubbard model](https://en.wikipedia.org/wiki/Hubbard_model) Hamiltonian.
+
+ Each Hamiltonian can be instantiated by specifying a `lattice`, the number of [unit cells](https://en.wikipedia.org/wiki/Unit_cell),
+ `n_cells`, and the Hamiltonian parameters as keyword arguments. Here is an example with the transverse-field
+ Ising model:
+
+ ```pycon
+ >>> tfim_ham = qml.spin.transverse_ising(lattice="square", n_cells=[2, 2], coupling=0.5, h=0.2)
+ >>> tfim_ham
+ (
+ -0.5 * (Z(0) @ Z(1))
+ + -0.5 * (Z(0) @ Z(2))
+ + -0.5 * (Z(1) @ Z(3))
+ + -0.5 * (Z(2) @ Z(3))
+ + -0.2 * X(0)
+ + -0.2 * X(1)
+ + -0.2 * X(2)
+ + -0.2 * X(3)
+ )
+ ```
+
+ The resulting object is a `qml.Hamiltonian` instance, making it easy to use in circuits like the following.
+
+ ```python
+ dev = qml.device("default.qubit", shots=1)
+
+ @qml.qnode(dev)
+ def circuit():
+ return qml.expval(tfim_ham)
+ ```
+
+ ```
+ >>> circuit()
+ -2.0
+ ```
+
+ More features will be added to the `qml.spin` module in the coming releases, so stay tuned!
A Prep-Select-Prep template
-* The `qml.PrepSelPrep` template is added. The template implements a block-encoding of a linear
- combination of unitaries.
+* A new template called `qml.PrepSelPrep` has been added that implements a block-encoding of a linear
+ combination of unitaries.
[(#5756)](https://github.com/PennyLaneAI/pennylane/pull/5756)
[(#5987)](https://github.com/PennyLaneAI/pennylane/pull/5987)
+ This operator acts as a nice wrapper for having to perform `qml.StatePrep`, `qml.Select`, and `qml.adjoint(qml.StatePrep)`
+ in succession, which is quite common in many quantum algorithms (e.g., [LCU and block encoding](https://pennylane.ai/qml/demos/tutorial_lcu_blockencoding/)). Here is an example showing the equivalence
+ between using `qml.PrepSelPrep` and `qml.StatePrep`, `qml.Select`, and `qml.adjoint(qml.StatePrep)`.
+
+ ```python
+ coeffs = [0.3, 0.1]
+ alphas = (np.sqrt(coeffs) / np.linalg.norm(np.sqrt(coeffs)))
+ unitaries = [qml.X(2), qml.Z(2)]
+
+ lcu = qml.dot(coeffs, unitaries)
+ control = [0, 1]
+
+ def prep_sel_prep(alphas, unitaries):
+ qml.StatePrep(alphas, wires=control, pad_with=0)
+ qml.Select(unitaries, control=control)
+ qml.adjoint(qml.StatePrep)(alphas, wires=control, pad_with=0)
+
+ @qml.qnode(qml.device("default.qubit"))
+ def circuit(lcu, control, alphas, unitaries):
+ qml.PrepSelPrep(lcu, control)
+ qml.adjoint(prep_sel_prep)(alphas, unitaries)
+ return qml.state()
+ ```
+
+ ```pycon
+ >>> import numpy as np
+ >>> np.round(circuit(lcu, control, alphas, unitaries), decimals=2)
+ tensor([1.+0.j -0.+0.j -0.+0.j -0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j], requires_grad=True)
+ ```
+
QChem improvements
* Molecules and Hamiltonians can now be constructed for all the elements present in the periodic table.
[(#5821)](https://github.com/PennyLaneAI/pennylane/pull/5821)
+ This new feature is made possible by integrating with the [basis-set-exchange package](https://pypi.org/project/basis-set-exchange/).
+ If loading basis sets from `basis-set-exchange` is needed for your molecule, make sure that you
+ `pip install basis-set-exchange` and set `load_data=True`.
+
+ ```python
+ symbols = ['Ti', 'Ti']
+ geometry = np.array([[0.0, 0.0, -1.1967],
+ [0.0, 0.0, 1.1967]], requires_grad=True)
+ mol = qml.qchem.Molecule(symbols, geometry, load_data=True)
+ ```
+
+ ```pycon
+ >>> mol.n_electrons
+ 44
+ ```
+
* `qml.UCCSD` now accepts an additional optional argument, `n_repeats`, which defines the number of
times the UCCSD template is repeated. This can improve the accuracy of the template by reducing
- the Trotter error but would result in deeper circuits.
+ the Trotter error, but would result in deeper circuits.
[(#5801)](https://github.com/PennyLaneAI/pennylane/pull/5801)
-* The `qubit_observable` function is modified to return an ascending wire order for molecular
+* The `qml.qchem.qubit_observable` function has been modified to return an ascending wire order for molecular
Hamiltonians.
[(#5950)](https://github.com/PennyLaneAI/pennylane/pull/5950)
-* A new method `to_mat` has been added to the `FermiWord` and `FermiSentence` classes, which allows
- computing the matrix representation of these Fermi operators.
+* A new method called `to_mat` has been added to the `qml.FermiWord` and `qml.FermiSentence` classes,
+ which allows for computing the matrix representation of these Fermi operators.
[(#5920)](https://github.com/PennyLaneAI/pennylane/pull/5920)
-
-* `qml.pauli.group_observables` now uses `Rustworkx` colouring algorithms to solve the Minimum Clique Cover problem.
- This adds two new options for the `method` argument: `dsatur` and `gis`. In addition, the creation of the adjacency matrix
- now takes advantage of the symplectic representation of the Pauli observables. An additional function `qml.pauli.compute_partition_indices`
- is added to calculate the indices from the partitioned observables more efficiently. `qml.pauli.grouping.PauliGroupingStrategy.idx_partitions_from_graph`
- can be used to compute partitions of custom indices. These changes improve the wall time of `qml.LinearCombination.compute_grouping`
- and `grouping_type='qwc'` by orders of magnitude.
- [(#6043)](https://github.com/PennyLaneAI/pennylane/pull/6043)
Improvements to operators
-* `GlobalPhase` now supports parameter broadcasting.
+* `qml.GlobalPhase` now supports parameter broadcasting.
[(#5923)](https://github.com/PennyLaneAI/pennylane/pull/5923)
-* Added the `compute_decomposition` method for `qml.Hermitian`.
+* `qml.Hermitian` now has a `compute_decomposition` method.
[(#6062)](https://github.com/PennyLaneAI/pennylane/pull/6062)
-* Port the fast `apply_operation` implementation of `PauliZ` to `PhaseShift`, `S` and `T`.
+* The implementation of `qml.PhaseShift`, `qml.S`, and `qml.T` has been improved, resulting in faster
+ circuit execution times.
[(#5876)](https://github.com/PennyLaneAI/pennylane/pull/5876)
-* The `tree-traversal` algorithm implemented in `default.qubit` is refactored
- into an iterative (instead of recursive) implementation, doing away with
- potential stack overflow for deep circuits.
- [(#5868)](https://github.com/PennyLaneAI/pennylane/pull/5868)
-
-* The `tree-traversal` algorithm is compatible with analytic-mode execution (`shots=None`).
- [(#5868)](https://github.com/PennyLaneAI/pennylane/pull/5868)
-
-* `fuse_rot_angles` now respects the global phase of the combined rotations.
- [(#6031)](https://github.com/PennyLaneAI/pennylane/pull/6031)
-
-* The `CNOT` operator no longer decomposes to itself. Instead, it raises a `qml.DecompositionUndefinedError`.
+* The `qml.CNOT` operator no longer decomposes into itself. Instead, it raises a `qml.DecompositionUndefinedError`.
[(#6039)](https://github.com/PennyLaneAI/pennylane/pull/6039)
-Mid-circuit measurement improvements
+Mid-circuit measurements
-* `qml.dynamic_one_shot` now supports circuits using the `"tensorflow"` interface.
+* The `qml.dynamic_one_shot` transform now supports circuits using the `"tensorflow"` interface.
[(#5973)](https://github.com/PennyLaneAI/pennylane/pull/5973)
* If the conditional does not include a mid-circuit measurement, then `qml.cond`
@@ -137,39 +421,41 @@
Transforms
-* The `diagonalize_measurements` transform is added. This transform converts measurements
- to the Z basis by applying the relevant diagonalizing gates. It can be set to diagonalize only
- a subset of the base observables `{X, Y, Z, Hadamard}`.
+* `qml.transforms.single_qubit_fusion` and `qml.transforms.merge_rotations` now respect global phases.
+ [(#6031)](https://github.com/PennyLaneAI/pennylane/pull/6031)
+
+* A new transform called `qml.transforms.diagonalize_measurements` has been added. This transform converts
+ measurements to the computational basis by applying the relevant diagonalizing gates. It can be set
+ to diagonalize only a subset of the base observables `{qml.X, qml.Y, qml.Z, qml.Hadamard}`.
[(#5829)](https://github.com/PennyLaneAI/pennylane/pull/5829)
-* The `split_to_single_terms` transform is added. This transform splits expectation values of sums
- into multiple single-term measurements on a single tape, providing better support for simulators
+* A new transform called `split_to_single_terms` has been added. This transform splits expectation values
+ of sums into multiple single-term measurements on a single tape, providing better support for simulators
that can handle non-commuting observables but don't natively support multi-term observables.
[(#5884)](https://github.com/PennyLaneAI/pennylane/pull/5884)
-* New functionality has been added to natively support exponential extrapolation when using the `mitigate_with_zne`. This allows
- users to have more control over the error mitigation protocol without needing to add further dependencies.
+* New functionality has been added to natively support exponential extrapolation when using `qml.transforms.mitigate_with_zne`.
+ This allows users to have more control over the error mitigation protocol without needing to add further
+ dependencies.
[(#5972)](https://github.com/PennyLaneAI/pennylane/pull/5972)
-* `fuse_rot_angles` now respects the global phase of the combined rotations.
- [(#6031)](https://github.com/PennyLaneAI/pennylane/pull/6031)
-
Capturing and representing hybrid programs
* `qml.for_loop` now supports `range`-like syntax with default `step=1`.
[(#6068)](https://github.com/PennyLaneAI/pennylane/pull/6068)
-* Applying `adjoint` and `ctrl` to a quantum function can now be captured into plxpr.
- Furthermore, the `qml.cond` function can be captured into plxpr.
+* Applying `adjoint` and `ctrl` to a quantum function can now be captured into plxpr. Furthermore, the
+ `qml.cond` function can be captured into plxpr.
[(#5966)](https://github.com/PennyLaneAI/pennylane/pull/5966)
[(#5967)](https://github.com/PennyLaneAI/pennylane/pull/5967)
[(#5999)](https://github.com/PennyLaneAI/pennylane/pull/5999)
[(#6058)](https://github.com/PennyLaneAI/pennylane/pull/6058)
-* During experimental program capture, functions that accept and/or return `pytree` structures can now be handled in the `QNode` call, `cond`, `for_loop` and `while_loop`.
+* During experimental program capture, functions that accept and/or return `pytree` structures can now
+ be handled in the `qml.QNode` call, `qml.cond`, `qml.for_loop` and `qml.while_loop`.
[(#6081)](https://github.com/PennyLaneAI/pennylane/pull/6081)
-* During experimental program capture, the qnode can now use closure variables.
+* During experimental program capture, QNodes can now use closure variables.
[(#6052)](https://github.com/PennyLaneAI/pennylane/pull/6052)
* Mid-circuit measurements can now be captured with `qml.capture` enabled.
@@ -179,9 +465,8 @@
[(#6041)](https://github.com/PennyLaneAI/pennylane/pull/6041)
[(#6064)](https://github.com/PennyLaneAI/pennylane/pull/6064)
-* `qml.for_loop` and `qml.while_loop` now fallback to standard Python control
- flow if `@qjit` is not present, allowing the same code to work with and without
- `@qjit` without any rewrites.
+* `qml.for_loop` and `qml.while_loop` now fall back to standard Python control flow if `@qjit` is not
+ present, allowing the same code to work with and without `@qjit` without any rewrites.
[(#6014)](https://github.com/PennyLaneAI/pennylane/pull/6014)
```python
@@ -226,55 +511,80 @@
Community contributions 🥳
-* Resolved the bug in `qml.ThermalRelaxationError` where there was a typo from `tq` to `tg`.
+* Fixed a bug in `qml.ThermalRelaxationError` where there was a typo from `tq` to `tg`.
[(#5988)](https://github.com/PennyLaneAI/pennylane/issues/5988)
-* `DefaultQutritMixed` readout error has been added using parameters `readout_relaxation_probs` and
- `readout_misclassification_probs` on the `default.qutrit.mixed` device. These parameters add a `~.QutritAmplitudeDamping` and a `~.TritFlip` channel, respectively,
- after measurement diagonalization. The amplitude damping error represents the potential for
- relaxation to occur during longer measurements. The trit flip error represents misclassification during readout.
- [(#5842)](https://github.com/PennyLaneAI/pennylane/pull/5842)s
+* Readout error has been added using parameters `readout_relaxation_probs` and `readout_misclassification_probs`
+ on the `default.qutrit.mixed` device. These parameters add a `qml.QutritAmplitudeDamping` and a `qml.TritFlip`
+ channel, respectively, after measurement diagonalization. The amplitude damping error represents the
+ potential for relaxation to occur during longer measurements. The trit flip error represents misclassification
+ during readout.
+ [(#5842)](https://github.com/PennyLaneAI/pennylane/pull/5842)
+
+* `qml.ops.qubit.BasisStateProjector` now has a `compute_sparse_matrix` method that computes the sparse
+ CSR matrix representation of the projector onto the given basis state.
+ [(#5790)](https://github.com/PennyLaneAI/pennylane/pull/5790)
Other improvements
-* Added the decomposition of zyz for special unitaries with multiple control wires.
+* `qml.pauli.group_observables` now uses `rustworkx` colouring algorithms to solve the
+ [Minimum Clique Cover problem](https://en.wikipedia.org/wiki/Clique_cover), resulting in orders of
+ magnitude performance improvements.
+ [(#6043)](https://github.com/PennyLaneAI/pennylane/pull/6043)
+
+ This adds two new options for the `method` argument: `dsatur` (degree of saturation) and `gis` (independent
+ set). In addition, the creation of the adjacency matrix now takes advantage of the symplectic representation
+ of the Pauli observables.
+
+ Additionally, a new function called `qml.pauli.compute_partition_indices` has been added to calculate
+ the indices from the partitioned observables more efficiently. These changes improve the wall time
+ of `qml.LinearCombination.compute_grouping` and the `grouping_type='qwc'` by orders of magnitude.
+
+* `qml.counts` measurements with `all_outcomes=True` can now be used with JAX jitting. Additionally,
+ measurements broadcasted across all available wires (e.g., `qml.probs()`) can now be used with JAX
+ jit and devices that allow dynamic numbers of wires (only `'default.qubit'` currently).
+ [(#6108)](https://github.com/PennyLaneAI/pennylane/pull/6108/)
+
+* `qml.ops.op_math.ctrl_decomp_zyz` can now decompose special unitaries with multiple control wires.
[(#6042)](https://github.com/PennyLaneAI/pennylane/pull/6042)
-* A new method `process_density_matrix` has been added to the `ProbabilityMP` and `DensityMatrixMP`
- classes, allowing for more efficient handling of quantum density matrices, particularly with batch
- processing support. This method simplifies the calculation of probabilities from quantum states
- represented as density matrices.
+* A new method called `process_density_matrix` has been added to the `ProbabilityMP` and `DensityMatrixMP`
+ measurement processes, allowing for more efficient handling of quantum density matrices, particularly
+ with batch processing support. This method simplifies the calculation of probabilities from quantum
+ states represented as density matrices.
[(#5830)](https://github.com/PennyLaneAI/pennylane/pull/5830)
* `SProd.terms` now flattens out the terms if the base is a multi-term observable.
[(#5885)](https://github.com/PennyLaneAI/pennylane/pull/5885)
-* `QNGOptimizer` now supports cost functions with multiple arguments, updating each argument independently.
+* `qml.QNGOptimizer` now supports cost functions with multiple arguments, updating each argument independently.
[(#5926)](https://github.com/PennyLaneAI/pennylane/pull/5926)
-* Removed `semantic_version` from the list of required packages in PennyLane.
+* `semantic_version` has been removed from the list of required packages in PennyLane.
[(#5836)](https://github.com/PennyLaneAI/pennylane/pull/5836)
-* `qml.devices.LegacyDeviceFacade` has been added to map the legacy devices to the new
- device interface.
+* `qml.devices.LegacyDeviceFacade` has been added to map the legacy devices to the new device interface,
+ making it easier for developers to develop legacy devices.
[(#5927)](https://github.com/PennyLaneAI/pennylane/pull/5927)
-* Added the `compute_sparse_matrix` method for `qml.ops.qubit.BasisStateProjector`.
- [(#5790)](https://github.com/PennyLaneAI/pennylane/pull/5790)
-
-* `StateMP.process_state` defines rules in `cast_to_complex` for complex casting, avoiding a superfluous state vector copy in Lightning simulations
+* `StateMP.process_state` now defines rules in `cast_to_complex` for complex casting, avoiding a superfluous
+ statevector copy in PennyLane-Lightning simulations.
[(#5995)](https://github.com/PennyLaneAI/pennylane/pull/5995)
* `QuantumScript.hash` is now cached, leading to performance improvements.
[(#5919)](https://github.com/PennyLaneAI/pennylane/pull/5919)
-* Observable validation for `default.qubit` is now based on execution mode (analytic vs. finite shots) and measurement type (sample measurement vs. state measurement).
+* Observable validation for `default.qubit` is now based on execution mode (analytic vs. finite shots)
+ and measurement type (sample measurement vs. state measurement). This improves our error handling when,
+ for example, non-hermitian operators are given to `qml.expval`.
[(#5890)](https://github.com/PennyLaneAI/pennylane/pull/5890)
-* Added `is_leaf` parameter to function `flatten` in the `qml.pytrees` module. This is to allow node flattening to be stopped for any node where the `is_leaf` optional argument evaluates to being `True`.
+* A new `is_leaf` parameter has been added to the function `flatten` in the `qml.pytrees` module. This
+ is to allow for node flattening to be stopped for any node where the `is_leaf` optional argument evaluates
+ to being `True`.
[(#6107)](https://github.com/PennyLaneAI/pennylane/issues/6107)
-* Added a progress bar when downloading datasets with `qml.data.load()`
+* A progress bar has been added to `qml.data.load()` when downloading a dataset.
[(#5560)](https://github.com/PennyLaneAI/pennylane/pull/5560)
* Upgraded and simplified `StatePrep` and `AmplitudeEmbedding` templates.
@@ -287,33 +597,34 @@
Breaking changes 💔
* `MeasurementProcess.shape(shots: Shots, device:Device)` is now
- `MeasurementProcess.shape(shots: Optional[int], num_device_wires:int = 0)`. This has been done to allow
- jitting when a measurement is broadcasted across all available wires, but the device does not specify wires.
+ `MeasurementProcess.shape(shots: Optional[int], num_device_wires:int = 0)`. This has been done to
+ allow for jitting when a measurement is broadcasted across all available wires, but the device does
+ not specify wires.
[(#6108)](https://github.com/PennyLaneAI/pennylane/pull/6108/)
-* If the shape of a probability measurement is affected by a `Device.cutoff` property, it will no longer work with
- jitting.
+* If the shape of a probability measurement is affected by a `Device.cutoff` property, it will no longer
+ work with jitting.
[(#6108)](https://github.com/PennyLaneAI/pennylane/pull/6108/)
-* `GlobalPhase` is considered non-differentiable with tape transforms.
- As a consequence, `qml.gradients.finite_diff` and `qml.gradients.spsa_grad` no longer
- support differentiation of `GlobalPhase` with state-based outputs.
+* `qml.GlobalPhase` is considered non-differentiable with tape transforms. As a consequence, `qml.gradients.finite_diff`
+ and `qml.gradients.spsa_grad` no longer support differentiating `qml.GlobalPhase` with state-based
+ outputs.
[(#5620)](https://github.com/PennyLaneAI/pennylane/pull/5620)
-* The `CircuitGraph.graph` rustworkx graph now stores indices into the circuit as the node labels,
- instead of the operator/ measurement itself. This allows the same operator to occur multiple times in
- the circuit.
+* The `CircuitGraph.graph` `rustworkx` graph now stores indices into the circuit as the node labels,
+ instead of the operator/ measurement itself. This allows the same operator to occur multiple times
+ in the circuit.
[(#5907)](https://github.com/PennyLaneAI/pennylane/pull/5907)
-* `queue_idx` attribute has been removed from the `Operator`, `CompositeOp`, and `SymbolicOp` classes.
+* The `queue_idx` attribute has been removed from the `Operator`, `CompositeOp`, and `SymbolicOp` classes.
[(#6005)](https://github.com/PennyLaneAI/pennylane/pull/6005)
-* `qml.from_qasm` no longer removes measurements from the QASM code. Use
- `measurements=[]` to remove measurements from the original circuit.
+* `qml.from_qasm` no longer removes measurements from the QASM code. Use `measurements=[]` to remove
+ measurements from the original circuit.
[(#5982)](https://github.com/PennyLaneAI/pennylane/pull/5982)
-* `qml.transforms.map_batch_transform` has been removed, since transforms can be applied directly to a batch of tapes.
- See :func:`~.pennylane.transform` for more information.
+* `qml.transforms.map_batch_transform` has been removed, since transforms can be applied directly to
+ a batch of tapes. See `qml.transform` for more information.
[(#5981)](https://github.com/PennyLaneAI/pennylane/pull/5981)
* `QuantumScript.interface` has been removed.
@@ -327,7 +638,7 @@
* The `max_expansion` argument in `qml.QNode` has been deprecated.
[(#6026)](https://github.com/PennyLaneAI/pennylane/pull/6026)
-* The `expansion_strategy` attribute in the `QNode` class is deprecated.
+* The `expansion_strategy` attribute `qml.QNode` has been deprecated.
[(#5989)](https://github.com/PennyLaneAI/pennylane/pull/5989)
* The `expansion_strategy` argument has been deprecated in all of `qml.draw`, `qml.draw_mpl`, and `qml.specs`.
@@ -338,32 +649,33 @@
for equivalent behaviour.
[(#5994)](https://github.com/PennyLaneAI/pennylane/pull/5994)
-* `pennylane.transforms.sum_expand` and `pennylane.transforms.hamiltonian_expand` have been deprecated.
- Users should instead use `pennylane.transforms.split_non_commuting` for equivalent behaviour.
+* `qml.transforms.sum_expand` and `qml.transforms.hamiltonian_expand` have been deprecated. Users should
+ instead use `qml.transforms.split_non_commuting` for equivalent behaviour.
[(#6003)](https://github.com/PennyLaneAI/pennylane/pull/6003)
-* The `expand_fn` argument in `qml.execute` has been deprecated.
- Instead, please create a `qml.transforms.core.TransformProgram` with the desired preprocessing and pass it to the `transform_program` argument of `qml.execute`.
+* The `expand_fn` argument in `qml.execute` has been deprecated. Instead, please create a `qml.transforms.core.TransformProgram`
+ with the desired preprocessing and pass it to the `transform_program` argument of `qml.execute`.
[(#5984)](https://github.com/PennyLaneAI/pennylane/pull/5984)
* The `max_expansion` argument in `qml.execute` has been deprecated.
- Instead, please use `qml.devices.preprocess.decompose` with the desired expansion level, add it to a `TransformProgram` and pass it to the `transform_program` argument of `qml.execute`.
+ Instead, please use `qml.devices.preprocess.decompose` with the desired expansion level, add it to
+ a `qml.transforms.core.TransformProgram` and pass it to the `transform_program` argument of `qml.execute`.
[(#5984)](https://github.com/PennyLaneAI/pennylane/pull/5984)
-* The `override_shots` argument in `qml.execute` is deprecated.
- Instead, please add the shots to the `QuantumTape`'s to be executed.
+* The `override_shots` argument in `qml.execute` has been deprecated.
+ Instead, please add the shots to the `QuantumTape`s to be executed.
[(#5984)](https://github.com/PennyLaneAI/pennylane/pull/5984)
-* The `device_batch_transform` argument in `qml.execute` is deprecated.
+* The `device_batch_transform` argument in `qml.execute` has been deprecated.
Instead, please create a `qml.transforms.core.TransformProgram` with the desired preprocessing and pass it to the `transform_program` argument of `qml.execute`.
[(#5984)](https://github.com/PennyLaneAI/pennylane/pull/5984)
-* `pennylane.qinfo.classical_fisher` and `pennylane.qinfo.quantum_fisher` have been deprecated.
- Instead, use `pennylane.gradients.classical_fisher` and `pennylane.gradients.quantum_fisher`.
+* `qml.qinfo.classical_fisher` and `qml.qinfo.quantum_fisher` have been deprecated.
+ Instead, use `qml.gradients.classical_fisher` and `qml.gradients.quantum_fisher`.
[(#5985)](https://github.com/PennyLaneAI/pennylane/pull/5985)
-* The legacy devices `default.qubit.{autograd,torch,tf,jax,legacy}` are deprecated.
- Instead, use `default.qubit` as it now supports backpropagation through the several backends.
+* The legacy devices `default.qubit.{autograd,torch,tf,jax,legacy}` have been deprecated.
+ Instead, use `default.qubit`, as it now supports backpropagation through the several backends.
[(#5997)](https://github.com/PennyLaneAI/pennylane/pull/5997)
* The logic for internally switching a device for a different backpropagation
@@ -373,15 +685,23 @@
Documentation 📝
-* Improves the docstring for `qinfo.quantum_fisher` regarding the internally used functions and
- potentially required auxiliary wires.
+* The docstring for `qml.qinfo.quantum_fisher`, regarding the internally used functions and potentially
+ required auxiliary wires, has been improved.
[(#6074)](https://github.com/PennyLaneAI/pennylane/pull/6074)
-* Improves the docstring for `QuantumScript.expand` and `qml.tape.tape.expand_tape`.
+* The docstring for `QuantumScript.expand` and `qml.tape.tape.expand_tape` has been improved.
[(#5974)](https://github.com/PennyLaneAI/pennylane/pull/5974)
Bug fixes 🐛
+* The sparse matrix can now be computed for a product operator when one operand is a `GlobalPhase`
+ on no wires.
+ [(#6197)](https://github.com/PennyLaneAI/pennylane/pull/6197)
+
+* For `default.qubit`, JAX is now used for sampling whenever the state is a JAX array. This fixes normalization issues
+ that can occur when the state uses 32-bit precision.
+ [(#6190)](https://github.com/PennyLaneAI/pennylane/pull/6190)
+
* Fix Pytree serialization of operators with empty shot vectors
[(#6155)](https://github.com/PennyLaneAI/pennylane/pull/6155)
@@ -395,17 +715,17 @@
batch size.
[(#6147)](https://github.com/PennyLaneAI/pennylane/pull/6147)
-* Catalyst replaced `argnum` with `argnums` in gradient related functions, therefore we update the Catalyst
+* Catalyst replaced `argnum` with `argnums` in gradient related functions, therefore we updated the Catalyst
calls to those functions in PennyLane.
[(#6117)](https://github.com/PennyLaneAI/pennylane/pull/6117)
-* `fuse_rot_angles` no longer returns wrong derivatives at singular points but returns NaN.
+* `fuse_rot_angles` now returns NaN instead of incorrect derivatives at singular points.
[(#6031)](https://github.com/PennyLaneAI/pennylane/pull/6031)
-* `qml.GlobalPhase` and `qml.I` can now be captured when acting on no wires.
+* `qml.GlobalPhase` and `qml.Identity` can now be captured with plxpr when acting on no wires.
[(#6060)](https://github.com/PennyLaneAI/pennylane/pull/6060)
-* Fix `jax.grad` + `jax.jit` not working for `AmplitudeEmbedding`, `StatePrep` and `MottonenStatePreparation`.
+* Fixed `jax.grad` and `jax.jit` to work for `qml.AmplitudeEmbedding`, `qml.StatePrep` and `qml.MottonenStatePreparation`.
[(#5620)](https://github.com/PennyLaneAI/pennylane/pull/5620)
* Fixed a bug in `qml.center` that omitted elements from the center if they were
@@ -418,35 +738,37 @@
* Fixed a bug in `qml.SPSAOptimizer` that ignored keyword arguments in the objective function.
[(#6027)](https://github.com/PennyLaneAI/pennylane/pull/6027)
-* `dynamic_one_shot` was broken for old-API devices since `override_shots` was deprecated.
+* Fixed `dynamic_one_shot` for use with devices using the old device API, since `override_shots` was deprecated.
[(#6024)](https://github.com/PennyLaneAI/pennylane/pull/6024)
* `CircuitGraph` can now handle circuits with the same operation instance occuring multiple times.
[(#5907)](https://github.com/PennyLaneAI/pennylane/pull/5907)
-* `qml.QSVT` is updated to store wire order correctly.
+* `qml.QSVT` has been updated to store wire order correctly.
[(#5959)](https://github.com/PennyLaneAI/pennylane/pull/5959)
* `qml.devices.qubit.measure_with_samples` now returns the correct result if the provided measurements
- contain sum of operators acting on the same wire.
+ contain a sum of operators acting on the same wire.
[(#5978)](https://github.com/PennyLaneAI/pennylane/pull/5978)
* `qml.AmplitudeEmbedding` has better support for features using low precision integer data types.
[(#5969)](https://github.com/PennyLaneAI/pennylane/pull/5969)
-* `qml.BasisState` and `qml.BasisEmbedding` now works with jax.jit, lightning.qubit and give the correct decomposition.
+* `qml.BasisState` and `qml.BasisEmbedding` now works with jax.jit, `lightning.qubit`, and give the correct
+ decomposition.
[(#6021)](https://github.com/PennyLaneAI/pennylane/pull/6021)
-* Jacobian shape is fixed for measurements with dimension in `qml.gradients.vjp.compute_vjp_single`.
-[(5986)](https://github.com/PennyLaneAI/pennylane/pull/5986)
+* Jacobian shape has been fixed for measurements with dimension in `qml.gradients.vjp.compute_vjp_single`.
+ [(5986)](https://github.com/PennyLaneAI/pennylane/pull/5986)
-* `qml.lie_closure` works with sums of Paulis.
+* `qml.lie_closure` now works with sums of Paulis.
[(#6023)](https://github.com/PennyLaneAI/pennylane/pull/6023)
* Workflows that parameterize the coefficients of `qml.exp` are now jit-compatible.
[(#6082)](https://github.com/PennyLaneAI/pennylane/pull/6082)
-* Fixes a bug where `CompositeOp.overlapping_ops` changes the original ordering of ops, causing incorrect matrix generated for `Prod` with `Sum` as operands.
+* Fixed a bug where `CompositeOp.overlapping_ops` changes the original ordering of operators, causing
+ an incorrect matrix to be generated for `Prod` with `Sum` as operands.
[(#6091)](https://github.com/PennyLaneAI/pennylane/pull/6091)
* `qml.qsvt` now works with "Wx" convention and any number of angles.
diff --git a/pennylane/devices/qubit/sampling.py b/pennylane/devices/qubit/sampling.py
index 50377cbad90..be6090a0354 100644
--- a/pennylane/devices/qubit/sampling.py
+++ b/pennylane/devices/qubit/sampling.py
@@ -471,9 +471,9 @@ def sample_state(
Returns:
ndarray[int]: Sample values of the shape (shots, num_wires)
"""
- if prng_key is not None:
+ if prng_key is not None or qml.math.get_interface(state) == "jax":
return _sample_state_jax(
- state, shots, prng_key, is_state_batched=is_state_batched, wires=wires
+ state, shots, prng_key, is_state_batched=is_state_batched, wires=wires, seed=rng
)
rng = np.random.default_rng(rng)
@@ -530,6 +530,7 @@ def _sample_state_jax(
prng_key,
is_state_batched: bool = False,
wires=None,
+ seed=None,
) -> np.ndarray:
"""
Returns a series of samples of a state for the JAX interface based on the PRNG.
@@ -541,6 +542,7 @@ def _sample_state_jax(
the key to the JAX pseudo random number generator.
is_state_batched (bool): whether the state is batched or not
wires (Sequence[int]): The wires to sample
+ seed (numpy.random.Generator): seed to use to generate a key if a ``prng_key`` is not present. ``None`` by default.
Returns:
ndarray[int]: Sample values of the shape (shots, num_wires)
@@ -549,7 +551,8 @@ def _sample_state_jax(
import jax
import jax.numpy as jnp
- key = prng_key
+ if prng_key is None:
+ prng_key = jax.random.PRNGKey(np.random.default_rng(seed).integers(100000))
total_indices = len(state.shape) - is_state_batched
state_wires = qml.wires.Wires(range(total_indices))
@@ -574,6 +577,6 @@ def _sample_state_jax(
_, key = jax_random_split(prng_key)
samples = jax.random.choice(key, basis_states, shape=(shots,), p=probs)
- powers_of_two = 1 << np.arange(num_wires, dtype=np.int64)[::-1]
+ powers_of_two = 1 << np.arange(num_wires, dtype=int)[::-1]
states_sampled_base_ten = samples[..., None] & powers_of_two
- return (states_sampled_base_ten > 0).astype(np.int64)
+ return (states_sampled_base_ten > 0).astype(int)
diff --git a/pennylane/math/matrix_manipulation.py b/pennylane/math/matrix_manipulation.py
index fb06140011d..6e2d487ac19 100644
--- a/pennylane/math/matrix_manipulation.py
+++ b/pennylane/math/matrix_manipulation.py
@@ -105,6 +105,10 @@ def expand_matrix(mat, wires, wire_order=None, sparse_format="csr"):
if (wire_order is None) or (wire_order == wires):
return mat
+ if not wires and qml.math.shape(mat) == (2, 2):
+ # global phase
+ wires = wire_order[0:1]
+
wires = list(wires)
wire_order = list(wire_order)
diff --git a/pennylane/spin/lattice.py b/pennylane/spin/lattice.py
index 5a8ec5511fa..84bae270698 100644
--- a/pennylane/spin/lattice.py
+++ b/pennylane/spin/lattice.py
@@ -33,7 +33,8 @@ class Lattice:
n_cells (list[int]): Number of cells in each direction of the grid.
vectors (list[list[float]]): Primitive vectors for the lattice.
positions (list[list[float]]): Initial positions of spin cites. Default value is
- ``[[0.0]*number of dimensions]``.
+ ``[[0.0]`` :math:`\times` ``number of dimensions]``.
+
boundary_condition (bool or list[bool]): Defines boundary conditions in different lattice axes,
default is ``False`` indicating open boundary condition.
neighbour_order (int): Specifies the interaction level for neighbors within the lattice.
@@ -53,13 +54,15 @@ class Lattice:
Lattice object
**Example**
+
>>> n_cells = [2,2]
>>> vectors = [[0, 1], [1, 0]]
>>> boundary_condition = [True, False]
>>> lattice = qml.spin.Lattice(n_cells, vectors,
>>> boundary_condition=boundary_condition)
- >>> print(lattice.edges)
+ >>> lattice.edges
[(2, 3, 0), (0, 2, 0), (1, 3, 0), (0, 1, 0)]
+
"""
def __init__(
diff --git a/pennylane/spin/spin_hamiltonian.py b/pennylane/spin/spin_hamiltonian.py
index 71cf365dc1a..3d6c78cd1f2 100644
--- a/pennylane/spin/spin_hamiltonian.py
+++ b/pennylane/spin/spin_hamiltonian.py
@@ -27,7 +27,7 @@
def transverse_ising(
lattice, n_cells, coupling=1.0, h=1.0, boundary_condition=False, neighbour_order=1
):
- r"""Generates the transverse-field Ising model on a lattice.
+ r"""Generates the Hamiltonian for the transverse-field Ising model on a lattice.
The Hamiltonian is represented as:
@@ -39,20 +39,21 @@ def transverse_ising(
transverse magnetic field and ``i,j`` represent the indices for neighbouring spins.
Args:
- lattice (str): Shape of the lattice. Input Values can be ``'chain'``, ``'square'``,
- ``'rectangle'``, ``'honeycomb'``, ``'triangle'``, or ``'kagome'``.
- n_cells (List[int]): Number of cells in each direction of the grid.
- coupling (float or List[float] or List[math.array[float]]): Coupling between spins. It can be a
- number, a list of length equal to ``neighbour_order`` or a square matrix of size
- ``(num_spins, num_spins)``. Default value is 1.0.
- h (float): Value of external magnetic field. Default is 1.0.
- boundary_condition (bool or list[bool]): Defines boundary conditions for different lattice axes,
- default is ``False`` indicating open boundary condition.
- neighbour_order (int): Specifies the interaction level for neighbors within the lattice.
- Default is 1, indicating nearest neighbours.
+ lattice (str): Shape of the lattice. Input values can be ``'chain'``, ``'square'``,
+ ``'rectangle'``, ``'honeycomb'``, ``'triangle'``, or ``'kagome'``.
+ n_cells (List[int]): Number of cells in each direction of the grid.
+ coupling (float or List[float] or List[math.array[float]]): Coupling between spins. It can
+ be a number, a list of length equal to ``neighbour_order`` or a square matrix of shape
+ ``(num_spins, num_spins)``, where ``num_spins`` is the total number of spins. Default
+ value is 1.0.
+ h (float): Value of external magnetic field. Default is 1.0.
+ boundary_condition (bool or list[bool]): Defines boundary conditions for different lattice
+ axes, default is ``False`` indicating open boundary condition.
+ neighbour_order (int): Specifies the interaction level for neighbors within the lattice.
+ Default is 1, indicating nearest neighbours.
Returns:
- pennylane.LinearCombination: Hamiltonian for the transverse-field Ising model.
+ ~ops.op_math.Sum: Hamiltonian for the transverse-field Ising model.
**Example**
@@ -102,7 +103,7 @@ def transverse_ising(
def heisenberg(lattice, n_cells, coupling=None, boundary_condition=False, neighbour_order=1):
- r"""Generates the Heisenberg model on a lattice.
+ r"""Generates the Hamiltonian for the Heisenberg model on a lattice.
The Hamiltonian is represented as:
@@ -110,22 +111,23 @@ def heisenberg(lattice, n_cells, coupling=None, boundary_condition=False, neighb
\hat{H} = J\sum_{}(\sigma_i^x\sigma_j^x + \sigma_i^y\sigma_j^y + \sigma_i^z\sigma_j^z)
- where ``J`` is the coupling constant defined for the Hamiltonian, and ``i,j`` represent the indices for neighbouring spins.
+ where ``J`` is the coupling constant defined for the Hamiltonian, and ``i,j`` represent the
+ indices for neighbouring spins.
Args:
- lattice (str): Shape of the lattice. Input Values can be ``'chain'``, ``'square'``, ``'rectangle'``,
- ``'honeycomb'``, ``'triangle'``, or ``'kagome'``.
- n_cells (List[int]): Number of cells in each direction of the grid.
- coupling (List[List[float]] or List[math.array[float]]): Coupling between spins. It can be a 2D array
- of shape (neighbour_order, 3) or a 3D array of shape 3 * number of spins * number of spins.
- Default value is [1.0, 1.0, 1.0].
- boundary_condition (bool or list[bool]): Defines boundary conditions for different lattice axes,
- default is ``False`` indicating open boundary condition.
- neighbour_order (int): Specifies the interaction level for neighbors within the lattice.
- Default is 1, indicating nearest neighbours.
+ lattice (str): Shape of the lattice. Input values can be ``'chain'``, ``'square'``,
+ ``'rectangle'``, ``'honeycomb'``, ``'triangle'``, or ``'kagome'``.
+ n_cells (List[int]): Number of cells in each direction of the grid.
+ coupling (List[List[float]] or List[math.array[float]]): Coupling between spins. It can be a
+ 2D array of shape ``(neighbour_order, 3)`` or a 3D array of shape
+ ``(3, num_spins, num_spins)``, where ``num_spins`` is the total number of spins.
+ boundary_condition (bool or list[bool]): Defines boundary conditions for different lattice
+ axes, default is ``False`` indicating open boundary condition.
+ neighbour_order (int): Specifies the interaction level for neighbors within the lattice.
+ Default is 1, indicating nearest neighbours.
Returns:
- pennylane.LinearCombination: Hamiltonian for the heisenberg model.
+ ~ops.op_math.Sum: Hamiltonian for the heisenberg model.
**Example**
@@ -192,7 +194,7 @@ def fermi_hubbard(
neighbour_order=1,
mapping="jordan_wigner",
):
- r"""Generates the Fermi-Hubbard model on a lattice.
+ r"""Generates the Hamiltonian for the Fermi-Hubbard model on a lattice.
The Hamiltonian is represented as:
@@ -200,37 +202,41 @@ def fermi_hubbard(
\hat{H} = -t\sum_{, \sigma}(c_{i\sigma}^{\dagger}c_{j\sigma}) + U\sum_{i}n_{i \uparrow} n_{i\downarrow}
- where ``t`` is the hopping term representing the kinetic energy of electrons, ``U`` is the on-site Coulomb interaction,
- representing the repulsion between electrons, ``i,j`` represent the indices for neighbouring spins, ``\sigma``
- is the spin degree of freedom, and ``n_{i \uparrow}, n_{i \downarrow}`` are number operators for spin-up and
- spin-down fermions at site ``i``.
- This function assumes there are two fermions with opposite spins on each lattice site.
+ where ``t`` is the hopping term representing the kinetic energy of electrons, ``U`` is the
+ on-site Coulomb interaction, representing the repulsion between electrons, ``i,j`` represent the
+ indices for neighbouring spins, :math:`\sigma` is the spin degree of freedom, and
+ :math:`n_{i \uparrow}, n_{i \downarrow}` are number operators for spin-up and spin-down fermions
+ at site ``i``. This function assumes there are two fermions with opposite spins on each lattice
+ site.
Args:
- lattice (str): Shape of the lattice. Input Values can be ``'chain'``, ``'square'``,
- ``'rectangle'``, ``'honeycomb'``, ``'triangle'``, or ``'kagome'``.
- n_cells (List[int]): Number of cells in each direction of the grid.
- hopping (float or List[float] or List[math.array(float)]): Hopping strength between neighbouring sites, it can be a
- number, a list of length equal to ``neighbour_order`` or a square matrix of size
- ``(num_spins, num_spins)``. Default value is 1.0.
- coulomb (float or List[float]): Coulomb interaction between spins, it can be a constant or a list of length ``num_spins``.
- boundary_condition (bool or list[bool]): Defines boundary conditions for different lattice axes,
- default is ``False`` indicating open boundary condition.
- neighbour_order (int): Specifies the interaction level for neighbors within the lattice.
- Default is 1, indicating nearest neighbours.
- mapping (str): Specifies the fermion-to-qubit mapping. Input values can be
- ``'jordan_wigner'``, ``'parity'`` or ``'bravyi_kitaev'``.
+ lattice (str): Shape of the lattice. Input values can be ``'chain'``, ``'square'``,
+ ``'rectangle'``, ``'honeycomb'``, ``'triangle'``, or ``'kagome'``.
+ n_cells (List[int]): Number of cells in each direction of the grid.
+ hopping (float or List[float] or List[math.array(float)]): Hopping strength between
+ neighbouring sites, it can be a number, a list of length equal to ``neighbour_order`` or
+ a square matrix of size ``(num_spins, num_spins)``, where ``num_spins`` is the total
+ number of spins. Default value is 1.0.
+ coulomb (float or List[float]): Coulomb interaction between spins. It can be a constant or a
+ list of length equal to number of spins.
+ boundary_condition (bool or list[bool]): Defines boundary conditions for different lattice
+ axes, default is ``False`` indicating open boundary condition.
+ neighbour_order (int): Specifies the interaction level for neighbors within the lattice.
+ Default is 1, indicating nearest neighbours.
+ mapping (str): Specifies the fermion-to-qubit mapping. Input values can be
+ ``'jordan_wigner'``, ``'parity'`` or ``'bravyi_kitaev'``.
Returns:
- pennylane.operator: Hamiltonian for the Fermi-Hubbard model.
+ ~ops.op_math.Sum: Hamiltonian for the Fermi-Hubbard model.
**Example**
>>> n_cells = [2]
>>> h = [0.5]
- >>> u = [1.0]
+ >>> u = 1.0
>>> spin_ham = qml.spin.fermi_hubbard("chain", n_cells, hopping=h, coulomb=u)
>>> spin_ham
+ (
-0.25 * (Y(0) @ Z(1) @ Y(2))
+ -0.25 * (X(0) @ Z(1) @ X(2))
+ 0.5 * I(0)
@@ -242,7 +248,7 @@ def fermi_hubbard(
+ -0.25 * Z(3)
+ -0.25 * Z(2)
+ 0.25 * (Z(2) @ Z(3))
-
+ )
"""
lattice = _generate_lattice(lattice, n_cells, boundary_condition, neighbour_order)
diff --git a/pennylane/templates/subroutines/adder.py b/pennylane/templates/subroutines/adder.py
index 32a621c4985..074b92f6ab3 100644
--- a/pennylane/templates/subroutines/adder.py
+++ b/pennylane/templates/subroutines/adder.py
@@ -22,54 +22,79 @@
class Adder(Operation):
r"""Performs the in-place modular addition operation.
- This operator performs the modular addition by an integer :math:`k` modulo :math:`mod` in the
- computational basis:
+ This operator performs the modular addition by an integer :math:`k` modulo :math:`mod` in the
+ computational basis:
- .. math::
+ .. math::
- \text{Adder}(k, mod) |x \rangle = | x+k \; \text{modulo} \; mod \rangle.
+ \text{Adder}(k, mod) |x \rangle = | x+k \; \text{mod} \; mod \rangle.
- The implementation is based on the quantum Fourier transform method presented in
- `arXiv:2311.08555 `_.
+ The implementation is based on the quantum Fourier transform method presented in
+ `arXiv:2311.08555 `_.
.. note::
- Note that :math:`x` must be smaller than :math:`mod` to get the correct result.
+ To obtain the correct result, :math:`x` must be smaller than :math:`mod`.
+ .. seealso:: :class:`~.PhaseAdder` and :class:`~.OutAdder`.
- Args:
- k (int): the number that needs to be added
- x_wires (Sequence[int]): the wires the operation acts on
- mod (int): the modulus for performing the addition, default value is :math:`2^{len(x\_wires)}`
- work_wires (Sequence[int]): the auxiliary wires to be used for performing the addition
+ Args:
+ k (int): the number that needs to be added
+ x_wires (Sequence[int]): the wires the operation acts on. The number of wires must be enough
+ for encoding `x` in the computational basis. The number of wires also limits the
+ maximum value for `mod`.
+ mod (int): the modulo for performing the addition. If not provided, it will be set to its maximum value, :math:`2^{\text{len(x_wires)}}`.
+ work_wires (Sequence[int]): the auxiliary wires to use for the addition. The
+ work wires are not needed if :math:`mod=2^{\text{len(x_wires)}}`, otherwise two work wires
+ should be provided. Defaults to ``None``.
- **Example**
+ **Example**
This example computes the sum of two integers :math:`x=8` and :math:`k=5` modulo :math:`mod=15`.
- .. code-block::
+ .. code-block::
- x = 8
- k = 5
- mod = 15
+ x = 8
+ k = 5
+ mod = 15
- x_wires =[0,1,2,3]
- work_wires=[4,5]
+ x_wires =[0,1,2,3]
+ work_wires=[4,5]
- dev = qml.device("default.qubit", shots=1)
- @qml.qnode(dev)
- def circuit(x, k, mod, x_wires, work_wires):
- qml.BasisEmbedding(x, wires=x_wires)
- qml.Adder(k, x_wires, mod, work_wires)
- return qml.sample(wires=x_wires)
+ dev = qml.device("default.qubit", shots=1)
+ @qml.qnode(dev)
+ def circuit():
+ qml.BasisEmbedding(x, wires=x_wires)
+ qml.Adder(k, x_wires, mod, work_wires)
+ return qml.sample(wires=x_wires)
- .. code-block:: pycon
+ .. code-block:: pycon
- >>> print(circuit(x, k, mod,x_wires, work_wires))
- [1 1 0 1]
+ >>> print(circuit())
+ [1 1 0 1]
- The result, :math:`[1 1 0 1]`, is the ket representation of
- :math:`8 + 5 \, \text{modulo} \, 15 = 13`.
+ The result, :math:`[1 1 0 1]`, is the binary representation of
+ :math:`8 + 5 \; \text{modulo} \; 15 = 13`.
+
+ .. details::
+ :title: Usage Details
+
+ This template takes as input two different sets of wires.
+
+ The first one is ``x_wires``, used to encode the integer :math:`x < \text{mod}` in the Fourier basis.
+ To represent :math:`x`, ``x_wires`` must include at least :math:`\lceil \log_2(x) \rceil` wires.
+ After the modular addition, the result can be as large as :math:`\text{mod} - 1`,
+ requiring at least :math:`\lceil \log_2(\text{mod}) \rceil` wires. Since :math:`x < \text{mod}`,
+ :math:`\lceil \log_2(\text{mod}) \rceil` is a sufficient length for ``x_wires`` to cover all possible inputs and outputs.
+
+ The second set of wires is ``work_wires`` which consist of the auxiliary qubits used to perform the modular addition operation.
+
+ - If :math:`mod = 2^{\text{len(x_wires)}}`, there will be no need for ``work_wires``, hence ``work_wires=None``. This is the case by default.
+
+ - If :math:`mod \neq 2^{\text{len(x_wires)}}`, two ``work_wires`` have to be provided.
+
+ Note that the ``Adder`` template allows us to perform modular addition in the computational basis. However if one just wants to perform standard addition (with no modulo), that would be equivalent to setting
+ the modulo :math:`mod` to a large enough value to ensure that :math:`x+k < mod`.
"""
grad_method = None
@@ -80,17 +105,22 @@ def __init__(
x_wires = qml.wires.Wires(x_wires)
+ num_works_wires = 0 if work_wires is None else len(work_wires)
+
if mod is None:
mod = 2 ** len(x_wires)
- elif work_wires is None and mod != 2 ** len(x_wires):
- raise ValueError(f"If mod is not 2^{len(x_wires)}, two work wires should be provided.")
+ elif mod != 2 ** len(x_wires) and num_works_wires != 2:
+ raise ValueError(f"If mod is not 2^{len(x_wires)}, two work wires should be provided")
if not isinstance(k, int) or not isinstance(mod, int):
raise ValueError("Both k and mod must be integers")
if work_wires is not None:
if any(wire in work_wires for wire in x_wires):
raise ValueError("None of the wires in work_wires should be included in x_wires.")
if mod > 2 ** len(x_wires):
- raise ValueError("Adder must have enough x_wires to represent mod.")
+ raise ValueError(
+ "Adder must have enough x_wires to represent mod. The maximum mod "
+ f"with len(x_wires)={len(x_wires)} is {2 ** len(x_wires)}, but received {mod}."
+ )
self.hyperparameters["k"] = k
self.hyperparameters["mod"] = mod
@@ -135,11 +165,16 @@ def _primitive_bind_call(cls, *args, **kwargs):
@staticmethod
def compute_decomposition(k, x_wires, mod, work_wires): # pylint: disable=arguments-differ
r"""Representation of the operator as a product of other operators.
+
Args:
k (int): the number that needs to be added
- x_wires (Sequence[int]): the wires the operation acts on
- mod (int): the modulus for performing the addition, default value is :math:`2^{len(x\_wires)}`
- work_wires (Sequence[int]): the auxiliary wires to be used for performing the addition
+ x_wires (Sequence[int]): the wires the operation acts on. The number of wires must be enough
+ for encoding `x` in the computational basis. The number of wires also limits the
+ maximum value for `mod`.
+ mod (int): the modulo for performing the addition. If not provided, it will be set to its maximum value, :math:`2^{\text{len(x_wires)}}`.
+ work_wires (Sequence[int]): the auxiliary wires to use for the addition. The
+ work wires are not needed if :math:`mod=2^{\text{len(x_wires)}}`, otherwise two work wires
+ should be provided. Defaults to ``None``.
Returns:
list[.Operator]: Decomposition of the operator
diff --git a/pennylane/templates/subroutines/mod_exp.py b/pennylane/templates/subroutines/mod_exp.py
index 52d652d5dfb..37e15d016e7 100644
--- a/pennylane/templates/subroutines/mod_exp.py
+++ b/pennylane/templates/subroutines/mod_exp.py
@@ -14,6 +14,8 @@
"""
Contains the ModExp template.
"""
+import numpy as np
+
import pennylane as qml
from pennylane.operation import Operation
@@ -26,26 +28,28 @@ class ModExp(Operation):
.. math::
- \text{ModExp}(base,mod) |x \rangle |k \rangle = |x \rangle |k*base^x \, \text{mod} \, mod \rangle,
+ \text{ModExp}(base,mod) |x \rangle |b \rangle = |x \rangle |b \cdot base^x \; \text{mod} \; mod \rangle,
The implementation is based on the quantum Fourier transform method presented in
`arXiv:2311.08555 `_.
.. note::
- Note that :math:`x` must be smaller than :math:`mod` to get the correct result.
- Also, it is required that :math:`base` has inverse, :math:`base^-1` modulo :math:`mod`.
- That means :math:`base*base^-1 modulo mod = 1`, which will only be possible if :math:`base`
+ To obtain the correct result, :math:`x` must be smaller than :math:`mod`.
+ Also, it is required that :math:`base` has a modular inverse, :math:`base^{-1}`, with respect to :math:`mod`.
+ That means :math:`base \cdot base^{-1}` modulo :math:`mod` is equal to 1, which will only be possible if :math:`base`
and :math:`mod` are coprime.
+ .. seealso:: :class:`~.Multiplier`.
+
Args:
x_wires (Sequence[int]): the wires that store the integer :math:`x`
- output_wires (Sequence[int]): the wires that store the exponentiation result
+ output_wires (Sequence[int]): the wires that store the operator result. These wires also encode :math:`b`.
base (int): integer that needs to be exponentiated
- mod (int): the modulus for performing the exponentiation, default value is :math:`2^{len(output\_wires)}`
- work_wires (Sequence[int]): the auxiliary wires to be used for the exponentiation. There
- must be as many as ``output_wires`` and if :math:`mod \neq 2^{len(x\_wires)}`, two more
- wires must be added.
+ mod (int): the modulo for performing the exponentiation. If not provided, it will be set to its maximum value, :math:`2^{\text{len(output_wires)}}`
+ work_wires (Sequence[int]): the auxiliary wires to use for the exponentiation. If
+ :math:`mod=2^{\text{len(output_wires)}}`, the number of auxiliary wires must be ``len(output_wires)``. Otherwise
+ ``len(output_wires) + 2`` auxiliary wires are needed.
**Example**
@@ -53,17 +57,19 @@ class ModExp(Operation):
.. code-block::
- x, k = 3, 1
+ x, b = 3, 1
base = 2
mod = 7
+
x_wires = [0, 1]
output_wires = [2, 3, 4]
work_wires = [5, 6, 7, 8, 9]
+
dev = qml.device("default.qubit", shots=1)
@qml.qnode(dev)
def circuit():
qml.BasisEmbedding(x, wires = x_wires)
- qml.BasisEmbedding(k, wires = output_wires)
+ qml.BasisEmbedding(b, wires = output_wires)
qml.ModExp(x_wires, output_wires, base, mod, work_wires)
return qml.sample(wires = output_wires)
@@ -72,8 +78,35 @@ def circuit():
>>> print(circuit())
[0 0 1]
- The result :math:`[0 0 1]`, is the ket representation of
- :math:`2^3 \, \text{modulo} \, 7 = 1`.
+ The result :math:`[0 0 1]`, is the binary representation of
+ :math:`2^3 \; \text{modulo} \; 7 = 1`.
+
+ .. details::
+ :title: Usage Details
+
+ This template takes as input three different sets of wires.
+
+ The first one is ``x_wires`` which is used
+ to encode the integer :math:`x < mod` in the computational basis. Therefore, ``x_wires`` must contain at least
+ :math:`\lceil \log_2(x)\rceil` wires to represent :math:`x`.
+
+ The second one is ``output_wires`` which is used
+ to encode the integer :math:`b \cdot base^x \; \text{mod} \; mod` in the computational basis. Therefore, at least
+ :math:`\lceil \log_2(mod)\rceil` ``output_wires`` are required to represent :math:`b \cdot base^x \; \text{mod} \; mod`. Note that these wires can be initialized with any integer
+ :math:`b`, but the most common choice is :math:`b=1` to obtain as a final result :math:`base^x \; \text{mod} \; mod`.
+
+ The third set of wires is ``work_wires`` which consist of the auxiliary qubits used to perform the modular exponentiation operation.
+
+ - If :math:`mod = 2^{\text{len(output_wires)}}`, the length of ``work_wires`` must be equal to the length of ``output_wires``.
+
+ - If :math:`mod \neq 2^{\text{len(output_wires)}}`, the length of ``work_wires`` must be ``len(output_wires) + 2``
+
+ Note that the ``ModExp`` template allows us to perform modular exponentiation in the computational basis. However if one just wants to perform standard exponentiation (with no modulo),
+ that would be equivalent to setting the modulo :math:`mod` to a large enough value to ensure that :math:`base^x < mod`.
+
+ Also, to perform the out-place modular exponentiation operator it is required that :math:`base` has inverse, :math:`base^{-1} \; \text{mod} \; mod`. That means
+ :math:`base \cdot base^{-1}` modulo :math:`mod` is equal to 1, which will only be possible if :math:`base` and
+ :math:`mod` are coprime. In other words, :math:`base` and :math:`mod` should not have any common factors other than 1.
"""
grad_method = None
@@ -84,11 +117,14 @@ def __init__(
output_wires = qml.wires.Wires(output_wires)
+ if work_wires is None:
+ raise ValueError("Work wires must be specified for ModExp")
+
if mod is None:
mod = 2 ** (len(output_wires))
if len(output_wires) == 0 or (mod > 2 ** (len(output_wires))):
raise ValueError("ModExp must have enough wires to represent mod.")
- if mod != 2 ** len(x_wires):
+ if mod != 2 ** len(output_wires):
if len(work_wires) < (len(output_wires) + 2):
raise ValueError("ModExp needs as many work_wires as output_wires plus two.")
else:
@@ -103,6 +139,10 @@ def __init__(
)
if any(wire in x_wires for wire in output_wires):
raise ValueError("None of the wires in x_wires should be included in output_wires.")
+
+ if np.gcd(base, mod) != 1:
+ raise ValueError("The operator cannot be built because base has no inverse modulo mod.")
+
wire_keys = ["x_wires", "output_wires", "work_wires"]
for key in wire_keys:
self.hyperparameters[key] = qml.wires.Wires(locals()[key])
@@ -161,13 +201,15 @@ def compute_decomposition(
x_wires, output_wires, base, mod, work_wires
): # pylint: disable=arguments-differ
r"""Representation of the operator as a product of other operators.
+
Args:
x_wires (Sequence[int]): the wires that store the integer :math:`x`
- output_wires (Sequence[int]): the wires that store the exponentiation result
+ output_wires (Sequence[int]): the wires that store the operator result. These wires also encode :math:`b`.
base (int): integer that needs to be exponentiated
- mod (int): the modulus for performing the exponentiation, default value is :math:`2^{len(output\_wires)}`
- work_wires (Sequence[int]): the auxiliary wires to be used for the exponentiation. There must be as many as ``output_wires`` and if :math:`mod \neq 2^{len(x\_wires)}`, two more wires must be added.
-
+ mod (int): the modulo for performing the exponentiation. If not provided, it will be set to its maximum value, :math:`2^{\text{len(output_wires)}}`
+ work_wires (Sequence[int]): the auxiliary wires to use for the exponentiation. If
+ :math:`mod=2^{\text{len(output_wires)}}`, the number of auxiliary wires must be ``len(output_wires)``. Otherwise
+ ``len(output_wires) + 2`` auxiliary wires are needed.
Returns:
list[.Operator]: Decomposition of the operator
diff --git a/pennylane/templates/subroutines/multiplier.py b/pennylane/templates/subroutines/multiplier.py
index dbc3ff96325..70a929c018b 100644
--- a/pennylane/templates/subroutines/multiplier.py
+++ b/pennylane/templates/subroutines/multiplier.py
@@ -41,24 +41,25 @@ class Multiplier(Operation):
.. math::
- \text{Multiplier}(k,mod) |x \rangle = | x \cdot k \; \text{modulo} \; \text{mod} \rangle.
+ \text{Multiplier}(k,mod) |x \rangle = | x \cdot k \; \text{mod} \; mod \rangle.
The implementation is based on the quantum Fourier transform method presented in
`arXiv:2311.08555 `_.
.. note::
- Note that :math:`x` must be smaller than :math:`mod` to get the correct result. Also, it
- is required that :math:`k` has inverse, :math:`k^-1`, modulo :math:`mod`. That means
- :math:`k*k^-1 modulo mod is equal to 1`, which will only be possible if :math:`k` and
- :math:`mod` are coprime. Furthermore, if :math:`mod \neq 2^{len(x\_wires)}`, two more
- auxiliaries must be added.
+ To obtain the correct result, :math:`x` must be smaller than :math:`mod`. Also, it
+ is required that :math:`k` has modular inverse :math:`k^{-1}` with respect to :math:`mod`. That means
+ :math:`k \cdot k^{-1}` modulo :math:`mod` is equal to 1, which will only be possible if :math:`k` and
+ :math:`mod` are coprime.
+
+ .. seealso:: :class:`~.PhaseAdder` and :class:`~.OutMultiplier`.
Args:
k (int): the number that needs to be multiplied
- x_wires (Sequence[int]): the wires the operation acts on
- mod (int): the modulus for performing the multiplication, default value is :math:`2^{len(x\_wires)}`
- work_wires (Sequence[int]): the auxiliary wires to be used for performing the multiplication
+ x_wires (Sequence[int]): the wires the operation acts on. The number of wires must be enough for encoding `x` in the computational basis. The number of wires also limits the maximum value for `mod`.
+ mod (int): the modulo for performing the multiplication. If not provided, it will be set to its maximum value, :math:`2^{\text{len(x_wires)}}`.
+ work_wires (Sequence[int]): the auxiliary wires to use for the multiplication. If :math:`mod=2^{\text{len(x_wires)}}`, the number of auxiliary wires must be ``len(x_wires)``. Otherwise ``len(x_wires) + 2`` auxiliary wires are needed.
**Example**
@@ -70,23 +71,47 @@ class Multiplier(Operation):
k = 4
mod = 7
- x_wires =[0,1,2]
- work_wires=[3,4,5,6,7]
+ x_wires = [0,1,2]
+ work_wires = [3,4,5,6,7]
dev = qml.device("default.qubit", shots=1)
@qml.qnode(dev)
- def circuit(x, k, mod, wires_m, work_wires):
- qml.BasisEmbedding(x, wires=wires_m)
+ def circuit():
+ qml.BasisEmbedding(x, wires=x_wires)
qml.Multiplier(k, x_wires, mod, work_wires)
- return qml.sample(wires=wires_m)
+ return qml.sample(wires=x_wires)
.. code-block:: pycon
- >>> print(circuit(x, k, mod, x_wires, work_wires))
+ >>> print(circuit())
[1 0 1]
- The result :math:`[1 0 1]`, is the ket representation of
- :math:`3 \cdot 4 \, \text{modulo} \, 12 = 5`.
+ The result :math:`[1 0 1]`, is the binary representation of
+ :math:`3 \cdot 4 \; \text{modulo} \; 7 = 5`.
+
+ .. details::
+ :title: Usage Details
+
+ This template takes as input two different sets of wires.
+
+ The first one is ``x_wires``, used to encode the integer :math:`x < \text{mod}` in the Fourier basis.
+ To represent :math:`x`, ``x_wires`` must include at least :math:`\lceil \log_2(x) \rceil` wires.
+ After the modular addition, the result can be as large as :math:`\text{mod} - 1`,
+ requiring at least :math:`\lceil \log_2(\text{mod}) \rceil` wires. Since :math:`x < \text{mod}`,
+ :math:`\lceil \log_2(\text{mod}) \rceil` is a sufficient length for ``x_wires`` to cover all possible inputs and outputs.
+
+ The second set of wires is ``work_wires`` which consist of the auxiliary qubits used to perform the modular multiplication operation.
+
+ - If :math:`mod = 2^{\text{len(x_wires)}}`, the length of ``work_wires`` must be equal to the length of ``x_wires``.
+
+ - If :math:`mod \neq 2^{\text{len(x_wires)}}`, the length of ``work_wires`` must be ``len(x_wires) + 2``.
+
+ Note that the ``Multiplier`` template allows us to perform modular multiplication in the computational basis. However if one just want to perform standard multiplication (with no modulo),
+ that would be equivalent to setting the modulo :math:`mod` to a large enough value to ensure that :math:`x \cdot k < mod`.
+
+ Also, to perform the in-place multiplication operator it is required that :math:`k` has inverse, :math:`k^{-1} \; \text{mod} \; mod`. That means
+ :math:`k \cdot k^{-1}` modulo :math:`mod` is equal to 1, which will only be possible if :math:`k` and
+ :math:`mod` are coprime. In other words, :math:`k` and :math:`mod` should not have any common factors other than 1.
"""
grad_method = None
@@ -94,17 +119,26 @@ def circuit(x, k, mod, wires_m, work_wires):
def __init__(
self, k, x_wires, mod=None, work_wires=None, id=None
): # pylint: disable=too-many-arguments
+ if work_wires is None:
+ raise ValueError("Work wires must be specified for Multiplier")
+
+ x_wires = qml.wires.Wires(x_wires)
+ work_wires = qml.wires.Wires(work_wires)
+
if any(wire in work_wires for wire in x_wires):
raise ValueError("None of the wire in work_wires should be included in x_wires.")
if mod is None:
mod = 2 ** len(x_wires)
- if mod != 2 ** len(x_wires) and len(work_wires) < (len(x_wires) + 2):
+ if mod != 2 ** len(x_wires) and len(work_wires) != (len(x_wires) + 2):
raise ValueError("Multiplier needs as many work_wires as x_wires plus two.")
if len(work_wires) < len(x_wires):
raise ValueError("Multiplier needs as many work_wires as x_wires.")
- if (not hasattr(x_wires, "__len__")) or (mod > 2 ** len(x_wires)):
- raise ValueError("Multiplier must have enough wires to represent mod.")
+ if mod > 2 ** len(x_wires):
+ raise ValueError(
+ "Multiplier must have enough wires to represent mod. The maximum mod "
+ f"with len(x_wires)={len(x_wires)} is {2 ** len(x_wires)}, but received {mod}."
+ )
k = k % mod
if np.gcd(k, mod) != 1:
@@ -158,11 +192,12 @@ def _primitive_bind_call(cls, *args, **kwargs):
@staticmethod
def compute_decomposition(k, x_wires, mod, work_wires): # pylint: disable=arguments-differ
r"""Representation of the operator as a product of other operators.
+
Args:
k (int): the number that needs to be multiplied
- x_wires (Sequence[int]): the wires the operation acts on
- mod (int): the modulus for performing the multiplication, default value is :math:`2^{len(x\_wires)}`
- work_wires (Sequence[int]): the auxiliary wires to be used for performing the multiplication
+ x_wires (Sequence[int]): the wires the operation acts on. The number of wires must be enough for encoding `x` in the computational basis. The number of wires also limits the maximum value for `mod`.
+ mod (int): the modulo for performing the multiplication. If not provided, it will be set to its maximum value, :math:`2^{\text{len(x_wires)}}`.
+ work_wires (Sequence[int]): the auxiliary wires to use for the multiplication. If :math:`mod=2^{\text{len(x_wires)}}`, the number of auxiliary wires must be ``len(x_wires)``. Otherwise ``len(x_wires) + 2`` auxiliary wires are needed.
Returns:
list[.Operator]: Decomposition of the operator
diff --git a/pennylane/templates/subroutines/out_adder.py b/pennylane/templates/subroutines/out_adder.py
index 2f8a398e1a6..40006e402fc 100644
--- a/pennylane/templates/subroutines/out_adder.py
+++ b/pennylane/templates/subroutines/out_adder.py
@@ -27,25 +27,28 @@ class OutAdder(Operation):
.. math::
- \text{OutAdder}(mod) |x \rangle | y \rangle | b \rangle = |x \rangle | y \rangle | b+x+y \, \text{mod} \, mod \rangle,
+ \text{OutAdder}(mod) |x \rangle | y \rangle | b \rangle = |x \rangle | y \rangle | b+x+y \; \text{mod} \; mod \rangle,
The implementation is based on the quantum Fourier transform method presented in
`arXiv:2311.08555 `_.
.. note::
- Note that :math:`x` and :math:`y` must be smaller than :math:`mod` to get the correct result.
+ To obtain the correct result, :math:`x`, :math:`y` and :math:`b` must be smaller than :math:`mod`.
+
+ .. seealso:: :class:`~.PhaseAdder` and :class:`~.Adder`.
Args:
x_wires (Sequence[int]): the wires that store the integer :math:`x`
y_wires (Sequence[int]): the wires that store the integer :math:`y`
- output_wires (Sequence[int]): the wires that store the addition result
- mod (int): the modulus for performing the addition, default value is :math:`2^{\text{len(output\_wires)}}`
- work_wires (Sequence[int]): the auxiliary wires to use for the addition
+ output_wires (Sequence[int]): the wires that store the addition result. If the register is in a non-zero state :math:`b`, the solution will be added to this value.
+ mod (int): the modulo for performing the addition. If not provided, it will be set to its maximum value, :math:`2^{\text{len(output_wires)}}`.
+ work_wires (Sequence[int]): the auxiliary wires to use for the addition. The work wires are not needed if :math:`mod=2^{\text{len(output_wires)}}`, otherwise two work wires should be provided. Defaults to ``None``.
**Example**
This example computes the sum of two integers :math:`x=5` and :math:`y=6` modulo :math:`mod=7`.
+ We'll let :math:`b=0`. See Usage Details for :math:`b \neq 0`.
.. code-block::
@@ -71,8 +74,65 @@ def circuit():
>>> print(circuit())
[1 0 0]
- The result :math:`[1 0 0]`, is the ket representation of
- :math:`5 + 6 \, \text{modulo} \, 7 = 4`.
+ The result :math:`[1 0 0]`, is the binary representation of
+ :math:`5 + 6 \; \text{modulo} \; 7 = 4`.
+
+ .. details::
+ :title: Usage Details
+
+ This template takes as input four different sets of wires.
+
+ The first one is ``x_wires`` which is used
+ to encode the integer :math:`x < mod` in the computational basis. Therefore, ``x_wires`` must contain
+ at least :math:`\lceil \log_2(x)\rceil` to represent :math:`x`.
+
+ The second one is ``y_wires`` which is used
+ to encode the integer :math:`y < mod` in the computational basis. Therefore, ``y_wires`` must contain
+ at least :math:`\lceil \log_2(y)\rceil` wires to represent :math:`y`.
+
+ The third one is ``output_wires`` which is used
+ to encode the integer :math:`b+x+y \; \text{mod} \; mod` in the computational basis. Therefore, it will require at least
+ :math:`\lceil \log_2(mod)\rceil` ``output_wires`` to represent :math:`b+x+y \; \text{mod} \; mod`. Note that these wires can be initialized with any integer
+ :math:`b < mod`, but the most common choice is :math:`b=0` to obtain as a final result :math:`x + y \; \text{mod} \; mod`.
+ The following is an example for :math:`b = 1`.
+
+ .. code-block::
+
+ b=1
+ x=5
+ y=6
+ mod=7
+
+ x_wires=[0,1,2]
+ y_wires=[3,4,5]
+ output_wires=[7,8,9]
+ work_wires=[6,10]
+
+ dev = qml.device("default.qubit", shots=1)
+ @qml.qnode(dev)
+ def circuit():
+ qml.BasisEmbedding(x, wires=x_wires)
+ qml.BasisEmbedding(y, wires=y_wires)
+ qml.BasisEmbedding(b, wires=output_wires)
+ qml.OutAdder(x_wires, y_wires, output_wires, mod, work_wires)
+ return qml.sample(wires=output_wires)
+
+ .. code-block:: pycon
+
+ >>> print(circuit())
+ [1 0 1]
+
+ The result :math:`[1 0 1]`, is the binary representation of
+ :math:`5 + 6 + 1\; \text{modulo} \; 7 = 5`.
+
+ The fourth set of wires is ``work_wires`` which consist of the auxiliary qubits used to perform the modular addition operation.
+
+ - If :math:`mod = 2^{\text{len(output_wires)}}`, there will be no need for ``work_wires``, hence ``work_wires=None``. This is the case by default.
+
+ - If :math:`mod \neq 2^{\text{len(output_wires)}}`, two ``work_wires`` have to be provided.
+
+ Note that the ``OutAdder`` template allows us to perform modular addition in the computational basis. However if one just wants to perform standard addition (with no modulo),
+ that would be equivalent to setting the modulo :math:`mod` to a large enough value to ensure that :math:`x+k < mod`.
"""
grad_method = None
@@ -81,10 +141,23 @@ def __init__(
self, x_wires, y_wires, output_wires, mod=None, work_wires=None, id=None
): # pylint: disable=too-many-arguments
+ x_wires = qml.wires.Wires(x_wires)
+ y_wires = qml.wires.Wires(y_wires)
+ output_wires = qml.wires.Wires(output_wires)
+
+ num_work_wires = 0 if work_wires is None else len(work_wires)
+
if mod is None:
mod = 2 ** (len(output_wires))
- if (not hasattr(output_wires, "__len__")) or (mod > 2 ** len(output_wires)):
- raise ValueError("OutAdder must have enough wires to represent mod.")
+ if mod > 2 ** len(output_wires):
+ raise ValueError(
+ "OutAdder must have enough wires to represent mod. The maximum mod "
+ f"with len(output_wires)={len(output_wires)} is {2 ** len(output_wires)}, but received {mod}."
+ )
+ if mod != 2 ** len(output_wires) and num_work_wires != 2:
+ raise ValueError(
+ f"If mod is not 2^{len(output_wires)}, two work wires should be provided."
+ )
if work_wires is not None:
if any(wire in work_wires for wire in x_wires):
raise ValueError("None of the wires in work_wires should be included in x_wires.")
@@ -150,12 +223,13 @@ def compute_decomposition(
x_wires, y_wires, output_wires, mod, work_wires
): # pylint: disable=arguments-differ
r"""Representation of the operator as a product of other operators.
+
Args:
x_wires (Sequence[int]): the wires that store the integer :math:`x`
y_wires (Sequence[int]): the wires that store the integer :math:`y`
- output_wires (Sequence[int]): the wires that store the addition result
- mod (int): the modulus for performing the addition, default value is :math:`2^{\text{len(output\_wires)}}`
- work_wires (Sequence[int]): the auxiliary wires to use for the addition
+ output_wires (Sequence[int]): the wires that store the addition result. If the register is in a non-zero state :math:`b`, the solution will be added to this value.
+ mod (int): the modulo for performing the addition. If not provided, it will be set to its maximum value, :math:`2^{\text{len(output_wires)}}`.
+ work_wires (Sequence[int]): the auxiliary wires to use for the addition. The work wires are not needed if :math:`mod=2^{\text{len(output_wires)}}`, otherwise two work wires should be provided. Defaults to ``None``.
Returns:
list[.Operator]: Decomposition of the operator
diff --git a/pennylane/templates/subroutines/out_multiplier.py b/pennylane/templates/subroutines/out_multiplier.py
index 3fd9a8a2043..d551fd8299e 100644
--- a/pennylane/templates/subroutines/out_multiplier.py
+++ b/pennylane/templates/subroutines/out_multiplier.py
@@ -26,25 +26,30 @@ class OutMultiplier(Operation):
:math:`mod` in the computational basis:
.. math::
- \text{OutMultiplier}(mod) |x \rangle |y \rangle |b \rangle = |x \rangle |y \rangle |b + x \cdot y \; \text{modulo} \; mod \rangle,
+ \text{OutMultiplier}(mod) |x \rangle |y \rangle |b \rangle = |x \rangle |y \rangle |b + x \cdot y \; \text{mod} \; mod \rangle,
The implementation is based on the quantum Fourier transform method presented in
`arXiv:2311.08555 `_.
.. note::
- Note that :math:`x` and :math:`y` must be smaller than :math:`mod` to get the correct result.
+ To obtain the correct result, :math:`x`, :math:`y` and :math:`b` must be smaller than :math:`mod`.
+
+ .. seealso:: :class:`~.PhaseAdder` and :class:`~.Multiplier`.
Args:
x_wires (Sequence[int]): the wires that store the integer :math:`x`
y_wires (Sequence[int]): the wires that store the integer :math:`y`
- output_wires (Sequence[int]): the wires that store the multiplication result
- mod (int): the modulus for performing the multiplication, default value is :math:`2^{len(output\_wires)}`
- work_wires (Sequence[int]): the auxiliary wires to use for the multiplication modulo
+ output_wires (Sequence[int]): the wires that store the multiplication result. If the register is in a non-zero state :math:`b`, the solution will be added to this value
+ mod (int): the modulo for performing the multiplication. If not provided, it will be set to its maximum value, :math:`2^{\text{len(output_wires)}}`
+ work_wires (Sequence[int]): the auxiliary wires to use for the multiplication. The
+ work wires are not needed if :math:`mod=2^{\text{len(output_wires)}}`, otherwise two work wires
+ should be provided. Defaults to ``None``.
**Example**
This example performs the multiplication of two integers :math:`x=2` and :math:`y=7` modulo :math:`mod=12`.
+ We'll let :math:`b=0`. See Usage Details for :math:`b \neq 0`.
.. code-block::
@@ -70,8 +75,65 @@ def circuit():
>>> print(circuit())
[0 0 1 0]
- The result :math:`[0 0 1 0]`, is the ket representation of
- :math:`2 \cdot 7 \, \text{modulo} \, 12 = 2`.
+ The result :math:`[0 0 1 0]`, is the binary representation of
+ :math:`2 \cdot 7 \; \text{modulo} \; 12 = 2`.
+
+ .. details::
+ :title: Usage Details
+
+ This template takes as input four different sets of wires.
+
+ The first one is ``x_wires`` which is used
+ to encode the integer :math:`x < mod` in the computational basis. Therefore, ``x_wires`` must contain
+ at least :math:`\lceil \log_2(x)\rceil` wires to represent :math:`x`.
+
+ The second one is ``y_wires`` which is used
+ to encode the integer :math:`y < mod` in the computational basis. Therefore, ``y_wires`` must contain
+ at least :math:`\lceil \log_2(y)\rceil` wires to represent :math:`y`.
+
+ The third one is ``output_wires`` which is used
+ to encode the integer :math:`b+ x \cdot y \; \text{mod} \; mod` in the computational basis. Therefore, it will require at least
+ :math:`\lceil \log_2(mod)\rceil` ``output_wires`` to represent :math:`b + x \cdot y \; \text{mod} \; mod`. Note that these wires can be initialized with any integer
+ :math:`b < mod`, but the most common choice is :math:`b=0` to obtain as a final result :math:`x \cdot y \; \text{mod} \; mod`.
+ The following is an example for :math:`b = 1`.
+
+ .. code-block::
+
+ b = 1
+ x = 2
+ y = 7
+ mod = 12
+
+ x_wires = [0, 1]
+ y_wires = [2, 3, 4]
+ output_wires = [6, 7, 8, 9]
+ work_wires = [5, 10]
+
+ dev = qml.device("default.qubit", shots=1)
+ @qml.qnode(dev)
+ def circuit():
+ qml.BasisEmbedding(x, wires=x_wires)
+ qml.BasisEmbedding(y, wires=y_wires)
+ qml.BasisEmbedding(b, wires=output_wires)
+ qml.OutMultiplier(x_wires, y_wires, output_wires, mod, work_wires)
+ return qml.sample(wires=output_wires)
+
+ .. code-block:: pycon
+
+ >>> print(circuit())
+ [0 0 1 1]
+
+ The result :math:`[0 0 1 1]`, is the binary representation of
+ :math:`2 \cdot 7 + 1\; \text{modulo} \; 12 = 3`.
+
+ The fourth set of wires is ``work_wires`` which consist of the auxiliary qubits used to perform the modular multiplication operation.
+
+ - If :math:`mod = 2^{\text{len(output_wires)}}`, there will be no need for ``work_wires``, hence ``work_wires=None``. This is the case by default.
+
+ - If :math:`mod \neq 2^{\text{len(output_wires)}}`, two ``work_wires`` have to be provided.
+
+ Note that the ``OutMultiplier`` template allows us to perform modular multiplication in the computational basis. However if one just wants to perform
+ standard multiplication (with no modulo), that would be equivalent to setting the modulo :math:`mod` to a large enough value to ensure that :math:`x \cdot k < mod`.
"""
grad_method = None
@@ -80,14 +142,23 @@ def __init__(
self, x_wires, y_wires, output_wires, mod=None, work_wires=None, id=None
): # pylint: disable=too-many-arguments
+ x_wires = qml.wires.Wires(x_wires)
+ y_wires = qml.wires.Wires(y_wires)
+ output_wires = qml.wires.Wires(output_wires)
+
+ num_work_wires = 0 if work_wires is None else len(work_wires)
+
if mod is None:
mod = 2 ** len(output_wires)
- if mod != 2 ** len(output_wires) and work_wires is None:
+ if mod != 2 ** len(output_wires) and num_work_wires != 2:
raise ValueError(
f"If mod is not 2^{len(output_wires)}, two work wires should be provided."
)
- if (not hasattr(output_wires, "__len__")) or (mod > 2 ** (len(output_wires))):
- raise ValueError("OutMultiplier must have enough wires to represent mod.")
+ if mod > 2 ** (len(output_wires)):
+ raise ValueError(
+ "OutMultiplier must have enough wires to represent mod. The maximum mod "
+ f"with len(output_wires)={len(output_wires)} is {2 ** len(output_wires)}, but received {mod}."
+ )
if work_wires is not None:
if any(wire in work_wires for wire in x_wires):
@@ -159,12 +230,16 @@ def compute_decomposition(
x_wires, y_wires, output_wires, mod, work_wires
): # pylint: disable=arguments-differ
r"""Representation of the operator as a product of other operators.
+
Args:
x_wires (Sequence[int]): the wires that store the integer :math:`x`
y_wires (Sequence[int]): the wires that store the integer :math:`y`
- output_wires (Sequence[int]): the wires that store the multiplication result
- mod (int): the modulus for performing the multiplication, default value is :math:`2^{len(output\_wires)}`
- work_wires (Sequence[int]): the auxiliary wires to use for the multiplication
+ output_wires (Sequence[int]): the wires that store the multiplication result. If the register is in a non-zero state :math:`b`, the solution will be added to this value
+ mod (int): the modulo for performing the multiplication. If not provided, it will be set to its maximum value, :math:`2^{\text{len(output_wires)}}`
+ work_wires (Sequence[int]): the auxiliary wires to use for the multiplication. The
+ work wires are not needed if :math:`mod=2^{\text{len(output_wires)}}`, otherwise two work wires
+ should be provided. Defaults to ``None``.
+
Returns:
list[.Operator]: Decomposition of the operator
diff --git a/pennylane/templates/subroutines/phase_adder.py b/pennylane/templates/subroutines/phase_adder.py
index e76a9ff8c1e..548857eb9a8 100644
--- a/pennylane/templates/subroutines/phase_adder.py
+++ b/pennylane/templates/subroutines/phase_adder.py
@@ -37,28 +37,34 @@ class PhaseAdder(Operation):
.. math::
- \text{PhaseAdder}(k,mod) |\phi (x) \rangle = |\phi (x+k \; \text{modulo} \; mod) \rangle,
+ \text{PhaseAdder}(k,mod) |\phi (x) \rangle = |\phi (x+k \; \text{mod} \; mod) \rangle,
- where :math:`|\phi (x) \rangle` represents the :math:`| x \rangle` : state in the Fourier basis,
+ where :math:`|\phi (x) \rangle` represents the :math:`| x \rangle` state in the Fourier basis,
- .. math::
+ .. math::
- \text{QFT} |x \rangle = |\phi (x) \rangle.
+ \text{QFT} |x \rangle = |\phi (x) \rangle.
The implementation is based on the quantum Fourier transform method presented in
`arXiv:2311.08555 `_.
.. note::
- Note that :math:`x` must be smaller than :math:`mod` to get the correct result. Also, when
- :math:`mod \neq 2^{\text{len(x\_wires)}}` we need :math:`x < 2^{\text{len(x\_wires)}}/2`,
- which means that we need one extra wire in ``x_wires``.
+ To obtain the correct result, :math:`x` must be smaller than :math:`mod`. Also, when
+ :math:`mod \neq 2^{\text{len(x_wires)}}`, :math:`x` must satisfy :math:`x < 2^{\text{len(x_wires)}-1}`,
+ which means that one extra wire in ``x_wires`` is required.
+
+ .. seealso:: :class:`~.QFT` and :class:`~.Adder`.
Args:
k (int): the number that needs to be added
- x_wires (Sequence[int]): the wires the operation acts on
- mod (int): the modulus for performing the addition, default value is :math:`2^{len(x\_wires)}`
- work_wire (Sequence[int]): the auxiliary wire to be used for performing the addition
+ x_wires (Sequence[int]): the wires the operation acts on. The number of wires must be enough
+ for a binary representation of the value being targeted, :math:`x`. In some cases an additional
+ wire is needed, see usage details below. The number of wires also limits the maximum
+ value for `mod`.
+ mod (int): the modulo for performing the addition. If not provided, it will be set to its maximum value, :math:`2^{\text{len(x_wires)}}`.
+ work_wire (Sequence[int] or int): the auxiliary wire to use for the addition. Optional
+ when `mod` is :math:`2^{len(x\_wires)}`. Defaults to ``None``.
**Example**
@@ -75,7 +81,7 @@ class PhaseAdder(Operation):
dev = qml.device("default.qubit", shots=1)
@qml.qnode(dev)
- def circuit(x, k, mod, x_wires, work_wire):
+ def circuit():
qml.BasisEmbedding(x, wires=x_wires)
qml.QFT(wires=x_wires)
qml.PhaseAdder(k, x_wires, mod, work_wire)
@@ -84,11 +90,34 @@ def circuit(x, k, mod, x_wires, work_wire):
.. code-block:: pycon
- >>> print(circuit(x, k, mod, x_wires, work_wire))
+ >>> print(circuit())
[1 1 0 1]
- The result, :math:`[1 1 0 1]`, is the ket representation of
- :math:`8 + 5 \, \text{modulo} \, 15 = 13`.
+ The result, :math:`[1 1 0 1]`, is the binary representation of
+ :math:`8 + 5 \; \text{modulo} \; 15 = 13`.
+
+ .. details::
+ :title: Usage Details
+
+ This template takes as input two different sets of wires.
+
+ The first one is ``x_wires``, used to encode the integer :math:`x < \text{mod}` in the Fourier basis.
+ To represent :math:`x`, at least :math:`\lceil \log_2(x) \rceil` wires are needed.
+ After the modular addition, the result can be as large as :math:`\text{mod} - 1`,
+ requiring at least :math:`\lceil \log_2(\text{mod}) \rceil` wires. Since :math:`x < \text{mod}`, a length of
+ :math:`\lceil \log_2(\text{mod}) \rceil` is sufficient for ``x_wires`` to cover all possible inputs and
+ outputs when :math:`mod = 2^{\text{len(x_wires)}}`.
+ An exception occurs when :math:`mod \neq 2^{\text{len(x_wires)}}`. In that case one extra wire in ``x_wires`` will be needed to correctly perform the phase
+ addition operation.
+
+ The second set of wires is ``work_wire`` which consist of the auxiliary qubit used to perform the modular phase addition operation.
+
+ - If :math:`mod = 2^{\text{len(x_wires)}}`, there will be no need for ``work_wire``, hence ``work_wire=None``. This is the case by default.
+
+ - If :math:`mod \neq 2^{\text{len(x_wires)}}`, one ``work_wire`` has to be provided.
+
+ Note that the ``PhaseAdder`` template allows us to perform modular addition in the Fourier basis. However if one just wants to perform standard addition (with no modulo),
+ that would be equivalent to setting the modulo :math:`mod` to a large enough value to ensure that :math:`x+k < mod`.
"""
grad_method = None
@@ -97,15 +126,22 @@ def __init__(
self, k, x_wires, mod=None, work_wire=None, id=None
): # pylint: disable=too-many-arguments
+ work_wire = qml.wires.Wires(work_wire) if work_wire is not None else work_wire
x_wires = qml.wires.Wires(x_wires)
+
+ num_work_wires = 0 if work_wire is None else len(work_wire)
+
if mod is None:
mod = 2 ** len(x_wires)
- elif work_wire is None and mod != 2 ** len(x_wires):
+ elif mod != 2 ** len(x_wires) and num_work_wires != 1:
raise ValueError(f"If mod is not 2^{len(x_wires)}, one work wire should be provided.")
if not isinstance(k, int) or not isinstance(mod, int):
raise ValueError("Both k and mod must be integers")
if mod > 2 ** len(x_wires):
- raise ValueError("PhaseAdder must have enough x_wires to represent mod.")
+ raise ValueError(
+ "PhaseAdder must have enough x_wires to represent mod. The maximum mod "
+ f"with len(x_wires)={len(x_wires)} is {2 ** len(x_wires)}, but received {mod}."
+ )
if work_wire is not None:
if any(wire in work_wire for wire in x_wires):
raise ValueError("None of the wires in work_wire should be included in x_wires.")
@@ -153,11 +189,16 @@ def _primitive_bind_call(cls, *args, **kwargs):
@staticmethod
def compute_decomposition(k, x_wires, mod, work_wire): # pylint: disable=arguments-differ
r"""Representation of the operator as a product of other operators.
+
Args:
k (int): the number that needs to be added
- x_wires (Sequence[int]): the wires the operation acts on
- mod (int): the modulus for performing the addition, default value is :math:`2^{len(x_wires)}`
- work_wire (Sequence[int]): the auxiliary wire to be used for performing the addition
+ x_wires (Sequence[int]): the wires the operation acts on. The number of wires must be enough
+ for a binary representation of the value being targeted, :math:`x`. In some cases an additional
+ wire is needed, see usage details below. The number of wires also limits the maximum
+ value for `mod`.
+ mod (int): the modulo for performing the addition. If not provided, it will be set to its maximum value, :math:`2^{\text{len(x_wires)}}`.
+ work_wire (Sequence[int]): the auxiliary wire to use for the addition. Optional
+ when `mod` is :math:`2^{len(x\_wires)}`. Defaults to ``None``.
Returns:
list[.Operator]: Decomposition of the operator
diff --git a/tests/capture/test_templates.py b/tests/capture/test_templates.py
index d9c4b0d96ee..d04990b2971 100644
--- a/tests/capture/test_templates.py
+++ b/tests/capture/test_templates.py
@@ -876,7 +876,7 @@ def test_mod_exp(self):
kwargs = {
"x_wires": [0, 1],
"output_wires": [4, 5],
- "base": 2,
+ "base": 3,
"mod": None,
"work_wires": [2, 3],
}
diff --git a/tests/devices/default_qubit/test_default_qubit.py b/tests/devices/default_qubit/test_default_qubit.py
index 74d9628d1e7..8b3a1e257dd 100644
--- a/tests/devices/default_qubit/test_default_qubit.py
+++ b/tests/devices/default_qubit/test_default_qubit.py
@@ -2197,3 +2197,49 @@ def test_broadcasted_parameter(max_workers):
results = dev.execute(batch, config)
processed_results = pre_processing_fn(results)
assert qml.math.allclose(processed_results, np.cos(x))
+
+
+@pytest.mark.jax
+def test_renomalization_issue():
+ """Test that no normalization error occurs with the following workflow in float32 mode.
+ Just tests executes without error. Not producing a more minimal example due to difficulty
+ finding an exact case that leads to renomalization issues.
+ """
+ import jax
+ from jax import numpy as jnp
+
+ initial_mode = jax.config.jax_enable_x64
+ jax.config.update("jax_enable_x64", False)
+
+ def gaussian_fn(p, t):
+ return p[0] * jnp.exp(-((t - p[1]) ** 2) / (2 * p[2] ** 2))
+
+ global_drive = qml.pulse.rydberg_drive(
+ amplitude=gaussian_fn, phase=0, detuning=0, wires=[0, 1, 2]
+ )
+
+ a = 5
+
+ coordinates = [(0, 0), (a, 0), (a / 2, np.sqrt(a**2 - (a / 2) ** 2))]
+
+ settings = {"interaction_coeff": 862619.7915580727}
+
+ H_interaction = qml.pulse.rydberg_interaction(coordinates, **settings)
+
+ max_amplitude = 2.0
+ displacement = 1.0
+ sigma = 0.3
+
+ amplitude_params = [max_amplitude, displacement, sigma]
+
+ params = [amplitude_params]
+ ts = [0.0, 1.75]
+
+ def circuit(params):
+ qml.evolve(H_interaction + global_drive)(params, ts)
+ return qml.counts()
+
+ circuit_qml = qml.QNode(circuit, qml.device("default.qubit", shots=1000), interface="jax")
+
+ circuit_qml(params)
+ jax.config.update("jax_enable_x64", initial_mode)
diff --git a/tests/devices/qubit/test_sampling.py b/tests/devices/qubit/test_sampling.py
index 26a86a12592..4174ed63aae 100644
--- a/tests/devices/qubit/test_sampling.py
+++ b/tests/devices/qubit/test_sampling.py
@@ -94,8 +94,6 @@ def test_prng_key_as_seed_uses_sample_state_jax(self, mocker):
# prng_key specified, should call _sample_state_jax
_ = sample_state(state, 10, prng_key=jax.random.PRNGKey(15))
- # prng_key defaults to None, should NOT call _sample_state_jax
- _ = sample_state(state, 10, rng=15)
spy.assert_called_once()
@@ -723,7 +721,7 @@ def test_nan_shadow_expval(self, H, interface, shots):
two_qubit_state_to_be_normalized = np.array([[0, 1.0000000005j], [-1, 0]]) / np.sqrt(2)
-two_qubit_state_not_normalized = np.array([[0, 1.0000005j], [-1.00000001, 0]]) / np.sqrt(2)
+two_qubit_state_not_normalized = np.array([[0, 1.00005j], [-1.00000001, 0]]) / np.sqrt(2)
batched_state_to_be_normalized = np.stack(
[
@@ -752,8 +750,9 @@ def test_sample_state_renorm(self, interface):
state = qml.math.array(two_qubit_state_to_be_normalized, like=interface)
_ = sample_state(state, 10)
+ # jax.random.choice accepts unnormalized probabilities
@pytest.mark.all_interfaces
- @pytest.mark.parametrize("interface", ["numpy", "jax", "torch", "tensorflow"])
+ @pytest.mark.parametrize("interface", ["numpy", "torch", "tensorflow"])
def test_sample_state_renorm_error(self, interface):
"""Test that renormalization does not occur if the error is too large."""
@@ -762,15 +761,16 @@ def test_sample_state_renorm_error(self, interface):
_ = sample_state(state, 10)
@pytest.mark.all_interfaces
- @pytest.mark.parametrize("interface", ["numpy", "jax", "torch", "tensorflow"])
+ @pytest.mark.parametrize("interface", ["numpy", "torch", "jax", "tensorflow"])
def test_sample_batched_state_renorm(self, interface):
"""Test renormalization for a batched state."""
state = qml.math.array(batched_state_to_be_normalized, like=interface)
_ = sample_state(state, 10, is_state_batched=True)
+ # jax.random.choices accepts unnormalized probabilities
@pytest.mark.all_interfaces
- @pytest.mark.parametrize("interface", ["numpy", "jax", "torch", "tensorflow"])
+ @pytest.mark.parametrize("interface", ["numpy", "torch", "tensorflow"])
def test_sample_batched_state_renorm_error(self, interface):
"""Test that renormalization does not occur if the error is too large."""
diff --git a/tests/ops/functions/test_assert_valid.py b/tests/ops/functions/test_assert_valid.py
index 9fff7758338..e426b91a405 100644
--- a/tests/ops/functions/test_assert_valid.py
+++ b/tests/ops/functions/test_assert_valid.py
@@ -188,7 +188,7 @@ def __init__(self, f, wires):
def test_bad_pickling():
"""Test an error is raised in an operator cant be pickled."""
- with pytest.raises(AttributeError, match="Can't pickle local object"):
+ with pytest.raises(AttributeError):
assert_valid(BadPickling0(lambda x: x, wires=0))
diff --git a/tests/ops/op_math/test_prod.py b/tests/ops/op_math/test_prod.py
index fabdfe61bdc..7963ce3fb6c 100644
--- a/tests/ops/op_math/test_prod.py
+++ b/tests/ops/op_math/test_prod.py
@@ -541,6 +541,7 @@ def test_prod_fails_with_non_callable_arg(self):
prod(1)
+# pylint: disable=too-many-public-methods
class TestMatrix:
"""Test matrix-related methods."""
@@ -845,6 +846,15 @@ def test_sparse_matrix(self, op1, mat1, op2, mat2):
assert np.allclose(true_mat, prod_mat)
+ def test_sparse_matrix_global_phase(self):
+ """Test that a prod with a global phase still defines a sparse matrix."""
+
+ op = qml.GlobalPhase(0.5) @ qml.X(0) @ qml.X(0)
+
+ sparse_mat = op.sparse_matrix(wire_order=(0, 1))
+ mat = sparse_mat.todense()
+ assert qml.math.allclose(mat, np.exp(-0.5j) * np.eye(4))
+
@pytest.mark.parametrize("op1, mat1", non_param_ops[:5])
@pytest.mark.parametrize("op2, mat2", non_param_ops[:5])
def test_sparse_matrix_same_wires(self, op1, mat1, op2, mat2):
diff --git a/tests/templates/test_subroutines/test_adder.py b/tests/templates/test_subroutines/test_adder.py
index 0f5a0ef4f59..d4fd3f7a205 100644
--- a/tests/templates/test_subroutines/test_adder.py
+++ b/tests/templates/test_subroutines/test_adder.py
@@ -118,6 +118,7 @@ def circuit(x):
if mod is None:
mod = 2 ** len(x_wires)
+ # pylint: disable=bad-reversed-sequence
result = sum(bit * (2**i) for i, bit in enumerate(reversed(circuit(x))))
assert np.allclose(result, (x + k) % mod)
@@ -131,13 +132,6 @@ def circuit(x):
[3, 4],
("Adder must have enough x_wires to represent mod."),
),
- (
- 1,
- [0, 1, 2],
- 9,
- None,
- (r"If mod is not"),
- ),
(
3,
[0, 1, 2, 3, 4],
@@ -155,6 +149,17 @@ def test_operation_and_test_wires_error(
with pytest.raises(ValueError, match=msg_match):
qml.Adder(k, x_wires, mod, work_wires)
+ @pytest.mark.parametrize("work_wires", [None, [3], [3, 4, 5]])
+ def test_validation_of_num_work_wires(self, work_wires):
+ """Test that when mod is not 2**len(x_wires), validation confirms two
+ work wires are present, while any work wires are accepted for mod=2**len(x_wires)"""
+
+ # if mod=2**len(x_wires), anything goes
+ qml.Adder(1, [0, 1, 2], mod=8, work_wires=work_wires)
+
+ with pytest.raises(ValueError, match="two work wires should be provided"):
+ qml.Adder(1, [0, 1, 2], mod=9, work_wires=work_wires)
+
@pytest.mark.parametrize(
("k", "x_wires", "mod", "work_wires", "msg_match"),
[
@@ -220,5 +225,6 @@ def circuit():
qml.Adder(k, x_wires, mod, work_wires)
return qml.sample(wires=x_wires)
+ # pylint: disable=bad-reversed-sequence
result = sum(bit * (2**i) for i, bit in enumerate(reversed(circuit())))
assert jax.numpy.allclose(result, (x + k) % mod)
diff --git a/tests/templates/test_subroutines/test_mod_exp.py b/tests/templates/test_subroutines/test_mod_exp.py
index 221250d5050..963fb155c60 100644
--- a/tests/templates/test_subroutines/test_mod_exp.py
+++ b/tests/templates/test_subroutines/test_mod_exp.py
@@ -71,6 +71,7 @@ def circuit(x, k):
if mod is None:
mod = 2 ** len(output_wires)
+ # pylint: disable=bad-reversed-sequence
assert np.allclose(
sum(bit * (2**i) for i, bit in enumerate(reversed(circuit(x, k)))),
(k * (base**x)) % mod,
@@ -79,6 +80,14 @@ def circuit(x, k):
@pytest.mark.parametrize(
("x_wires", "output_wires", "base", "mod", "work_wires", "msg_match"),
[
+ (
+ [0, 1, 2],
+ [3, 4, 5],
+ 8,
+ 5,
+ None,
+ "Work wires must be specified for ModExp",
+ ),
(
[0, 1, 2],
[3, 4, 5],
@@ -136,6 +145,18 @@ def test_wires_error(
with pytest.raises(ValueError, match=msg_match):
qml.ModExp(x_wires, output_wires, base, mod, work_wires)
+ def test_check_base_and_mod_are_coprime(self):
+ """Test that an error is raised when base and mod are not coprime"""
+
+ with pytest.raises(ValueError, match="base has no inverse modulo mod"):
+ qml.ModExp(
+ x_wires=[0, 1, 2],
+ output_wires=[3, 4, 5],
+ base=8,
+ mod=6,
+ work_wires=[6, 7, 8, 9, 10],
+ )
+
def test_decomposition(self):
"""Test that compute_decomposition and decomposition work as expected."""
x_wires, output_wires, base, mod, work_wires = (
@@ -183,6 +204,7 @@ def circuit():
qml.ModExp(x_wires, output_wires, base, mod, work_wires)
return qml.sample(wires=output_wires)
+ # pylint: disable=bad-reversed-sequence
assert jax.numpy.allclose(
sum(bit * (2**i) for i, bit in enumerate(reversed(circuit()))), (base**x) % mod
)
diff --git a/tests/templates/test_subroutines/test_multiplier.py b/tests/templates/test_subroutines/test_multiplier.py
index 4cb9cadee5f..175c3076af1 100644
--- a/tests/templates/test_subroutines/test_multiplier.py
+++ b/tests/templates/test_subroutines/test_multiplier.py
@@ -101,6 +101,7 @@ def circuit(x):
if mod is None:
mod = 2 ** len(x_wires)
+ # pylint: disable=bad-reversed-sequence
assert np.allclose(
sum(bit * (2**i) for i, bit in enumerate(reversed(circuit(x)))), (x * k) % mod
)
@@ -108,6 +109,13 @@ def circuit(x):
@pytest.mark.parametrize(
("k", "x_wires", "mod", "work_wires", "msg_match"),
[
+ (
+ 3,
+ [0, 1, 2, 3, 4],
+ 11,
+ None,
+ "Work wires must be specified for Multiplier",
+ ),
(
6,
[0, 1],
@@ -133,7 +141,14 @@ def circuit(x):
3,
[0, 1, 2, 3, 4],
11,
- [5, 6, 7, 8, 9, 10],
+ [5, 6, 7, 8, 9, 10], # not enough
+ "Multiplier needs as many work_wires as x_wires plus two.",
+ ),
+ (
+ 3,
+ [0, 1, 2, 3, 4],
+ 11,
+ [5, 6, 7, 8, 9, 10, 11, 12], # too many
"Multiplier needs as many work_wires as x_wires plus two.",
),
(
@@ -197,6 +212,7 @@ def circuit():
qml.Multiplier(k, x_wires, mod, work_wires)
return qml.sample(wires=x_wires)
+ # pylint: disable=bad-reversed-sequence
assert jax.numpy.allclose(
sum(bit * (2**i) for i, bit in enumerate(reversed(circuit()))), (x * k) % mod
)
diff --git a/tests/templates/test_subroutines/test_out_adder.py b/tests/templates/test_subroutines/test_out_adder.py
index f54edc3857b..7a53701f4a0 100644
--- a/tests/templates/test_subroutines/test_out_adder.py
+++ b/tests/templates/test_subroutines/test_out_adder.py
@@ -80,6 +80,7 @@ def circuit(x, y, z):
if mod is None:
mod = 2 ** len(output_wires)
+ # pylint: disable=bad-reversed-sequence
assert np.allclose(
sum(bit * (2**i) for i, bit in enumerate(reversed(circuit(x, y, z)))),
(x + y + z) % mod,
@@ -145,6 +146,29 @@ def test_wires_error(
with pytest.raises(ValueError, match=msg_match):
qml.OutAdder(x_wires, y_wires, output_wires, mod, work_wires)
+ @pytest.mark.parametrize("work_wires", [None, [9], [9, 10, 11]])
+ def test_validation_of_num_work_wires(self, work_wires):
+ """Test that when mod is not 2**len(output_wires), validation confirms two
+ work wires are present, while any work wires are accepted for mod=2**len(output_wires)"""
+
+ # if mod=2**len(output_wires), anything goes
+ qml.OutAdder(
+ x_wires=[0, 1, 2],
+ y_wires=[3, 4, 5],
+ output_wires=[6, 7, 8],
+ mod=8,
+ work_wires=work_wires,
+ )
+
+ with pytest.raises(ValueError, match="two work wires should be provided"):
+ qml.OutAdder(
+ x_wires=[0, 1, 2],
+ y_wires=[3, 4, 5],
+ output_wires=[6, 7, 8],
+ mod=7,
+ work_wires=work_wires,
+ )
+
def test_decomposition(self):
"""Test that compute_decomposition and decomposition work as expected."""
x_wires, y_wires, output_wires, mod, work_wires = (
@@ -205,6 +229,7 @@ def circuit():
qml.OutAdder(x_wires, y_wires, output_wires, mod, work_wires)
return qml.sample(wires=output_wires)
+ # pylint: disable=bad-reversed-sequence
assert jax.numpy.allclose(
sum(bit * (2**i) for i, bit in enumerate(reversed(circuit()))), (x + y) % mod
)
diff --git a/tests/templates/test_subroutines/test_out_multiplier.py b/tests/templates/test_subroutines/test_out_multiplier.py
index 9541474b7f3..938b03bb541 100644
--- a/tests/templates/test_subroutines/test_out_multiplier.py
+++ b/tests/templates/test_subroutines/test_out_multiplier.py
@@ -111,6 +111,7 @@ def circuit(x, y):
if mod is None:
mod = 2 ** len(output_wires)
+ # pylint: disable=bad-reversed-sequence
assert np.allclose(
sum(bit * (2**i) for i, bit in enumerate(reversed(circuit(x, y)))), (x * y) % mod
)
@@ -183,6 +184,29 @@ def test_wires_error(
with pytest.raises(ValueError, match=msg_match):
OutMultiplier(x_wires, y_wires, output_wires, mod, work_wires)
+ @pytest.mark.parametrize("work_wires", [None, [9], [9, 10, 11]])
+ def test_validation_of_num_work_wires(self, work_wires):
+ """Test that when mod is not 2**len(output_wires), validation confirms two
+ work wires are present, while any work wires are accepted for mod=2**len(output_wires)"""
+
+ # if mod=2**len(output_wires), anything goes
+ OutMultiplier(
+ x_wires=[0, 1, 2],
+ y_wires=[3, 4, 5],
+ output_wires=[6, 7, 8],
+ mod=8,
+ work_wires=work_wires,
+ )
+
+ with pytest.raises(ValueError, match="two work wires should be provided"):
+ OutMultiplier(
+ x_wires=[0, 1, 2],
+ y_wires=[3, 4, 5],
+ output_wires=[6, 7, 8],
+ mod=7,
+ work_wires=work_wires,
+ )
+
def test_decomposition(self):
"""Test that compute_decomposition and decomposition work as expected."""
x_wires, y_wires, output_wires, mod, work_wires = (
@@ -242,6 +266,7 @@ def circuit():
OutMultiplier(x_wires, y_wires, output_wires, mod, work_wires)
return qml.sample(wires=output_wires)
+ # pylint: disable=bad-reversed-sequence
assert jax.numpy.allclose(
sum(bit * (2**i) for i, bit in enumerate(reversed(circuit()))), (x * y) % mod
)
diff --git a/tests/templates/test_subroutines/test_phase_adder.py b/tests/templates/test_subroutines/test_phase_adder.py
index 509228fc7df..fe4703328ec 100644
--- a/tests/templates/test_subroutines/test_phase_adder.py
+++ b/tests/templates/test_subroutines/test_phase_adder.py
@@ -131,6 +131,7 @@ def circuit(x):
if mod is None:
mod = 2 ** len(x_wires)
+ # pylint: disable=bad-reversed-sequence
assert np.allclose(
sum(bit * (2**i) for i, bit in enumerate(reversed(circuit(x)))), (x + k) % mod
)
@@ -168,6 +169,26 @@ def test_operation_and_wires_error(
with pytest.raises(ValueError, match=msg_match):
qml.PhaseAdder(k, x_wires, mod, work_wire)
+ @pytest.mark.parametrize("work_wire", [None, [], [3, 4]])
+ def test_validation_of_num_work_wires(self, work_wire):
+ """Test that when mod is not 2**len(x_wires), validation confirms two
+ work wires are present, while any work wires are accepted for mod=2**len(x_wires)"""
+
+ # if mod=2**len(x_wires), anything goes
+ qml.PhaseAdder(3, [0, 1, 2], mod=8, work_wire=work_wire)
+
+ with pytest.raises(ValueError, match="one work wire should be provided"):
+ qml.PhaseAdder(3, [0, 1, 2], mod=7, work_wire=work_wire)
+
+ def test_valid_inputs_for_work_wires(self):
+ """Test that both an integer and a list with a length of 1 are valid
+ inputs for work_wires, and have the same result"""
+
+ op1 = qml.PhaseAdder(3, [0, 1, 2], mod=8, work_wire=[3])
+ op2 = qml.PhaseAdder(3, [0, 1, 2], mod=8, work_wire=3)
+
+ assert op1.hyperparameters["work_wire"] == op2.hyperparameters["work_wire"]
+
@pytest.mark.parametrize(
("k", "x_wires", "mod", "work_wire", "msg_match"),
[
@@ -248,6 +269,7 @@ def circuit():
qml.adjoint(qml.QFT)(wires=x_wires)
return qml.sample(wires=x_wires)
+ # pylint: disable=bad-reversed-sequence
assert jax.numpy.allclose(
sum(bit * (2**i) for i, bit in enumerate(reversed(circuit()))), (x + k) % mod
)