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 )