Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds elementary gate decomposition for single-qubit QubitUnitary #1427

Merged
merged 45 commits into from
Jul 7, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
acb3357
Add decomposition for QubitUnitary.
Jun 22, 2021
62863e1
Add tests. Rewrite decomposition as Rot instead of RZ,RY,RZ.
Jun 22, 2021
5dbe2c0
Update decomposition to use qml.math for differentiability.
Jun 22, 2021
2772e52
Apply suggestions from code review
glassnotes Jun 23, 2021
bb6f7f0
Merge branch 'master' into su2_su4_decomposition
glassnotes Jun 23, 2021
c90439a
Re-implement decomposition as a transform.
Jun 24, 2021
f3c5bd0
Rework code to work with different interfaces.
Jun 24, 2021
3fef91d
Change check for diagonal to use matrix element instead of computed a…
Jun 24, 2021
4d66997
Get things working for 64 bit data in all interfaces.
Jun 24, 2021
5f723fb
Remove print statements.
Jun 24, 2021
b8a524b
Add test of transform in all interfaces.
Jun 24, 2021
60a0b1e
Differentiability tests (currently only working for autograd).
Jun 24, 2021
f8d41ce
Fix order of variables and use interface math for each case.
Jun 25, 2021
dccdba7
Fix return of single measurement in qfunc transform.
Jun 25, 2021
f457347
Add unit test.
Jun 25, 2021
284e65f
Update CHANGELOG.
Jun 25, 2021
85eb1ab
Merge branch 'fix_transform_meas_return' into su2_su4_decomposition
Jun 25, 2021
c6ad65b
Coax all the interfaces to compute gradients.
Jun 25, 2021
9cc9ffe
Merge branch 'master' into su2_su4_decomposition
Jun 25, 2021
6986829
Merge branch 'master' into su2_su4_decomposition
josh146 Jun 29, 2021
5fddce4
Add comment for circular import.
Jun 29, 2021
e4591c9
Apply suggestions from code review
glassnotes Jun 29, 2021
0fe201e
Fix shape check in convert_to_su2.
Jun 29, 2021
b09e6d8
Change qml.math to math.
Jun 29, 2021
91ae536
Restructure; move decompositions into new directory.
Jun 29, 2021
ef09356
Remove redundant checks.
Jun 29, 2021
b9480be
Apply suggestions from code review
glassnotes Jun 29, 2021
b621546
Rename transform.
Jun 29, 2021
a40f05b
Update tests to new name.
Jun 29, 2021
3b6c694
Merge branch 'master' into su2_su4_decomposition
glassnotes Jun 29, 2021
97ee4db
Raise NotImplementedError and add new test. Fixes qmc test.
Jun 29, 2021
7232294
Update tests to use 2-qubit unitary.
Jun 29, 2021
262fbaf
Change unsupported QASM gate to DoubleExcitationPlus.
Jun 29, 2021
1489d81
Fix gradient test names and add min version for torch.
Jun 29, 2021
4e2925e
Merge branch 'master' into su2_su4_decomposition
glassnotes Jul 2, 2021
0cd62fa
Merge branch 'master' into su2_su4_decomposition
Jul 5, 2021
379ea3f
Add backprop diff method to tests.
Jul 5, 2021
0dd4ce0
Switch transform to using qml.apply.
Jul 5, 2021
8c41a0d
fix tests
josh146 Jul 6, 2021
513a4d6
Merge branch 'su2_su4_decomposition' of https://github.com/PennyLaneA…
Jul 6, 2021
40fdf67
Fix to work with qml.apply; don't requeue ops.
Jul 6, 2021
8a20067
Add zyz_decomp to docs.
Jul 6, 2021
26575ea
Clarify decomposition docstring.
Jul 6, 2021
03bd4dd
Update CHANGELOG.
Jul 7, 2021
070bc97
Update CHANGELOG.
Jul 7, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions pennylane/ops/qubit.py
Original file line number Diff line number Diff line change
Expand Up @@ -2157,6 +2157,43 @@ def _matrix(cls, *params):

return U

@staticmethod
def decomposition(U, wires):
# Decompose arbitrary single-qubit unitaries as the form RZ RY RZ
if U.shape[0] == 2:
glassnotes marked this conversation as resolved.
Show resolved Hide resolved
wires = Wires(wires)

# Remove the global phase if present; cannot just divide by square root of det
# because sometimes it is negative.
det = U[0, 0] * U[1, 1] - U[0, 1] * U[1, 0]
if not qml.math.isclose(det, 1):
U = U * (qml.math.exp(-1j * qml.math.angle(det) / 2))

# Compute the angle of the RY
theta = 2 * qml.math.arccos(qml.math.sqrt(U[0, 0] * U[1, 1]))

# If it's close to 0, the matrix is diagonal and we have just an RZ rotation
if qml.math.isclose(theta, 0, atol=1e-7):
omega = 2 * qml.math.angle(U[1, 1])
return [RZ(omega, wires=wires[0])]

# Otherwise recover the decomposition as a Rot, which can be further decomposed
# if desired. If the top left element is 0, can only use the off-diagonal elements
if qml.math.isclose(U[0, 0], 0):
phi = 1j * qml.math.log(U[0, 1] / U[1, 0])
omega = -phi - 2 * qml.math.angle(U[1, 0])
Copy link
Member

Choose a reason for hiding this comment

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

You may need to be careful here:

  • phi is complex
  • 2 * angle(...) will be real (I think float64)

and TensorFlow (maybe also PyTorch?) will not automatically cast the latter to a complex number before doing the subtraction.

I think the following should work:

Suggested change
omega = -phi - 2 * qml.math.angle(U[1, 0])
omega = -phi - qml.math.cast_like(2 * qml.math.angle(U[1, 0]), phi)

Copy link
Member

@josh146 josh146 Jun 23, 2021

Choose a reason for hiding this comment

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

(Note: qml.math.cast(..., "complex128") would also work above, but cast_like is safer, in that it will support both complex64 and complex128 depending on whatever phi is)

Copy link
Contributor Author

@glassnotes glassnotes Jun 23, 2021

Choose a reason for hiding this comment

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

Oh thanks, yes this looks safer! I originally had the conversion to real in the line of phi rather than in the return value, do you think it'd be better to convert phi first so that the subtraction ends up real? Or is it better to just do the casting anyways?

Copy link
Member

Choose a reason for hiding this comment

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

I think you'd need the casting regardless, potentially you could have float64 subtract float32, which would also cause an error

else:
omega = 1j * qml.math.log(qml.math.tan(theta / 2) * U[0, 0] / U[1, 0])
phi = -omega - 2 * qml.math.angle(U[0, 0])
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
phi = -omega - 2 * qml.math.angle(U[0, 0])
phi = -omega - qml.math.cast_like(2 * qml.math.angle(U[0, 0]), omega)


return [
qml.Rot(
qml.math.real(phi), qml.math.real(theta), qml.math.real(omega), wires=wires[0]
)
]
else:
return NotImplementedError
glassnotes marked this conversation as resolved.
Show resolved Hide resolved

def adjoint(self):
return QubitUnitary(qml.math.T(qml.math.conj(self.matrix)), wires=self.wires)

Expand Down
23 changes: 23 additions & 0 deletions tests/ops/test_qubit_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -1445,6 +1445,29 @@ def test_qubit_unitary_not_matrix_exception(self, U):
with pytest.raises(ValueError, match="must be a square matrix"):
qml.QubitUnitary(U, wires=0).matrix

@pytest.mark.parametrize(
"U,expected_gate,expected_params",
[ # First set of gates are diagonal and converted to RZ
(I, qml.RZ, [0]),
(Z, qml.RZ, [np.pi]),
(S, qml.RZ, [np.pi / 2]),
(T, qml.RZ, [np.pi / 4]),
(qml.RZ(0.3, wires=0).matrix, qml.RZ, [0.3]),
(qml.RZ(-0.5, wires=0).matrix, qml.RZ, [-0.5]),
# Next set of gates are non-diagonal and decomposed as Rots
(H, qml.Rot, [np.pi, np.pi / 2, 0]),
(X, qml.Rot, [0.0, np.pi, np.pi]),
(qml.Rot(0.2, 0.5, -0.3, wires=0).matrix, qml.Rot, [0.2, 0.5, -0.3]),
(np.exp(1j * 0.02) * qml.Rot(-1, 2, -3, wires=0).matrix, qml.Rot, [-1, 2, -3]),
],
)
def test_qubit_unitary_decomposition(self, U, expected_gate, expected_params):
decomp = qml.QubitUnitary.decomposition(U, wires=0)

assert len(decomp) == 1
assert isinstance(decomp[0], expected_gate)
assert np.allclose(decomp[0].parameters, expected_params)
glassnotes marked this conversation as resolved.
Show resolved Hide resolved

def test_iswap_eigenval(self):
"""Tests that the ISWAP eigenvalue matches the numpy eigenvalues of the ISWAP matrix"""
op = qml.ISWAP(wires=[0, 1])
Expand Down