From 4d19d463047106673b11cb5678fbec2d660d4708 Mon Sep 17 00:00:00 2001 From: lillian542 Date: Thu, 5 Oct 2023 17:11:45 -0400 Subject: [PATCH 1/8] cast to real if complex component is more or less 0 --- .../decompositions/single_qubit_unitary.py | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/pennylane/transforms/decompositions/single_qubit_unitary.py b/pennylane/transforms/decompositions/single_qubit_unitary.py index b19bbc8daa9..300ea866b7c 100644 --- a/pennylane/transforms/decompositions/single_qubit_unitary.py +++ b/pennylane/transforms/decompositions/single_qubit_unitary.py @@ -15,7 +15,7 @@ operations into elementary gates. """ -import numpy +import numpy as np import pennylane as qml from pennylane import math @@ -40,7 +40,6 @@ def _convert_to_su2(U, return_global_phase=False): # Compute the determinants U = qml.math.cast(U, "complex128") dets = math.linalg.det(U) - exp_angles = math.cast_like(math.angle(dets), 1j) / 2 U_SU2 = math.cast_like(U, dets) * math.exp(-1j * exp_angles)[:, None, None] return (U_SU2, exp_angles) if return_global_phase else U_SU2 @@ -186,12 +185,14 @@ def _zyz_decomposition(U, wire, return_global_phase=False): phis, thetas, omegas, alphas = map(math.squeeze, [phis, thetas, omegas, alphas]) - phis = phis % (4 * numpy.pi) - thetas = thetas % (4 * numpy.pi) - omegas = omegas % (4 * numpy.pi) + phis = phis % (4 * np.pi) + thetas = thetas % (4 * np.pi) + omegas = omegas % (4 * np.pi) operations = [qml.RZ(phis, wire), qml.RY(thetas, wire), qml.RZ(omegas, wire)] if return_global_phase: + if qml.math.all(np.imag(alphas) < 1e15): + alphas = np.real(alphas) operations.append(qml.GlobalPhase(-alphas)) return operations @@ -250,12 +251,14 @@ def _xyx_decomposition(U, wire, return_global_phase=False): phis, thetas, lams, gammas = map(math.squeeze, [phis, thetas, lams, gammas]) - phis = phis % (4 * numpy.pi) - thetas = thetas % (4 * numpy.pi) - lams = lams % (4 * numpy.pi) + phis = phis % (4 * np.pi) + thetas = thetas % (4 * np.pi) + lams = lams % (4 * np.pi) operations = [qml.RX(lams, wire), qml.RY(thetas, wire), qml.RX(phis, wire)] if return_global_phase: + if qml.math.all(np.imag(gammas) < 1e15): + gammas = np.real(gammas) operations.append(qml.GlobalPhase(-gammas)) return operations @@ -316,13 +319,15 @@ def _zxz_decomposition(U, wire, return_global_phase=False): phis, thetas, psis, alphas = map(math.squeeze, [phis, thetas, psis, alphas]) - phis = phis % (4 * numpy.pi) - thetas = thetas % (4 * numpy.pi) - psis = psis % (4 * numpy.pi) + phis = phis % (4 * np.pi) + thetas = thetas % (4 * np.pi) + psis = psis % (4 * np.pi) # Return gates in the order they will be applied on the qubit operations = [qml.RZ(psis, wire), qml.RX(thetas, wire), qml.RZ(phis, wire)] if return_global_phase: + if qml.math.all(np.imag(alphas) < 1e15): + alphas = np.real(alphas) operations.append(qml.GlobalPhase(-alphas)) return operations From 09ecd566d2083e66d1fc5a1aa8770ea1de0b00b6 Mon Sep 17 00:00:00 2001 From: lillian542 Date: Thu, 5 Oct 2023 17:24:19 -0400 Subject: [PATCH 2/8] update tests --- tests/transforms/test_decompositions.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/transforms/test_decompositions.py b/tests/transforms/test_decompositions.py index ccaaafd315f..685b10e6f09 100644 --- a/tests/transforms/test_decompositions.py +++ b/tests/transforms/test_decompositions.py @@ -38,7 +38,7 @@ typeof_gates_zyz = (qml.RZ, qml.RY, qml.RZ, qml.GlobalPhase) single_qubit_decomps_zyz = [ (I, typeof_gates_zyz, [0.0, 0.0, 0.0, 0]), - (Z, typeof_gates_zyz, [np.pi / 2, 0.0, np.pi / 2, (-1.5707963267948966 + 0j)]), + (Z, typeof_gates_zyz, [np.pi / 2, 0.0, np.pi / 2, -1.5707963267948966]), ( S, typeof_gates_zyz, @@ -70,8 +70,8 @@ typeof_gates_zyz, [12.382273469673908, np.pi, 0.18409714468526372, 0], ), - (H, typeof_gates_zyz, [np.pi, np.pi / 2, 0.0, (-1.5707963267948966 + 0j)]), - (X, typeof_gates_zyz, [np.pi / 2, np.pi, 10.995574287564276, (-1.5707963267948966 + 0j)]), + (H, typeof_gates_zyz, [np.pi, np.pi / 2, 0.0, -1.5707963267948966]), + (X, typeof_gates_zyz, [np.pi / 2, np.pi, 10.995574287564276, -1.5707963267948966]), ( np.exp(1j * 0.02) * qml.Rot(-1.0, 2.0, -3.0, wires=0).matrix(), typeof_gates_zyz, @@ -79,7 +79,7 @@ 11.566370614359172, 2.0, 9.566370614359172, - (-0.020000000000000042 - 2.122325752640375e-17j), + -0.020000000000000042, ], ), # Add two instances of broadcasted unitaries, one coming from RZ and another from Rot @@ -196,14 +196,14 @@ def test_zyz_decomposition_jax(self, U, expected_gates, expected_params): 10.845351366405708, 1.3974974118006183, 0.45246583660683803, - (1.1759220332464762 - 4.163336342344337e-17j), + 1.1759220332464762, ), ), # Try a few specific special unitaries (I, typeof_gates_xyx, [0, 0, 0, 0]), # This triggers the if conditional trivially - (X, typeof_gates_xyx, [4.71238898038469, 0.0, 10.995574287564276, (-1.5707963267948966 + 0j)]), - (Y, typeof_gates_xyx, [1 / 2 * np.pi, np.pi, 1 / 2 * np.pi, (-1.5707963267948966 + 0j)]), - (Z, typeof_gates_xyx, [10.995574287564276, np.pi, 1 / 2 * np.pi, (-1.5707963267948966 + 0j)]), + (X, typeof_gates_xyx, [4.71238898038469, 0.0, 10.995574287564276, -1.5707963267948966]), + (Y, typeof_gates_xyx, [1 / 2 * np.pi, np.pi, 1 / 2 * np.pi, -1.5707963267948966]), + (Z, typeof_gates_xyx, [10.995574287564276, np.pi, 1 / 2 * np.pi, -1.5707963267948966]), # Add two instances of broadcasted unitaries, one coming from RZ and another from Rot ( qml.QubitUnitary(qml.RZ.compute_matrix(np.array([np.pi, np.pi / 2])), wires=0).matrix(), @@ -311,7 +311,7 @@ def test_xyx_decomposition_jax(self, U, expected_gates, expected_params): typeof_gates_zxz = (qml.RZ, qml.RX, qml.RZ, qml.GlobalPhase) single_qubit_decomps_zxz = [ (I, typeof_gates_zxz, [0.0, 0.0, 0.0, 0]), - (Z, typeof_gates_zxz, [np.pi / 2, 0.0, np.pi / 2, (-1.5707963267948966 + 0j)]), + (Z, typeof_gates_zxz, [np.pi / 2, 0.0, np.pi / 2, -1.5707963267948966]), ( S, typeof_gates_zxz, @@ -484,7 +484,7 @@ def test_zxz_decomposition_jax(self, U, expected_gates, expected_params): 10.845351366405708, 1.3974974118006183, 0.45246583660683803, - 1.1759220332464762 - 4.163336342344337e-17j, + 1.1759220332464762, ), ), ( From 3096b42d23ca0c8351549c88f12c4b300239c229 Mon Sep 17 00:00:00 2001 From: lillian542 <38584660+lillian542@users.noreply.github.com> Date: Fri, 6 Oct 2023 10:25:16 -0400 Subject: [PATCH 3/8] Fix dumb mistake --- pennylane/transforms/decompositions/single_qubit_unitary.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pennylane/transforms/decompositions/single_qubit_unitary.py b/pennylane/transforms/decompositions/single_qubit_unitary.py index 300ea866b7c..06b138164a4 100644 --- a/pennylane/transforms/decompositions/single_qubit_unitary.py +++ b/pennylane/transforms/decompositions/single_qubit_unitary.py @@ -191,7 +191,7 @@ def _zyz_decomposition(U, wire, return_global_phase=False): operations = [qml.RZ(phis, wire), qml.RY(thetas, wire), qml.RZ(omegas, wire)] if return_global_phase: - if qml.math.all(np.imag(alphas) < 1e15): + if qml.math.all(np.imag(alphas) < 1e-15): alphas = np.real(alphas) operations.append(qml.GlobalPhase(-alphas)) @@ -257,7 +257,7 @@ def _xyx_decomposition(U, wire, return_global_phase=False): operations = [qml.RX(lams, wire), qml.RY(thetas, wire), qml.RX(phis, wire)] if return_global_phase: - if qml.math.all(np.imag(gammas) < 1e15): + if qml.math.all(np.imag(gammas) < 1e-15): gammas = np.real(gammas) operations.append(qml.GlobalPhase(-gammas)) @@ -326,7 +326,7 @@ def _zxz_decomposition(U, wire, return_global_phase=False): # Return gates in the order they will be applied on the qubit operations = [qml.RZ(psis, wire), qml.RX(thetas, wire), qml.RZ(phis, wire)] if return_global_phase: - if qml.math.all(np.imag(alphas) < 1e15): + if qml.math.all(np.imag(alphas) < 1e-15): alphas = np.real(alphas) operations.append(qml.GlobalPhase(-alphas)) From dc14969ba93f72955f289a9f250f7226df6bdcd7 Mon Sep 17 00:00:00 2001 From: lillian542 Date: Fri, 6 Oct 2023 14:12:13 -0400 Subject: [PATCH 4/8] update changelog --- doc/releases/changelog-dev.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index b526ea59d9d..14f084e3197 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -181,6 +181,11 @@ interface-specific scalar data, eg `[(tf.Variable(1.1), tf.Variable(2.2))]`. [(#4603)](https://github.com/PennyLaneAI/pennylane/pull/4603) +* When decomposing a unitary matrix with `one_qubit_decomposition`, and opting to include the `GlobalPhase` + in the decomposition, the phase no longer has `dtype=complex` for phases with an imaginary component of 0. + To account for rounding errors in calculating the phase, any complex component less than `1e-15` is discarded. + [(#4653)](https://github.com/PennyLaneAI/pennylane/pull/4653) +

Breaking changes 💔

* The device test suite now converts device kwargs to integers or floats if they can be converted to integers or floats. From e19f5ecf77ff3c63344411e19292e30a33b659ce Mon Sep 17 00:00:00 2001 From: lillian542 Date: Fri, 13 Oct 2023 10:30:36 -0400 Subject: [PATCH 5/8] cast to real in _convert_to_su2 instead --- .../decompositions/single_qubit_unitary.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pennylane/transforms/decompositions/single_qubit_unitary.py b/pennylane/transforms/decompositions/single_qubit_unitary.py index 06b138164a4..32c9c22b1d3 100644 --- a/pennylane/transforms/decompositions/single_qubit_unitary.py +++ b/pennylane/transforms/decompositions/single_qubit_unitary.py @@ -40,9 +40,13 @@ def _convert_to_su2(U, return_global_phase=False): # Compute the determinants U = qml.math.cast(U, "complex128") dets = math.linalg.det(U) + + # math.angle(dets) is always real, but we cast to complex for torch (so we can multiply by -1j) exp_angles = math.cast_like(math.angle(dets), 1j) / 2 U_SU2 = math.cast_like(U, dets) * math.exp(-1j * exp_angles)[:, None, None] - return (U_SU2, exp_angles) if return_global_phase else U_SU2 + + # we cast back to real again if returning exp_angles + return (U_SU2, qml.math.real(exp_angles)) if return_global_phase else U_SU2 def _rot_decomposition(U, wire): @@ -191,8 +195,6 @@ def _zyz_decomposition(U, wire, return_global_phase=False): operations = [qml.RZ(phis, wire), qml.RY(thetas, wire), qml.RZ(omegas, wire)] if return_global_phase: - if qml.math.all(np.imag(alphas) < 1e-15): - alphas = np.real(alphas) operations.append(qml.GlobalPhase(-alphas)) return operations @@ -257,8 +259,6 @@ def _xyx_decomposition(U, wire, return_global_phase=False): operations = [qml.RX(lams, wire), qml.RY(thetas, wire), qml.RX(phis, wire)] if return_global_phase: - if qml.math.all(np.imag(gammas) < 1e-15): - gammas = np.real(gammas) operations.append(qml.GlobalPhase(-gammas)) return operations @@ -326,8 +326,6 @@ def _zxz_decomposition(U, wire, return_global_phase=False): # Return gates in the order they will be applied on the qubit operations = [qml.RZ(psis, wire), qml.RX(thetas, wire), qml.RZ(phis, wire)] if return_global_phase: - if qml.math.all(np.imag(alphas) < 1e-15): - alphas = np.real(alphas) operations.append(qml.GlobalPhase(-alphas)) return operations From ba784a933d7f95da69a95479819ce2033c169950 Mon Sep 17 00:00:00 2001 From: lillian542 Date: Fri, 13 Oct 2023 10:31:01 -0400 Subject: [PATCH 6/8] update tests --- tests/transforms/test_decompositions.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/transforms/test_decompositions.py b/tests/transforms/test_decompositions.py index 685b10e6f09..b4bdda9688d 100644 --- a/tests/transforms/test_decompositions.py +++ b/tests/transforms/test_decompositions.py @@ -42,12 +42,12 @@ ( S, typeof_gates_zyz, - [np.pi / 4, 0.0, np.pi / 4, (-0.7853981633974483 - 1.6780315470477092e-09j)], + [np.pi / 4, 0.0, np.pi / 4, -0.7853981633974483], ), ( T, typeof_gates_zyz, - [np.pi / 8, 0.0, np.pi / 8, (-0.39269908047469393 - 3.225207101387184e-09j)], + [np.pi / 8, 0.0, np.pi / 8, -0.39269908047469393], ), (qml.RZ(0.3, wires=0).matrix(), typeof_gates_zyz, [0.15, 0.0, 0.15, 0]), ( @@ -315,12 +315,12 @@ def test_xyx_decomposition_jax(self, U, expected_gates, expected_params): ( S, typeof_gates_zxz, - [np.pi / 4, 0.0, np.pi / 4, (-0.7853981633974483 - 1.6780315470477092e-09j)], + [np.pi / 4, 0.0, np.pi / 4, -0.7853981633974483], ), ( T, typeof_gates_zxz, - [np.pi / 8, 0.0, np.pi / 8, (-0.39269908047469393 - 3.225207101387184e-09j)], + [np.pi / 8, 0.0, np.pi / 8, -0.39269908047469393], ), (qml.RZ(0.3, wires=0).matrix(), typeof_gates_zxz, [0.15, 0.0, 0.15, 0]), ( From f61e78cb101ec11021182d2a66ebd5f520a46351 Mon Sep 17 00:00:00 2001 From: lillian542 Date: Fri, 13 Oct 2023 10:39:04 -0400 Subject: [PATCH 7/8] actually just don't cast variable to complex --- .../transforms/decompositions/single_qubit_unitary.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pennylane/transforms/decompositions/single_qubit_unitary.py b/pennylane/transforms/decompositions/single_qubit_unitary.py index 32c9c22b1d3..62969e36b7a 100644 --- a/pennylane/transforms/decompositions/single_qubit_unitary.py +++ b/pennylane/transforms/decompositions/single_qubit_unitary.py @@ -40,13 +40,10 @@ def _convert_to_su2(U, return_global_phase=False): # Compute the determinants U = qml.math.cast(U, "complex128") dets = math.linalg.det(U) + exp_angles = math.angle(dets) / 2 + U_SU2 = math.cast_like(U, dets) * math.exp(-1j * math.cast_like(exp_angles, 1j))[:, None, None] - # math.angle(dets) is always real, but we cast to complex for torch (so we can multiply by -1j) - exp_angles = math.cast_like(math.angle(dets), 1j) / 2 - U_SU2 = math.cast_like(U, dets) * math.exp(-1j * exp_angles)[:, None, None] - - # we cast back to real again if returning exp_angles - return (U_SU2, qml.math.real(exp_angles)) if return_global_phase else U_SU2 + return (U_SU2, exp_angles) if return_global_phase else U_SU2 def _rot_decomposition(U, wire): From fa99fcbfab904891eca5d846d1531fe495053fc0 Mon Sep 17 00:00:00 2001 From: lillian542 Date: Fri, 13 Oct 2023 10:41:38 -0400 Subject: [PATCH 8/8] update changelog --- doc/releases/changelog-dev.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 8e7cae8fa49..706d039224b 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -182,8 +182,7 @@ [(#4603)](https://github.com/PennyLaneAI/pennylane/pull/4603) * When decomposing a unitary matrix with `one_qubit_decomposition`, and opting to include the `GlobalPhase` - in the decomposition, the phase no longer has `dtype=complex` for phases with an imaginary component of 0. - To account for rounding errors in calculating the phase, any complex component less than `1e-15` is discarded. + in the decomposition, the phase is no longer cast to `dtype=complex`. [(#4653)](https://github.com/PennyLaneAI/pennylane/pull/4653) * `_qfunc_output` has been removed from `QuantumScript`, as it is no longer necessary. There is