diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 559f1f20b6e..7c51b80bd11 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -50,6 +50,47 @@
unique representation of the object.
[(#6167)](https://github.com/PennyLaneAI/pennylane/pull/6167)
+* If the conditional does not include a mid-circuit measurement, then `qml.cond`
+ will automatically evaluate conditionals using standard Python control flow.
+ [(#6016)](https://github.com/PennyLaneAI/pennylane/pull/6016)
+
+ This allows `qml.cond` to be used to represent a wider range of conditionals:
+
+ ```python
+ dev = qml.device("default.qubit", wires=1)
+
+ @qml.qnode(dev)
+ def circuit(x):
+ c = qml.cond(x > 2.7, qml.RX, qml.RZ)
+ c(x, wires=0)
+ return qml.probs(wires=0)
+ ```
+
+ ```pycon
+ >>> print(qml.draw(circuit)(3.8))
+ 0: ──RX(3.80)─┤ Probs
+ >>> print(qml.draw(circuit)(0.54))
+ 0: ──RZ(0.54)─┤ Probs
+ ```
+
+* The `qubit_observable` function is modified to return an ascending wire order for molecular
+ Hamiltonians.
+ [(#5950)](https://github.com/PennyLaneAI/pennylane/pull/5950)
+
+* `qml.BasisRotation` is now jit compatible.
+ [(#6019)](https://github.com/PennyLaneAI/pennylane/pull/6019)
+
+* The `CNOT` operator no longer decomposes to itself. Instead, it raises a `qml.DecompositionUndefinedError`.
+ [(#6039)](https://github.com/PennyLaneAI/pennylane/pull/6039)
+
+
Community contributions 🥳
+
+* `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)
+
* The `to_mat` methods for `FermiWord` and `FermiSentence` now optionally return
a sparse matrix.
[(#6173)](https://github.com/PennyLaneAI/pennylane/pull/6173)
@@ -113,6 +154,9 @@
* The ``qml.Qubitization`` template now orders the ``control`` wires first and the ``hamiltonian`` wires second, which is the expected according to other templates.
[(#6229)](https://github.com/PennyLaneAI/pennylane/pull/6229)
+* `qml.AmplitudeEmbedding` has better support for features using low precision integer data types.
+ [(#5969)](https://github.com/PennyLaneAI/pennylane/pull/5969)
+
* The ``qml.FABLE`` template now returns the correct value when JIT is enabled.
[(#6263)](https://github.com/PennyLaneAI/pennylane/pull/6263)
@@ -135,4 +179,10 @@ Emiliano Godinez,
Christina Lee,
William Maxwell,
Lee J. O'Riordan,
-David Wierichs,
+Vincent Michaud-Rioux,
+Anurav Modak,
+Pablo A. Moreno Casares,
+Mudit Pandey,
+Erik Schultheis,
+Nate Stemen,
+David Wierichs.
\ No newline at end of file
diff --git a/pennylane/qchem/givens_decomposition.py b/pennylane/qchem/givens_decomposition.py
index 18645237c98..0889e8453e1 100644
--- a/pennylane/qchem/givens_decomposition.py
+++ b/pennylane/qchem/givens_decomposition.py
@@ -15,8 +15,6 @@
This module contains the functions needed for performing basis transformations defined by a set of fermionic ladder operators.
"""
-import numpy as np
-
import pennylane as qml
@@ -38,26 +36,49 @@ def _givens_matrix(a, b, left=True, tol=1e-8):
tol (float): determines tolerance limits for :math:`|a|` and :math:`|b|` under which they are considered as zero.
Returns:
- np.ndarray (or tensor): Givens rotation matrix
+ tensor_like: Givens rotation matrix
"""
- abs_a, abs_b = np.abs(a), np.abs(b)
- if abs_a < tol:
- cosine, sine, phase = 1.0, 0.0, 1.0
- elif abs_b < tol:
- cosine, sine, phase = 0.0, 1.0, 1.0
- else:
- hypot = np.hypot(abs_a, abs_b)
- cosine = abs_b / hypot
- sine = abs_a / hypot
- phase = 1.0 * b / abs_b * a.conjugate() / abs_a
+ abs_a, abs_b, interface = qml.math.abs(a), qml.math.abs(b), qml.math.get_interface(a)
+ aprod = qml.math.nan_to_num(abs_b * abs_a)
+ hypot = qml.math.hypot(abs_a, abs_b)
+
+ cosine = qml.math.where(abs_a < tol, 1.0, qml.math.where(abs_b < tol, 0.0, abs_b / hypot))
+ sine = qml.math.where(abs_a < tol, 0.0, qml.math.where(abs_b < tol, 1.0, abs_a / hypot))
+ phase = qml.math.where(
+ abs_a < tol,
+ 1.0,
+ qml.math.where(abs_b < tol, 1.0, (1.0 * b * qml.math.conj(a)) / (aprod + 1e-15)),
+ )
if left:
- return np.array([[phase * cosine, -sine], [phase * sine, cosine]])
+ return qml.math.array([[phase * cosine, -sine], [phase * sine, cosine]], like=interface)
+
+ return qml.math.array([[phase * sine, cosine], [-phase * cosine, sine]], like=interface)
+
+
+def _set_unitary_matrix(unitary_matrix, index, value, like=None):
+ """Set the values in the ``unitary_matrix`` at the specified index.
+
+ Args:
+ unitary_matrix (tensor_like): unitary being modified
+ index (Tuple[int | Ellipsis | List[Int]]): index for the slicing the unitary
+ value (tensor_like): new values for the specified index
+ like (str): interface for the unitary matrix.
+
+ Returns:
+ tensor_like: modified unitary
+ """
+ if like == "jax":
+ return unitary_matrix.at[index[0], index[1]].set(
+ value, indices_are_sorted=True, unique_indices=True
+ )
- return np.array([[phase * sine, cosine], [-phase * cosine, sine]])
+ unitary_matrix[index[0], index[1]] = value
+ return unitary_matrix
+# pylint:disable = too-many-branches
def givens_decomposition(unitary):
r"""Decompose a unitary into a sequence of Givens rotation gates with phase shifts and a diagonal phase matrix.
@@ -110,7 +131,7 @@ def givens_decomposition(unitary):
unitary (tensor): unitary matrix on which decomposition will be performed
Returns:
- (np.ndarray, list[(np.ndarray, tuple)]): diagonal elements of the phase matrix :math:`D` and Givens rotation matrix :math:`T` with their indices.
+ (tensor_like, list[(tensor_like, tuple)]): diagonal elements of the phase matrix :math:`D` and Givens rotation matrix :math:`T` with their indices.
Raises:
ValueError: if the provided matrix is not square.
@@ -147,8 +168,10 @@ def givens_decomposition(U):
# Update U = T(N+j-i-1, N+j-i) @ U
"""
+ interface = qml.math.get_deep_interface(unitary)
+ unitary = qml.math.copy(unitary) if interface == "jax" else qml.math.toarray(unitary).copy()
+ M, N = qml.math.shape(unitary)
- unitary, (M, N) = qml.math.toarray(unitary).copy(), unitary.shape
if M != N:
raise ValueError(f"The unitary matrix should be of shape NxN, got {unitary.shape}")
@@ -158,32 +181,41 @@ def givens_decomposition(U):
for j in range(0, i):
indices = [i - j - 1, i - j]
grot_mat = _givens_matrix(*unitary[N - j - 1, indices].T, left=True)
- unitary[:, indices] = unitary[:, indices] @ grot_mat.T
- right_givens.append((grot_mat.conj(), indices))
+ unitary = _set_unitary_matrix(
+ unitary, (Ellipsis, indices), unitary[:, indices] @ grot_mat.T, like=interface
+ )
+ right_givens.append((qml.math.conj(grot_mat), indices))
else:
for j in range(1, i + 1):
indices = [N + j - i - 2, N + j - i - 1]
grot_mat = _givens_matrix(*unitary[indices, j - 1], left=False)
- unitary[indices] = grot_mat @ unitary[indices]
+ unitary = _set_unitary_matrix(
+ unitary, (indices, Ellipsis), grot_mat @ unitary[indices, :], like=interface
+ )
left_givens.append((grot_mat, indices))
nleft_givens = []
for grot_mat, (i, j) in reversed(left_givens):
- sphase_mat = np.diag(np.diag(unitary)[[i, j]])
- decomp_mat = grot_mat.conj().T @ sphase_mat
+ sphase_mat = qml.math.diag(qml.math.diag(unitary)[qml.math.array([i, j])])
+ decomp_mat = qml.math.conj(grot_mat).T @ sphase_mat
givens_mat = _givens_matrix(*decomp_mat[1, :].T)
nphase_mat = decomp_mat @ givens_mat.T
# check for T_{m,n}^{-1} x D = D x T.
- if not np.allclose(nphase_mat @ givens_mat.conj(), decomp_mat): # pragma: no cover
+ if not qml.math.is_abstract(decomp_mat) and not qml.math.allclose(
+ nphase_mat @ qml.math.conj(givens_mat), decomp_mat
+ ): # pragma: no cover
raise ValueError("Failed to shift phase transposition.")
- unitary[i, i], unitary[j, j] = np.diag(nphase_mat)
- nleft_givens.append((givens_mat.conj(), (i, j)))
+ for diag_idx, diag_val in zip([(i, i), (j, j)], qml.math.diag(nphase_mat)):
+ unitary = _set_unitary_matrix(unitary, diag_idx, diag_val, like=interface)
+ nleft_givens.append((qml.math.conj(givens_mat), (i, j)))
- phases, ordered_rotations = np.diag(unitary), []
+ phases, ordered_rotations = qml.math.diag(unitary), []
for grot_mat, (i, j) in list(reversed(nleft_givens)) + list(reversed(right_givens)):
- if not np.all(np.isreal(grot_mat[0, 1]) and np.isreal(grot_mat[1, 1])): # pragma: no cover
+ if not qml.math.is_abstract(grot_mat) and not qml.math.all(
+ qml.math.isreal(grot_mat[0, 1]) and qml.math.isreal(grot_mat[1, 1])
+ ): # pragma: no cover
raise ValueError(f"Incorrect Givens Rotation encountered, {grot_mat}")
ordered_rotations.append((grot_mat, (i, j)))
diff --git a/pennylane/templates/subroutines/basis_rotation.py b/pennylane/templates/subroutines/basis_rotation.py
index 346e8b8370f..fd36d2a55d1 100644
--- a/pennylane/templates/subroutines/basis_rotation.py
+++ b/pennylane/templates/subroutines/basis_rotation.py
@@ -15,8 +15,6 @@
This module contains the template for performing basis transformation defined by a set of fermionic ladder operators.
"""
-import numpy as np
-
import pennylane as qml
from pennylane.operation import AnyWires, Operation
from pennylane.qchem.givens_decomposition import givens_decomposition
@@ -110,15 +108,19 @@ def _primitive_bind_call(cls, wires, unitary_matrix, check=False, id=None):
return cls._primitive.bind(*wires, unitary_matrix, check=check, id=id)
def __init__(self, wires, unitary_matrix, check=False, id=None):
- M, N = unitary_matrix.shape
+ M, N = qml.math.shape(unitary_matrix)
+
if M != N:
raise ValueError(
- f"The unitary matrix should be of shape NxN, got {unitary_matrix.shape}"
+ f"The unitary matrix should be of shape NxN, got {qml.math.shape(unitary_matrix)}"
)
if check:
- umat = qml.math.toarray(unitary_matrix)
- if not np.allclose(umat @ umat.conj().T, np.eye(M, dtype=complex), atol=1e-6):
+ if not qml.math.is_abstract(unitary_matrix) and not qml.math.allclose(
+ unitary_matrix @ qml.math.conj(unitary_matrix).T,
+ qml.math.eye(M, dtype=complex),
+ atol=1e-4,
+ ):
raise ValueError("The provided transformation matrix should be unitary.")
if len(wires) < 2:
@@ -153,35 +155,39 @@ def compute_decomposition(
list[.Operator]: decomposition of the operator
"""
- M, N = unitary_matrix.shape
+ M, N = qml.math.shape(unitary_matrix)
if M != N:
raise ValueError(
f"The unitary matrix should be of shape NxN, got {unitary_matrix.shape}"
)
if check:
- umat = qml.math.toarray(unitary_matrix)
- if not np.allclose(umat @ umat.conj().T, np.eye(M, dtype=complex), atol=1e-4):
+ if not qml.math.is_abstract(unitary_matrix) and not qml.math.allclose(
+ unitary_matrix @ qml.math.conj(unitary_matrix).T,
+ qml.math.eye(M, dtype=complex),
+ atol=1e-4,
+ ):
raise ValueError("The provided transformation matrix should be unitary.")
if len(wires) < 2:
raise ValueError(f"This template requires at least two wires, got {len(wires)}")
op_list = []
+
phase_list, givens_list = givens_decomposition(unitary_matrix)
for idx, phase in enumerate(phase_list):
- op_list.append(qml.PhaseShift(np.angle(phase), wires=wires[idx]))
+ op_list.append(qml.PhaseShift(qml.math.angle(phase), wires=wires[idx]))
for grot_mat, indices in givens_list:
- theta = np.arccos(np.real(grot_mat[1, 1]))
- phi = np.angle(grot_mat[0, 0])
+ theta = qml.math.arccos(qml.math.real(grot_mat[1, 1]))
+ phi = qml.math.angle(grot_mat[0, 0])
op_list.append(
qml.SingleExcitation(2 * theta, wires=[wires[indices[0]], wires[indices[1]]])
)
- if not np.isclose(phi, 0.0):
+ if qml.math.is_abstract(phi) or not qml.math.isclose(phi, 0.0):
op_list.append(qml.PhaseShift(phi, wires=wires[indices[0]]))
return op_list
diff --git a/pennylane/templates/subroutines/qdrift.py b/pennylane/templates/subroutines/qdrift.py
index a65152586e0..9c8198b4753 100644
--- a/pennylane/templates/subroutines/qdrift.py
+++ b/pennylane/templates/subroutines/qdrift.py
@@ -81,7 +81,7 @@ class QDrift(Operation):
The QDrift subroutine provides a method to approximate the matrix exponential of a Hamiltonian
expressed as a linear combination of terms which in general do not commute. For the Hamiltonian
:math:`H = \Sigma_j h_j H_{j}`, the product formula is constructed by random sampling from the
- terms of the Hamiltonian with the probability :math:`p_j = h_j / \sum_{j} hj` as:
+ terms of the Hamiltonian with the probability :math:`p_j = h_j / \sum_{j} h_{j}` as:
.. math::
diff --git a/tests/templates/test_subroutines/test_basis_rotation.py b/tests/templates/test_subroutines/test_basis_rotation.py
index c5c9c643450..aa310f4cdbe 100644
--- a/tests/templates/test_subroutines/test_basis_rotation.py
+++ b/tests/templates/test_subroutines/test_basis_rotation.py
@@ -334,11 +334,12 @@ def test_id(self):
assert template.id == "a"
-def circuit_template(unitary_matrix):
- qml.BasisState(np.array([1, 1, 0]), wires=[0, 1, 2])
+def circuit_template(unitary_matrix, check=False):
+ qml.BasisState(qml.math.array([1, 1, 0]), wires=[0, 1, 2])
qml.BasisRotation(
wires=range(3),
unitary_matrix=unitary_matrix,
+ check=check,
)
return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
@@ -402,7 +403,7 @@ def test_autograd(self, tol):
assert np.allclose(grads, np.zeros_like(unitary_matrix, dtype=complex), atol=tol, rtol=0)
@pytest.mark.jax
- def test_jax(self, tol):
+ def test_jax_jit(self, tol):
"""Test the jax interface."""
import jax
@@ -415,36 +416,25 @@ def test_jax(self, tol):
[-0.58608928 + 0.0j, 0.03902657 + 0.04633548j, -0.57220635 + 0.57044649j],
]
)
- weights = jnp.array(
- [
- 2.2707802713289267,
- 2.9355948424220206,
- -1.4869222527726533,
- 1.2601662579297865,
- 2.3559705032936717,
- 1.1748572730890159,
- 2.2500537657656356,
- -0.7251404204443089,
- 2.3577346350335198,
- ]
- )
dev = qml.device("default.qubit", wires=3)
- circuit = qml.QNode(circuit_template, dev)
- circuit2 = qml.QNode(circuit_decomposed, dev)
+ circuit = jax.jit(qml.QNode(circuit_template, dev), static_argnames="check")
+ circuit2 = qml.QNode(circuit_template, dev)
res = circuit(unitary_matrix)
- res2 = circuit2(weights)
- assert qml.math.allclose(res, res2, atol=tol, rtol=0)
+ res2 = circuit2(unitary_matrix)
+ res3 = circuit2(qml.math.toarray(unitary_matrix))
+ assert jnp.allclose(res, res2, atol=tol, rtol=0)
+ assert qml.math.allclose(res, res3, atol=tol, rtol=0)
grad_fn = jax.grad(circuit)
grads = grad_fn(unitary_matrix)
grad_fn2 = jax.grad(circuit2)
- grads2 = grad_fn2(weights)
+ grads2 = grad_fn2(unitary_matrix)
- assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0)
+ assert qml.math.allclose(grads[0], grads2[0], atol=tol, rtol=0)
@pytest.mark.tf
def test_tf(self, tol):