-
Notifications
You must be signed in to change notification settings - Fork 575
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
Add new differentiation method based on rewinding the tape [PR1] #1029
Conversation
Hello. You may have forgotten to update the changelog!
|
|
||
with RewindTape() as tape: | ||
qml.RX(a, wires=0) | ||
qml.expval(qml.PauliZ(0)) | ||
|
||
circuit_output = tape.execute(dev) | ||
expected_output = np.cos(a) | ||
assert np.allclose(circuit_output, expected_output, atol=tol, rtol=0) | ||
|
||
# circuit jacobians | ||
circuit_jacobian = tape.jacobian(dev, method="analytic") | ||
expected_jacobian = -np.sin(a) | ||
assert np.allclose(circuit_jacobian, expected_jacobian, atol=tol, rtol=0) | ||
|
||
def test_multiple_rx_gradient(self, tol): | ||
"""Tests that the gradient of multiple RX gates in a circuit yields the correct result.""" | ||
dev = qml.device("default.qubit", wires=3) | ||
params = np.array([np.pi, np.pi / 2, np.pi / 3]) | ||
|
||
with RewindTape() as tape: | ||
qml.RX(params[0], wires=0) | ||
qml.RX(params[1], wires=1) | ||
qml.RX(params[2], wires=2) | ||
|
||
for idx in range(3): | ||
qml.expval(qml.PauliZ(idx)) | ||
|
||
circuit_output = tape.execute(dev) | ||
expected_output = np.cos(params) | ||
assert np.allclose(circuit_output, expected_output, atol=tol, rtol=0) | ||
|
||
# circuit jacobians | ||
circuit_jacobian = tape.jacobian(dev, method="analytic") | ||
expected_jacobian = -np.diag(np.sin(params)) | ||
assert np.allclose(circuit_jacobian, expected_jacobian, atol=tol, rtol=0) | ||
|
||
qubit_ops = [getattr(qml, name) for name in qml.ops._qubit__ops__] | ||
analytic_qubit_ops = {cls for cls in qubit_ops if cls.grad_method == "A"} | ||
analytic_qubit_ops -= { | ||
qml.CRot, # not supported for RewindTape | ||
qml.PauliRot, # not supported in test | ||
qml.MultiRZ, # not supported in test | ||
qml.U1, # not supported on device | ||
qml.U2, # not supported on device | ||
qml.U3, # not supported on device | ||
} | ||
|
||
@pytest.mark.parametrize("obs", [qml.PauliX, qml.PauliY]) | ||
@pytest.mark.parametrize("op", analytic_qubit_ops) | ||
def test_gradients(self, op, obs, mocker, tol, dev): | ||
"""Tests that the gradients of circuits match between the | ||
finite difference and analytic methods.""" | ||
args = np.linspace(0.2, 0.5, op.num_params) | ||
|
||
with RewindTape() as tape: | ||
qml.Hadamard(wires=0) | ||
qml.RX(0.543, wires=0) | ||
qml.CNOT(wires=[0, 1]) | ||
|
||
op(*args, wires=range(op.num_wires)) | ||
|
||
qml.Rot(1.3, -2.3, 0.5, wires=[0]) | ||
qml.RZ(-0.5, wires=0) | ||
qml.RY(0.5, wires=1).inv() | ||
qml.CNOT(wires=[0, 1]) | ||
|
||
qml.expval(obs(wires=0)) | ||
qml.expval(qml.PauliZ(wires=1)) | ||
|
||
tape.execute(dev) | ||
|
||
tape.trainable_params = set(range(1, 1 + op.num_params)) | ||
|
||
grad_F = tape.jacobian(dev, method="numeric") | ||
|
||
spy = mocker.spy(RewindTape, "_rewind_jacobian") | ||
grad_A = tape.jacobian(dev, method="analytic") | ||
spy.assert_called() | ||
assert np.allclose(grad_A, grad_F, atol=tol, rtol=0) | ||
|
||
def test_gradient_gate_with_multiple_parameters(self, tol, dev): | ||
"""Tests that gates with multiple free parameters yield correct gradients.""" | ||
x, y, z = [0.5, 0.3, -0.7] | ||
|
||
with RewindTape() as tape: | ||
qml.RX(0.4, wires=[0]) | ||
qml.Rot(x, y, z, wires=[0]) | ||
qml.RY(-0.2, wires=[0]) | ||
qml.expval(qml.PauliZ(0)) | ||
|
||
tape.trainable_params = {1, 2, 3} | ||
|
||
grad_A = tape.jacobian(dev, method="analytic") | ||
grad_F = tape.jacobian(dev, method="numeric") | ||
|
||
# gradient has the correct shape and every element is nonzero | ||
assert grad_A.shape == (1, 3) | ||
assert np.count_nonzero(grad_A) == 3 | ||
# the different methods agree | ||
assert np.allclose(grad_A, grad_F, atol=tol, rtol=0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Many of these are copied from the reversible tests, and edited accordingly.
Codecov Report
@@ Coverage Diff @@
## master #1029 +/- ##
==========================================
- Coverage 97.94% 97.91% -0.03%
==========================================
Files 153 154 +1
Lines 11384 11441 +57
==========================================
+ Hits 11150 11203 +53
- Misses 234 238 +4
Continue to review full report at Codecov.
|
gradients of qubit operations using the rewind method of analytic differentiation. | ||
This gradient method returns *exact* gradients, however requires use of a statevector simulator. | ||
Simply create the tape, and then call the Jacobian method: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we show how to create a tape as a rewind tape?
>>> dev = qml.Device('default.qubit', wires=[0]) | |
>>> with qml.tape.RewindTape() as tape: | |
qml.RX(0.1, wires=0) |
|
||
if not supported_device: | ||
raise qml.QuantumFunctionError( | ||
"The rewind gradient method is only supported on statevector-based devices" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The device could be a statevector device, yet just not use the same internal functions as default.qubit
, the _apply_operation
and _apply_unitary
.
Maybe we should have one error for whether or not its a statevector device, and another for the internal functions. This would allow the error message to be more precise.
To maintain small PRs, #1017 will be split into a few manageable PRs. This is the first one - it contains the main
RewindTape
and tests.Coming later: