def test_single_argument_step(self, cost_fn, mocker, monkeypatch): """Test that a simple QNode with a single argument correctly performs an optimization step, and that the single-shot gradients generated have the correct shape""" opt = qml.ShotAdaptiveOptimizer(min_shots=10) spy_single_shot_expval = mocker.spy(opt, "_single_shot_expval_gradients") spy_single_shot_qnodes = mocker.spy(opt, "_single_shot_qnode_gradients") spy_grad = mocker.spy(opt, "compute_grad") x_init = 0.5 new_x = opt.step(cost_fn, x_init) assert isinstance(new_x, float) assert new_x != x_init spy_grad.assert_called_once() if isinstance(cost_fn, qml.ExpvalCost): spy_single_shot_expval.assert_called_once() single_shot_grads = opt._single_shot_expval_gradients( cost_fn, [x_init], {}) else: spy_single_shot_qnodes.assert_called_once() single_shot_grads = opt._single_shot_qnode_gradients( cost_fn, [x_init], {}) # assert single shot gradients are computed correctly assert len(single_shot_grads) == 1 assert single_shot_grads[0].shape == (10, ) # monkeypatch the optimizer to use the same single shot gradients # as previously monkeypatch.setattr(opt, "_single_shot_qnode_gradients", lambda *args, **kwargs: single_shot_grads) monkeypatch.setattr(opt, "_single_shot_expval_gradients", lambda *args, **kwargs: single_shot_grads) # reset the shot budget opt.s = [np.array(10)] # check that the gradient and variance are computed correctly grad, grad_variance = opt.compute_grad(cost_fn, [x_init], {}) assert len(grad) == 1 assert len(grad_variance) == 1 assert np.allclose(grad, np.mean(single_shot_grads)) assert np.allclose(grad_variance, np.var(single_shot_grads, ddof=1)) # check that the gradient and variance are computed correctly # with a different shot budget opt.s = [np.array(5)] grad, grad_variance = opt.compute_grad(cost_fn, [x_init], {}) assert len(grad) == 1 assert len(grad_variance) == 1 assert np.allclose(grad, np.mean(single_shot_grads[0][:5])) assert np.allclose(grad_variance, np.var(single_shot_grads[0][:5], ddof=1))
def test_multiple_argument_step(self, mocker, monkeypatch): """Test that a simple QNode with multiple scalar arguments correctly performs an optimization step, and that the single-shot gradients generated have the correct shape""" dev = qml.device("default.qubit", wires=1, shots=100) @qml.qnode(dev) def circuit(x, y): qml.RX(x, wires=0) qml.RY(y, wires=0) return qml.expval(qml.PauliZ(0)) opt = qml.ShotAdaptiveOptimizer(min_shots=10) spy_single_shot = mocker.spy(opt, "_single_shot_qnode_gradients") spy_grad = mocker.spy(opt, "compute_grad") args = [0.1, 0.2] new_x = opt.step(circuit, *args) assert isinstance(new_x, list) assert len(new_x) == 2 spy_single_shot.assert_called_once() spy_grad.assert_called_once() # assert single shot gradients are computed correctly single_shot_grads = opt._single_shot_qnode_gradients(circuit, args, {}) assert len(single_shot_grads) == 2 assert single_shot_grads[0].shape == (10, ) # monkeypatch the optimizer to use the same single shot gradients # as previously monkeypatch.setattr(opt, "_single_shot_qnode_gradients", lambda *args, **kwargs: single_shot_grads) # reset the shot budget opt.s = [np.array(10), np.array(10)] # check that the gradient and variance are computed correctly grad, grad_variance = opt.compute_grad(circuit, args, {}) assert len(grad) == 2 assert len(grad_variance) == 2 assert np.allclose(grad, np.mean(single_shot_grads, axis=1)) assert np.allclose(grad_variance, np.var(single_shot_grads, ddof=1, axis=1)) # check that the gradient and variance are computed correctly # with a different shot budget opt.s = [np.array(5), np.array(7)] grad, grad_variance = opt.compute_grad(circuit, args, {}) assert len(grad) == 2 assert len(grad_variance) == 2 for p, s in zip(range(2), opt.s): assert np.allclose(grad[p], np.mean(single_shot_grads[p][:s])) assert np.allclose(grad_variance[p], np.var(single_shot_grads[p][:s], ddof=1))
def test_sample_values_hermitian(self, tol): """Tests if the samples of a Hermitian observable returned by sample have the correct values """ dev = plf.WavefunctionDevice(wires=1, shots=1_000_000) theta = 0.543 A = np.array([[1, 2j], [-2j, 0]]) with qml.tape.QuantumTape() as tape: qml.RX(theta, wires=[0]) O = qml.sample(qml.Hermitian(A, wires=[0])) # test correct variance for <Z> of a rotated state dev.apply(tape.operations, rotations=tape.diagonalizing_gates) dev._samples = dev.generate_samples() s1 = dev.sample(O.obs) # s1 should only contain the eigenvalues of # the hermitian matrix eigvals = np.linalg.eigvalsh(A) assert np.allclose(sorted(list(set(s1))), sorted(eigvals), atol=tol, rtol=0) # the analytic mean is 2*sin(theta)+0.5*cos(theta)+0.5 assert np.allclose( np.mean(s1), 2 * np.sin(theta) + 0.5 * np.cos(theta) + 0.5, atol=0.1, rtol=0 ) # the analytic variance is 0.25*(sin(theta)-4*cos(theta))^2 assert np.allclose( np.var(s1), 0.25 * (np.sin(theta) - 4 * np.cos(theta)) ** 2, atol=0.1, rtol=0 )
def test_sample_values_hermitian(self, qvm, tol): """Tests if the samples of a Hermitian observable returned by sample have the correct values """ theta = 0.543 shots = 1000_000 A = np.array([[1, 2j], [-2j, 0]]) dev = plf.QVMDevice(device="1q-qvm", shots=shots) dev.apply('RX', wires=[0], par=[theta]) dev._obs_queue = [qml.Hermitian(A, wires=[0], do_queue=False)] dev.pre_measure() s1 = dev.sample('Hermitian', [0], [A]) # s1 should only contain the eigenvalues of # the hermitian matrix eigvals = np.linalg.eigvalsh(A) assert np.allclose(sorted(list(set(s1))), sorted(eigvals), atol=tol, rtol=0) # the analytic mean is 2*sin(theta)+0.5*cos(theta)+0.5 assert np.allclose(np.mean(s1), 2 * np.sin(theta) + 0.5 * np.cos(theta) + 0.5, atol=0.1, rtol=0) # the analytic variance is 0.25*(sin(theta)-4*cos(theta))^2 assert np.allclose(np.var(s1), 0.25 * (np.sin(theta) - 4 * np.cos(theta))**2, atol=0.1, rtol=0)
def test_sample_values_hermitian(self, device, tol): """Tests if the samples of a Hermitian observable returned by sample have the correct values """ n_wires = 1 dev = device(n_wires) if dev.shots is None: pytest.skip("Device is in analytic mode, cannot test sampling.") if "Hermitian" not in dev.observables: pytest.skip("Skipped because device does not support the Hermitian observable.") A_ = np.array([[1, 2j], [-2j, 0]]) theta = 0.543 @qml.qnode(dev) def circuit(): qml.RX(theta, wires=[0]) return qml.sample(qml.Hermitian(A_, wires=0)) res = circuit().flatten() # res should only contain the eigenvalues of # the hermitian matrix eigvals = np.linalg.eigvalsh(A_) assert np.allclose(sorted(list(set(res.tolist()))), sorted(eigvals), atol=tol(dev.shots)) # the analytic mean is 2*sin(theta)+0.5*cos(theta)+0.5 assert np.allclose( np.mean(res), 2 * np.sin(theta) + 0.5 * np.cos(theta) + 0.5, atol=tol(False) ) # the analytic variance is 0.25*(sin(theta)-4*cos(theta))^2 assert np.allclose( np.var(res), 0.25 * (np.sin(theta) - 4 * np.cos(theta)) ** 2, atol=tol(False) )
def test_sample_values_hermitian(self, qvm, tol): """Tests if the samples of a Hermitian observable returned by sample have the correct values """ theta = 0.543 shots = 1_000_000 A = np.array([[1, 2j], [-2j, 0]]) dev = plf.QVMDevice(device="1q-qvm", shots=shots) O1 = qml.sample(qml.Hermitian(A, wires=[0])) circuit_graph = CircuitGraph([qml.RX(theta, wires=[0])] + [O1], {}, dev.wires) dev.apply(circuit_graph.operations, rotations=circuit_graph.diagonalizing_gates) dev._samples = dev.generate_samples() s1 = dev.sample(O1) # s1 should only contain the eigenvalues of # the hermitian matrix eigvals = np.linalg.eigvalsh(A) assert np.allclose(sorted(list(set(s1))), sorted(eigvals), atol=tol, rtol=0) # the analytic mean is 2*sin(theta)+0.5*cos(theta)+0.5 assert np.allclose( np.mean(s1), 2 * np.sin(theta) + 0.5 * np.cos(theta) + 0.5, atol=0.1, rtol=0 ) # the analytic variance is 0.25*(sin(theta)-4*cos(theta))^2 assert np.allclose( np.var(s1), 0.25 * (np.sin(theta) - 4 * np.cos(theta)) ** 2, atol=0.1, rtol=0 )
def test_sample_values_projector(self, device, tol): """Tests if the samples of a Projector observable returned by sample have the correct values """ n_wires = 1 dev = device(n_wires) if dev.shots is None: pytest.skip("Device is in analytic mode, cannot test sampling.") if "Projector" not in dev.observables: pytest.skip( "Skipped because device does not support the Projector observable." ) theta = 0.543 @qml.qnode(dev) def circuit(basis_state): qml.RX(theta, wires=[0]) return qml.sample(qml.Projector(basis_state, wires=0)) res = circuit([0]).flatten() # res should only contain 0 or 1, the eigenvalues of the projector assert np.allclose(sorted(list(set(res.tolist()))), [0, 1], atol=tol(dev.shots)) assert np.allclose(np.mean(res), np.cos(theta / 2)**2, atol=tol(False)) assert np.allclose(np.var(res), np.cos(theta / 2)**2 - (np.cos(theta / 2)**2)**2, atol=tol(False)) res = circuit([1]).flatten() # res should only contain 0 or 1, the eigenvalues of the projector assert np.allclose(sorted(list(set(res.tolist()))), [0, 1], atol=tol(dev.shots)) assert np.allclose(np.mean(res), np.sin(theta / 2)**2, atol=tol(False)) assert np.allclose(np.var(res), np.sin(theta / 2)**2 - (np.sin(theta / 2)**2)**2, atol=tol(False))
def compute_grad( self, objective_fn, args, kwargs ): # pylint: disable=signature-differs,arguments-differ r"""Compute gradient of the objective function, as well as the variance of the gradient, at the given point. Args: objective_fn (function): the objective function for optimization args: arguments to the objective function kwargs: keyword arguments to the objective function Returns: tuple[array[float], array[float]]: a tuple of NumPy arrays containing the gradient :math:`\nabla f(x^{(t)})` and the variance of the gradient """ if isinstance(objective_fn, qml.ExpvalCost): grads = self._single_shot_expval_gradients(objective_fn, args, kwargs) elif isinstance(objective_fn, qml.QNode) or hasattr(objective_fn, "device"): grads = self._single_shot_qnode_gradients(objective_fn, args, kwargs) else: raise ValueError( "The objective function must either be encoded as a single QNode or " "an ExpvalCost object for the Shot adaptive optimizer. " ) # grads will have dimension [max(self.s), *params.shape] # For each parameter, we want to truncate the number of shots to self.s[idx], # and take the mean and the variance. gradients = [] gradient_variances = [] for i, grad in enumerate(grads): p_ind = np.ndindex(*grad.shape[1:]) g = np.zeros_like(grad[0]) s = np.zeros_like(grad[0]) for idx in p_ind: grad_slice = grad[(slice(0, self.s[i][idx]),) + idx] g[idx] = np.mean(grad_slice) s[idx] = np.var(grad_slice, ddof=1) gradients.append(g) gradient_variances.append(s) return gradients, gradient_variances
def test_pauliz_hadamard(self, device, tol, skip_if): """Test that a tensor product involving PauliZ and PauliY and hadamard works correctly""" n_wires = 3 dev = device(n_wires) if dev.shots is None: pytest.skip("Device is in analytic mode, cannot test sampling.") skip_if(dev, {"supports_tensor_observables": False}) theta = 0.432 phi = 0.123 varphi = -0.543 @qml.qnode(dev) def circuit(): qml.RX(theta, wires=[0]) qml.RX(phi, wires=[1]) qml.RX(varphi, wires=[2]) qml.CNOT(wires=[0, 1]) qml.CNOT(wires=[1, 2]) return qml.sample( qml.PauliZ(wires=[0]) @ qml.Hadamard(wires=[1]) @ qml.PauliY(wires=[2]) ) res = circuit() # s1 should only contain 1 and -1 assert np.allclose(res ** 2, 1, atol=tol(False)) mean = np.mean(res) expected = -(np.cos(varphi) * np.sin(phi) + np.sin(varphi) * np.cos(theta)) / np.sqrt(2) assert np.allclose(mean, expected, atol=tol(False)) var = np.var(res) expected = ( 3 + np.cos(2 * phi) * np.cos(varphi) ** 2 - np.cos(2 * theta) * np.sin(varphi) ** 2 - 2 * np.cos(theta) * np.sin(phi) * np.sin(2 * varphi) ) / 4 assert np.allclose(var, expected, atol=tol(False))
grad_vals = [] for i in range(num_samples): print(f"num_qubits {num_qubits}, step {i}") dev = qml.device("default.qubit", wires=num_qubits) ansatz = mera_circuit(num_qubits, periodic, fix_layers) cost_fn = qml.ExpvalCost(ansatz, H, dev) grad = qml.grad(cost_fn) num_params_per_gate = 15 num_gates = get_num_mera_gates(num_qubits, periodic, fix_layers) num_params = num_params_per_gate * num_gates params = np.pi * (np.random.rand(num_params) - 1.0) gradient = np.array(grad(params)[0]) grad_vals.append(gradient) variances.append(np.mean(np.var(grad_vals, axis=0))) print(variances) #variances = np.array(np.mean(variances, axis=1)) qubits = np.array(qubits) # Fit the semilog plot to a straight line p = np.polyfit(qubits, np.log(variances), 1) # Plot the straight line fit to the semilog plt.semilogy(qubits, variances, "o") plt.semilogy(qubits, np.exp(p[0] * qubits + p[1]), "o-.", label="Slope {:3.2f}".format(p[0])) plt.xlabel(r"N Qubits")
shots = 10000 dev = qml.device("default.gaussian", wires=1, shots=shots, analytic=False) # circuit is a simple displacement of the ground state and returns a sample of size shots @qml.qnode(dev) def variational_circuit(x=None): qml.Displacement(x, 0.0, wires=0) return qml.sample(qml.X(0)) output = variational_circuit(x=input) # calculate expectation value using the Berry-Essen theorem ev1 = np.mean(output) var = np.var(output) ev = np.random.normal(ev1, np.sqrt(var / shots)) # plot min_ = min(output) max_ = max(output) bins = np.linspace(min_, max_, 100) fig = plt.figure() plt.hist(output, bins) plt.plot([ev, ev], [0, 350], 'r-') plt.yticks() plt.xlabel("$x$", size=22) plt.ylabel("f", rotation='horizontal', labelpad=15, size=22) plt.tick_params(axis="both", which="major", labelsize=22) plt.tick_params(axis="both", which="minor", labelsize=22) plt.legend(['expectation value', 'data'], prop={'size': 12}, loc='upper right')
# for more accurate results. qcircuit = qml.QNode(rand_circuit, dev) grad = qml.grad(qcircuit, argnum=0) num_samples = 200 grad_vals = [] for i in range(num_samples): params = np.random.uniform(0, 2 * np.pi, size=num_qubits) grad_vals.append( grad(params, random_gate_sequence=gate_sequence, num_qubits=num_qubits)) print("Variance of the gradient for {} samples: {}".format( num_samples, np.var(grad_vals))) ########################################################### # Evaluate the gradient for more qubits # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # We can repeat the above analysis with increasing number # of qubits. def generate_random_circuit(num_qubits): """ Generates a random quantum circuit based on (McClean et. al., 2019). Args: num_qubits (int): the number of qubits in the circuit """
def test_hermitian(self, device, tol, skip_if): """Test that a tensor product involving qml.Hermitian works correctly""" n_wires = 3 dev = device(n_wires) if dev.shots is None: pytest.skip("Device is in analytic mode, cannot test sampling.") if "Hermitian" not in dev.observables: pytest.skip( "Skipped because device does not support the Hermitian observable." ) skip_if(dev, {"supports_tensor_observables": False}) theta = 0.432 phi = 0.123 varphi = -0.543 A_ = 0.1 * np.array([ [-6, 2 + 1j, -3, -5 + 2j], [2 - 1j, 0, 2 - 1j, -5 + 4j], [-3, 2 + 1j, 0, -4 + 3j], [-5 - 2j, -5 - 4j, -4 - 3j, -6], ]) @qml.qnode(dev) def circuit(): qml.RX(theta, wires=[0]) qml.RX(phi, wires=[1]) qml.RX(varphi, wires=[2]) qml.CNOT(wires=[0, 1]) qml.CNOT(wires=[1, 2]) return qml.sample( qml.PauliZ(wires=[0]) @ qml.Hermitian(A_, wires=[1, 2])) res = circuit() # res should only contain the eigenvalues of # the hermitian matrix tensor product Z Z = np.diag([1, -1]) eigvals = np.linalg.eigvalsh(np.kron(Z, A_)) assert np.allclose(sorted(np.unique(res)), sorted(eigvals), atol=tol(False)) mean = np.mean(res) expected = (0.1 * 0.5 * (-6 * np.cos(theta) * (np.cos(varphi) + 1) - 2 * np.sin(varphi) * (np.cos(theta) + np.sin(phi) - 2 * np.cos(phi)) + 3 * np.cos(varphi) * np.sin(phi) + np.sin(phi))) assert np.allclose(mean, expected, atol=tol(False)) var = np.var(res) expected = ( 0.01 * (1057 - np.cos(2 * phi) + 12 * (27 + np.cos(2 * phi)) * np.cos(varphi) - 2 * np.cos(2 * varphi) * np.sin(phi) * (16 * np.cos(phi) + 21 * np.sin(phi)) + 16 * np.sin(2 * phi) - 8 * (-17 + np.cos(2 * phi) + 2 * np.sin(2 * phi)) * np.sin(varphi) - 8 * np.cos(2 * theta) * (3 + 3 * np.cos(varphi) + np.sin(varphi))**2 - 24 * np.cos(phi) * (np.cos(phi) + 2 * np.sin(phi)) * np.sin(2 * varphi) - 8 * np.cos(theta) * (4 * np.cos(phi) * (4 + 8 * np.cos(varphi) + np.cos(2 * varphi) - (1 + 6 * np.cos(varphi)) * np.sin(varphi)) + np.sin(phi) * (15 + 8 * np.cos(varphi) - 11 * np.cos(2 * varphi) + 42 * np.sin(varphi) + 3 * np.sin(2 * varphi)))) / 16) assert np.allclose(var, expected, atol=tol(False))
# increased for more accurate results. We only consider the # gradient of the output with respect to the last parameter in the # circuit. Hence we choose to save ``gradient[-1]`` only. grad_vals = [] num_samples = 200 for i in range(num_samples): gate_sequence = {i: np.random.choice(gate_set) for i in range(num_qubits)} qcircuit = qml.QNode(rand_circuit, dev) grad = qml.grad(qcircuit, argnum=0) params = np.random.uniform(0, 2 * np.pi, size=num_qubits) gradient = grad(params, random_gate_sequence=gate_sequence, num_qubits=num_qubits) grad_vals.append(gradient[-1]) print("Variance of the gradients for {} random circuits: {}".format(num_samples, np.var(grad_vals))) print("Mean of the gradients for {} random circuits: {}".format(num_samples, np.mean(grad_vals))) ############################################################################## # Evaluate the gradient for more qubits # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # We can repeat the above analysis with increasing number of qubits. qubits = [2, 3, 4, 5, 6] variances = [] for num_qubits in qubits: grad_vals = []
grad_vals = [] num_samples = 200 for i in range(num_samples): gate_sequence = {i: np.random.choice(gate_set) for i in range(num_qubits)} qcircuit = qml.QNode(rand_circuit, dev) grad = qml.grad(qcircuit, argnum=0) params = np.random.uniform(0, 2 * np.pi, size=num_qubits) gradient = grad(params, random_gate_sequence=gate_sequence, num_qubits=num_qubits) grad_vals.append(gradient[-1]) print("Variance of the gradients for {} random circuits: {}".format( num_samples, np.var(grad_vals))) print("Mean of the gradients for {} random circuits: {}".format( num_samples, np.mean(grad_vals))) ############################################################################## # Evaluate the gradient for more qubits # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # We can repeat the above analysis with increasing number of qubits. qubits = [2, 3, 4, 5, 6] variances = [] for num_qubits in qubits: grad_vals = [] for i in range(num_samples): dev = qml.device("default.qubit", wires=num_qubits)
def test_single_array_argument_step(self, cost_fn, mocker, monkeypatch): """Test that a simple QNode with a single array argument correctly performs an optimization step, and that the single-shot gradients generated have the correct shape""" opt = qml.ShotAdaptiveOptimizer(min_shots=10) spy_single_shot_expval = mocker.spy(opt, "_single_shot_expval_gradients") spy_single_shot_qnodes = mocker.spy(opt, "_single_shot_qnode_gradients") spy_grad = mocker.spy(opt, "compute_grad") x_init = np.array([[1., 2., 3.], [4., 5., 6.]]) new_x = opt.step(cost_fn, x_init) assert isinstance(new_x, np.ndarray) assert not np.allclose(new_x, x_init) if isinstance(cost_fn, qml.ExpvalCost): spy_single_shot_expval.assert_called_once() single_shot_grads = opt._single_shot_expval_gradients(cost_fn, [x_init], {}) else: spy_single_shot_qnodes.assert_called_once() single_shot_grads = opt._single_shot_qnode_gradients(cost_fn, [x_init], {}) spy_grad.assert_called_once() # assert single shot gradients are computed correctly assert len(single_shot_grads) == 1 assert single_shot_grads[0].shape == (10, 2, 3) # monkeypatch the optimizer to use the same single shot gradients # as previously monkeypatch.setattr(opt, "_single_shot_qnode_gradients", lambda *args, **kwargs: single_shot_grads) monkeypatch.setattr(opt, "_single_shot_expval_gradients", lambda *args, **kwargs: single_shot_grads) # reset the shot budget opt.s = [10 * np.ones([2, 3], dtype=np.int64)] # check that the gradient and variance are computed correctly grad, grad_variance = opt.compute_grad(cost_fn, [x_init], {}) assert len(grad) == 1 assert len(grad_variance) == 1 assert grad[0].shape == x_init.shape assert grad_variance[0].shape == x_init.shape assert np.allclose(grad, np.mean(single_shot_grads, axis=1)) assert np.allclose(grad_variance, np.var(single_shot_grads, ddof=1, axis=1)) # check that the gradient and variance are computed correctly # with a different shot budget opt.s[0] = opt.s[0] // 2 # all array elements have a shot budget of 5 opt.s[0][0, 0] = 8 # set the shot budget of the zeroth element to 8 grad, grad_variance = opt.compute_grad(cost_fn, [x_init], {}) assert len(grad) == 1 assert len(grad_variance) == 1 # check zeroth element assert np.allclose(grad[0][0, 0], np.mean(single_shot_grads[0][:8, 0, 0])) assert np.allclose(grad_variance[0][0, 0], np.var(single_shot_grads[0][:8, 0, 0], ddof=1)) # check other elements assert np.allclose(grad[0][0, 1], np.mean(single_shot_grads[0][:5, 0, 1])) assert np.allclose(grad_variance[0][0, 1], np.var(single_shot_grads[0][:5, 0, 1], ddof=1))
def test_multiple_array_argument_step(self, mocker, monkeypatch): """Test that a simple QNode with multiple array arguments correctly performs an optimization step, and that the single-shot gradients generated have the correct shape""" dev = qml.device("default.qubit", wires=1, shots=100) @qml.qnode(dev) def circuit(x, y): qml.RX(x[0, 0], wires=0) qml.RY(x[0, 1], wires=0) qml.RZ(x[0, 2], wires=0) qml.RX(x[1, 0], wires=0) qml.RY(x[1, 1], wires=0) qml.RZ(x[1, 2], wires=0) qml.RX(y[0], wires=0) qml.RY(y[1], wires=0) qml.RZ(y[2], wires=0) return qml.expval(qml.PauliZ(0)) opt = qml.ShotAdaptiveOptimizer(min_shots=10) spy_single_shot = mocker.spy(opt, "_single_shot_qnode_gradients") spy_grad = mocker.spy(opt, "compute_grad") args = [np.array([[1., 2., 3.], [4., 5., 6.]]), np.array([1., 2., 3.])] new_x = opt.step(circuit, *args) assert isinstance(new_x, list) assert len(new_x) == 2 spy_single_shot.assert_called_once() spy_grad.assert_called_once() # assert single shot gradients are computed correctly single_shot_grads = opt._single_shot_qnode_gradients(circuit, args, {}) assert len(single_shot_grads) == 2 assert single_shot_grads[0].shape == (10, 2, 3) assert single_shot_grads[1].shape == (10, 3) # monkeypatch the optimizer to use the same single shot gradients # as previously monkeypatch.setattr(opt, "_single_shot_qnode_gradients", lambda *args, **kwargs: single_shot_grads) # reset the shot budget opt.s = [10 * np.ones([2, 3], dtype=np.int64), 10 * np.ones([3], dtype=np.int64)] # check that the gradient and variance are computed correctly grad, grad_variance = opt.compute_grad(circuit, args, {}) assert len(grad) == 2 assert len(grad_variance) == 2 for i in range(2): assert grad[i].shape == args[i].shape assert grad_variance[i].shape == args[i].shape assert np.allclose(grad[0], np.mean(single_shot_grads[0], axis=0)) assert np.allclose(grad_variance[0], np.var(single_shot_grads[0], ddof=1, axis=0)) assert np.allclose(grad[1], np.mean(single_shot_grads[1], axis=0)) assert np.allclose(grad_variance[1], np.var(single_shot_grads[1], ddof=1, axis=0)) # check that the gradient and variance are computed correctly # with a different shot budget opt.s[0] = opt.s[0] // 2 # all array elements have a shot budget of 5 opt.s[0][0, 0] = 8 # set the shot budget of the zeroth element to 8 opt.s[1] = opt.s[1] // 5 # all array elements have a shot budget of 2 opt.s[1][0] = 7 # set the shot budget of the zeroth element to 7 grad, grad_variance = opt.compute_grad(circuit, args, {}) assert len(grad) == 2 assert len(grad_variance) == 2 # check zeroth element of arg 0 assert np.allclose(grad[0][0, 0], np.mean(single_shot_grads[0][:8, 0, 0])) assert np.allclose(grad_variance[0][0, 0], np.var(single_shot_grads[0][:8, 0, 0], ddof=1)) # check other elements of arg 0 assert np.allclose(grad[0][0, 1], np.mean(single_shot_grads[0][:5, 0, 1])) assert np.allclose(grad_variance[0][0, 1], np.var(single_shot_grads[0][:5, 0, 1], ddof=1)) # check zeroth element of arg 1 assert np.allclose(grad[1][0], np.mean(single_shot_grads[1][:7, 0])) assert np.allclose(grad_variance[1][0], np.var(single_shot_grads[1][:7, 0], ddof=1)) # check other elements of arg 1 assert np.allclose(grad[1][1], np.mean(single_shot_grads[1][:2, 1])) assert np.allclose(grad_variance[1][1], np.var(single_shot_grads[1][:2, 1], ddof=1))
def test_projector(self, device, tol, skip_if): """Test that a tensor product involving qml.Projector works correctly""" n_wires = 3 dev = device(n_wires) if dev.shots is None: pytest.skip("Device is in analytic mode, cannot test sampling.") if "Projector" not in dev.observables: pytest.skip( "Skipped because device does not support the Projector observable." ) skip_if(dev, {"supports_tensor_observables": False}) theta = 1.432 phi = 1.123 varphi = -0.543 @qml.qnode(dev) def circuit(basis_state): qml.RX(theta, wires=[0]) qml.RX(phi, wires=[1]) qml.RX(varphi, wires=[2]) qml.CNOT(wires=[0, 1]) qml.CNOT(wires=[1, 2]) return qml.sample( qml.PauliZ(wires=[0]) @ qml.Projector(basis_state, wires=[1, 2])) res = circuit([0, 0]) # res should only contain the eigenvalues of the projector matrix tensor product Z, i.e. {-1, 0, 1} assert np.allclose(sorted(np.unique(res)), [-1, 0, 1], atol=tol(False)) mean = np.mean(res) expected = (np.cos(varphi / 2) * np.cos(phi / 2) * np.cos( theta / 2))**2 - (np.cos(varphi / 2) * np.sin(phi / 2) * np.sin(theta / 2))**2 assert np.allclose(mean, expected, atol=tol(False)) var = np.var(res) expected = ( (np.cos(varphi / 2) * np.cos(phi / 2) * np.cos(theta / 2))**2 + (np.cos(varphi / 2) * np.sin(phi / 2) * np.sin(theta / 2))**2 - ((np.cos(varphi / 2) * np.cos(phi / 2) * np.cos(theta / 2))**2 - (np.cos(varphi / 2) * np.sin(phi / 2) * np.sin(theta / 2))**2)**2) assert np.allclose(var, expected, atol=tol(False)) res = circuit([0, 1]) assert np.allclose(sorted(np.unique(res)), [-1, 0, 1], atol=tol(False)) mean = np.mean(res) expected = (np.sin(varphi / 2) * np.cos(phi / 2) * np.cos( theta / 2))**2 - (np.sin(varphi / 2) * np.sin(phi / 2) * np.sin(theta / 2))**2 assert np.allclose(mean, expected, atol=tol(False)) var = np.var(res) expected = ( (np.sin(varphi / 2) * np.cos(phi / 2) * np.cos(theta / 2))**2 + (np.sin(varphi / 2) * np.sin(phi / 2) * np.sin(theta / 2))**2 - ((np.sin(varphi / 2) * np.cos(phi / 2) * np.cos(theta / 2))**2 - (np.sin(varphi / 2) * np.sin(phi / 2) * np.sin(theta / 2))**2)**2) assert np.allclose(var, expected, atol=tol(False)) res = circuit([1, 0]) assert np.allclose(sorted(np.unique(res)), [-1, 0, 1], atol=tol(False)) mean = np.mean(res) expected = (np.sin(varphi / 2) * np.sin(phi / 2) * np.cos( theta / 2))**2 - (np.sin(varphi / 2) * np.cos(phi / 2) * np.sin(theta / 2))**2 assert np.allclose(mean, expected, atol=tol(False)) var = np.var(res) expected = ( (np.sin(varphi / 2) * np.sin(phi / 2) * np.cos(theta / 2))**2 + (np.sin(varphi / 2) * np.cos(phi / 2) * np.sin(theta / 2))**2 - ((np.sin(varphi / 2) * np.sin(phi / 2) * np.cos(theta / 2))**2 - (np.sin(varphi / 2) * np.cos(phi / 2) * np.sin(theta / 2))**2)**2) assert np.allclose(var, expected, atol=tol(False)) res = circuit([1, 1]) assert np.allclose(sorted(np.unique(res)), [-1, 0, 1], atol=tol(False)) mean = np.mean(res) expected = (np.cos(varphi / 2) * np.sin(phi / 2) * np.cos( theta / 2))**2 - (np.cos(varphi / 2) * np.cos(phi / 2) * np.sin(theta / 2))**2 assert np.allclose(mean, expected, atol=tol(False)) var = np.var(res) expected = ( (np.cos(varphi / 2) * np.sin(phi / 2) * np.cos(theta / 2))**2 + (np.cos(varphi / 2) * np.cos(phi / 2) * np.sin(theta / 2))**2 - ((np.cos(varphi / 2) * np.sin(phi / 2) * np.cos(theta / 2))**2 - (np.cos(varphi / 2) * np.cos(phi / 2) * np.sin(theta / 2))**2)**2) assert np.allclose(var, expected, atol=tol(False))