Beispiel #1
0
    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))
Beispiel #2
0
    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))
Beispiel #3
0
    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
        )
Beispiel #4
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)
Beispiel #5
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)
        )
Beispiel #6
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 = 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))
Beispiel #8
0
    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
Beispiel #9
0
    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")
Beispiel #11
0
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')
Beispiel #12
0
# 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 = []
Beispiel #15
0
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)
Beispiel #16
0
    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))
Beispiel #17
0
    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))