Ejemplo n.º 1
0
 def param_shift(weights, s_param):
     unit_v = np.zeros_like(weights)
     gradient_ = np.zeros_like(weights)
     for ii in np.ndenumerate(unit_v):
         ii = ii[0]
         unit_v[ii] = 1
         gradient_[ii] = (circuit(weights + s_param_g * unit_v) -
                          circuit(weights - s_param_g * unit_v)) / (
                              2 * np.sin(s_param_g))
         unit_v[ii] = 0
     return gradient_
Ejemplo n.º 2
0
def parameter_shift(weights):
    """Compute the gradient of the variational circuit given by the
    ansatz function using the parameter-shift rule.

    Write your code below between the # QHACK # markers—create a device with
    the correct number of qubits, create a QNode that applies the above ansatz,
    and compute the gradient of the provided ansatz using the parameter-shift rule.

    Args:
        weights (array): An array of floating-point numbers with size (2, 3).

    Returns:
        array: The gradient of the variational circuit. The shape should match
        the input weights array.
    """
    dev = qml.device("default.qubit", wires=3)

    @qml.qnode(dev)
    def circuit(weights):
        for i in range(len(weights)):
            qml.RX(weights[i, 0], wires=0)
            qml.RY(weights[i, 1], wires=1)
            qml.RZ(weights[i, 2], wires=2)

            qml.CNOT(wires=[0, 1])
            qml.CNOT(wires=[1, 2])
            qml.CNOT(wires=[2, 0])

        return qml.expval(qml.PauliY(0) @ qml.PauliZ(2))

    gradient = np.zeros_like(weights)

    # QHACK #
    #

    def parameter_shift_term(qnode, params, i, j):
        s = 0.1
        shifted = params.copy()
        shifted_back = params.copy()

        shifted[i][j] += s
        forward = qnode(shifted)

        shifted_back[i][j] -= s
        backward = qnode(shifted_back)

        return (forward - backward) / (2 * np.sin(s))

    def parameter_shift_all(qnode, params, gradient):
        for i in range(len(params[0]) - 1):
            for j in range(len(params[1])):
                gradient[i][j] = parameter_shift_term(qnode, params, i, j)

        return gradient

    gradient = parameter_shift_all(circuit, weights, gradient)

    # QHACK #

    return gradient
def parameter_shift(weights):
    """Compute the gradient of the variational circuit given by the
    ansatz function using the parameter-shift rule.
    Write your code below between the # QHACK # markers—create a device with
    the correct number of qubits, create a QNode that applies the above ansatz,
    and compute the gradient of the provided ansatz using the parameter-shift rule.
    Args:
        weights (array): An array of floating-point numbers with size (2, 3).
    Returns:
        array: The gradient of the variational circuit. The shape should match
        the input weights array.
    """
    dev = qml.device("default.qubit", wires=3)

    @qml.qnode(dev)
    def circuit(weights):
        for i in range(len(weights)):
            qml.RX(weights[i, 0], wires=0)
            qml.RY(weights[i, 1], wires=1)
            qml.RZ(weights[i, 2], wires=2)

            qml.CNOT(wires=[0, 1])
            qml.CNOT(wires=[1, 2])
            qml.CNOT(wires=[2, 0])

        return qml.expval(qml.PauliY(0) @ qml.PauliZ(2))

    gradient = np.zeros_like(weights)

    # QHACK #
    gradient = qml.grad(circuit)
    # QHACK #

    return gradient(weights)
Ejemplo n.º 4
0
    def parameter_shift(qnode, params):
        gradients = np.zeros_like(weights)
        for i in range(params.shape[0]):
            for j in range(params.shape[1]):
                gradients[i][j] = parameter_shift_term(qnode, params, i, j)

        return gradients
def parameter_shift(weights):
    """Compute the gradient of the variational circuit given by the
    ansatz function using the parameter-shift rule.

    Write your code below between the # QHACK # markers—create a device with
    the correct number of qubits, create a QNode that applies the above ansatz,
    and compute the gradient of the provided ansatz using the parameter-shift rule.

    Args:
        weights (array): An array of floating-point numbers with size (2, 3).

    Returns:
        array: The gradient of the variational circuit. The shape should match
        the input weights array.
    """
    dev = qml.device("default.qubit", wires=3)

    @qml.qnode(dev)
    def circuit(weights):
        for i in range(len(weights)):
            qml.RX(weights[i, 0], wires=0)
            qml.RY(weights[i, 1], wires=1)
            qml.RZ(weights[i, 2], wires=2)

            qml.CNOT(wires=[0, 1])
            qml.CNOT(wires=[1, 2])
            qml.CNOT(wires=[2, 0])

        return qml.expval(qml.PauliY(0) @ qml.PauliZ(2))

    gradient = np.zeros_like(weights)

    # QHACK #
    # grad_func = qml.grad(circuit)
    # gradient = grad_func(weights)[0]
    def parameter_shift_term(qnode, params, i, shift):
        shifted = params.copy()
        shifted[np.unravel_index(i, shifted.shape)] += shift  #np.pi / 2
        forward = qnode(shifted)  # forward evaluation

        shifted[np.unravel_index(i, shifted.shape)] -= 2 * shift  #np.pi
        backward = qnode(shifted)  # backward evaluation

        return 0.5 * (forward - backward) / np.sin(shift)

    def parameter_shift(qnode, params, shift):
        gradients = np.zeros_like(params)

        for i in range(len(gradients.flatten())):
            gradients[np.unravel_index(
                i, gradients.shape)] = parameter_shift_term(
                    qnode, params, i, shift)

        return gradients

    gradient = parameter_shift(circuit, weights, shift=0.5 * np.pi)
    # QHACK #

    return gradient
    def parameter_shift(qnode, weights):
        gradients = np.zeros_like(weights)
        print(len(weights))

        for i in range(weights.shape[0]):
            for j in range(weights.shape[1]):
                gradients[i, j] = parameter_shift_term(qnode, weights, i, j)
        return gradients
Ejemplo n.º 7
0
    def get_grads(qnode, params):

        gradients = np.zeros_like(params)

        for i in range(30):
            gradients[i] = parameter_shift_term(qnode, params, i)

        return gradients
Ejemplo n.º 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
    def parameter_shift(qnode, params, shift):
        gradients = np.zeros_like(params)

        for i in range(len(gradients.flatten())):
            gradients[np.unravel_index(
                i, gradients.shape)] = parameter_shift_term(
                    qnode, params, i, shift)

        return gradients
    def parameter_shiftz(weight):
        gradient = np.zeros_like(weight)

        for i in range(len(weight)):
            gradient[i][0] = parameter_shift_grad(weight, i, 0)
            gradient[i][1] = parameter_shift_grad(weight, i, 1)
            gradient[i][2] = parameter_shift_grad(weight, i, 2)

        return gradient
Ejemplo n.º 11
0
    def get_grads(qnode, params):

        gradients = np.zeros_like(params)

        for i in range(2):
            for j in range(3):
                gradients[i][j] = parameter_shift_term(qnode, params, i, j)

        return gradients
Ejemplo n.º 12
0
    def test_Rot_gradient(self, mocker, theta, shift, tol):
        """Tests that the automatic gradient of an arbitrary Euler-angle-parameterized gate is correct."""
        spy = mocker.spy(qml.gradients.parameter_shift,
                         "_get_operation_recipe")
        dev = qml.device("default.qubit", wires=1)
        params = np.array([theta, theta**3, np.sqrt(2) * theta])

        with qml.tape.JacobianTape() as tape:
            qml.QubitStateVector(np.array([1.0, -1.0], requires_grad=False) /
                                 np.sqrt(2),
                                 wires=0)
            qml.Rot(*params, wires=[0])
            qml.expval(qml.PauliZ(0))

        tape.trainable_params = {1, 2, 3}

        tapes, fn = qml.gradients.param_shift(tape, shift=shift)
        assert len(tapes) == 2 * len(tape.trainable_params)

        autograd_val = fn(dev.batch_execute(tapes))
        manualgrad_val = np.zeros_like(autograd_val)

        for idx in list(np.ndindex(*params.shape)):
            s = np.zeros_like(params)
            s[idx] += np.pi / 2

            forward = tape.execute(dev, params=params + s)
            backward = tape.execute(dev, params=params - s)

            manualgrad_val[0, idx] = (forward - backward) / 2

        assert np.allclose(autograd_val, manualgrad_val, atol=tol, rtol=0)
        assert spy.call_args[1]["shift"] == shift

        # compare to finite differences
        tapes, fn = qml.gradients.finite_diff(tape)
        numeric_val = fn(dev.batch_execute(tapes))
        assert np.allclose(autograd_val, numeric_val, atol=tol, rtol=0)
Ejemplo n.º 13
0
def parameter_shift(weights):
    """Compute the gradient of the variational circuit given by the
    ansatz function using the parameter-shift rule.

    Write your code below between the # QHACK # markers—create a device with
    the correct number of qubits, create a QNode that applies the above ansatz,
    and compute the gradient of the provided ansatz using the parameter-shift rule.

    Args:
        weights (array): An array of floating-point numbers with size (2, 3).

    Returns:
        array: The gradient of the variational circuit. The shape should match
        the input weights array.
    """
    dev = qml.device("default.qubit", wires=3)

    def parameter_shift_term(qnode, params, t,i):
        shifted = params.copy()
        shifted[t,i] += np.pi/2
        forward = qnode(shifted)  # forward evaluation

        shifted[t,i] -= np.pi
        backward = qnode(shifted) # backward evaluation

        return 0.5 * (forward - backward)

    @qml.qnode(dev)
    def circuit(weights):
        for i in range(len(weights)):
            qml.RX(weights[i, 0], wires=0)
            qml.RY(weights[i, 1], wires=1)
            qml.RZ(weights[i, 2], wires=2)

            qml.CNOT(wires=[0, 1])
            qml.CNOT(wires=[1, 2])
            qml.CNOT(wires=[2, 0])

        return qml.expval(qml.PauliY(0) @ qml.PauliZ(2))
    c_test, c_input = weights.shape 
    gradient = np.zeros_like(weights)

    for t in range(c_test):
        for a in range(c_input):
            gradient[t,a] = parameter_shift_term(circuit, weights, t,a)
    # QHACK #
    #
    # QHACK #

    return gradient
Ejemplo n.º 14
0
def plot_decision_boundaries(classifier, ax, N_gridpoints=14):
    _xx, _yy = np.meshgrid(np.linspace(-1, 1, N_gridpoints),
                           np.linspace(-1, 1, N_gridpoints))

    _zz = np.zeros_like(_xx)
    for idx in np.ndindex(*_xx.shape):
        _zz[idx] = classifier.predict(
            np.array([_xx[idx], _yy[idx]])[np.newaxis, :])

    plot_data = {'_xx': _xx, '_yy': _yy, '_zz': _zz}
    ax.contourf(_xx,
                _yy,
                _zz,
                cmap=mpl.colors.ListedColormap(['#FF0000', '#0000FF']),
                alpha=.2,
                levels=[-1, 0, 1])
    dataset.plot(ax)

    return plot_data
    def term(params, sign_term, sign_i, i, sign_j, j):
        qml.BasisState(np.array([0, 0, 0, 0, 0, 0]), wires=range(6))

        variational_circuit_wire_list(params)

        shift = np.zeros_like(params)
        shift[i] = sign_i * one
        shift[j] = sign_j * one
        variational_circuit_wire_list(params + shift, wire_list=[3, 4, 5])

        # return qml.DiagonalQubitUnitary([1] * 6, wires=[0, 1, 2]), qml.DiagonalQubitUnitary([1] * 6, wires=[3, 4, 5])
        # return [
        #     [qml.expval(qml.PauliX(wire)) for wire in [0, 1, 2]],
        #     [qml.expval(qml.PauliX(wire)) for wire in [3, 4, 5]],
        # ]
        return sign_term * np.abs(
            qml.dot(
                [qml.expval(qml.PauliX(wire)) for wire in [0, 1, 2]],
                [qml.expval(qml.PauliX(wire)) for wire in [3, 4, 5]],
            ))**2
Ejemplo n.º 16
0
def CFIM(weights, phi, gamma):
    p = experiment(weights, phi, gamma=gamma)
    dp = []

    for idx in range(3):
        # We use the parameter-shift rule explicitly
        # to compute the derivatives
        shift = np.zeros_like(phi)
        shift[idx] = np.pi / 2

        plus = experiment(weights, phi + shift, gamma=gamma)
        minus = experiment(weights, phi - shift, gamma=gamma)

        dp.append(0.5 * (plus - minus))

    matrix = [0] * 9
    for i in range(3):
        for j in range(3):
            matrix[3 * i + j] = np.sum(dp[i] * dp[j] / p)

    return np.array(matrix).reshape((3, 3))
Ejemplo n.º 17
0
def plot_decision_boundaries(classifier, ax, N_gridpoints=14):
    _xx, _yy = np.meshgrid(np.linspace(-1, 1, N_gridpoints),
                           np.linspace(-1, 1, N_gridpoints))

    _zz = np.zeros_like(_xx)
    for idx in np.ndindex(*_xx.shape):
        _zz[idx] = classifier.predict(
            np.array([_xx[idx], _yy[idx]])[np.newaxis, :])

    plot_data = {"_xx": _xx, "_yy": _yy, "_zz": _zz}
    ax.contourf(
        _xx,
        _yy,
        _zz,
        cmap=mpl.colors.ListedColormap(["#FF0000", "#0000FF"]),
        alpha=0.2,
        levels=[-1, 0, 1],
    )
    plot_double_cake_data(X, Y, ax)

    return plot_data
Ejemplo n.º 18
0
for i in range(20):
    weights, cost_ = opt.step_and_cost(opt_cost, weights)

    if (i + 1) % 5 == 0:
        print("Iteration {:>4}: Cost = {:6.4f}".format(i + 1, cost_))

##############################################################################
# Comparison with the standard protocol
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# Now we want to see how our protocol compares to the standard Ramsey interferometry protocol.
# The probe state in this case is a tensor product of three separate :math:`|+\rangle` states
# while the encoded state is measured in the :math:`|+\rangle / |-\rangle` basis.
# We can recreate the standard schemes with specific weights for our setup.

Ramsey_weights = np.zeros_like(weights)
Ramsey_weights[1:6:2] = np.pi / 2
Ramsey_weights[15:20:2] = np.pi / 2
print("Cost for standard Ramsey sensing = {:6.4f}".format(
    opt_cost(Ramsey_weights)))

##############################################################################
# We can now make a plot to compare the noise scaling of the above probes.
gammas = np.linspace(0, 0.75, 21)
comparison_costs = {
    "optimized": [],
    "standard": [],
}

for gamma in gammas:
    comparison_costs["optimized"].append(cost(weights, phi, gamma, J, W))
C1 = [circuit(np.array([theta, .5])) for theta in theta_func]
C2 = [circuit(np.array([3.3, theta])) for theta in theta_func]

# Show the sweeps.
fig, ax = plt.subplots(1, 1, figsize=(4, 3))
ax.plot(theta_func, C1, label="$E(\\theta, 0.5)$", color="r")
ax.plot(theta_func, C2, label="$E(3.3, \\theta)$", color="orange")
ax.set_xlabel("$\\theta$")
ax.set_ylabel("$E$")
ax.legend()
plt.tight_layout()

# Create a 2D grid and evaluate the energy on the grid points.
# We cut out a part of the landscape to increase clarity.
X, Y = np.meshgrid(theta_func, theta_func)
Z = np.zeros_like(X)
for i, t1 in enumerate(theta_func):
    for j, t2 in enumerate(theta_func):
        # Cut out the viewer-facing corner
        if (2 * np.pi - t2)**2 + t1**2 > 4:
            Z[i, j] = circuit([t1, t2])
        else:
            X[i, j] = np.nan
            Y[i, j] = np.nan
            Z[i, j] = np.nan

# Show the energy landscape on the grid.
fig, ax = plt.subplots(1, 1, subplot_kw={"projection": "3d"}, figsize=(4, 4))
surf = ax.plot_surface(X,
                       Y,
                       Z,
Ejemplo n.º 20
0
def Words_Entangler_circuit(params_, wires):
    """Apply many controled-rotation between words
    'params_' is a matrix of different angles of size (num_words/2) x qbits_per_word """

    mask = np.zeros_like(params_)
    #EVEN number of qubits
    if num_words % 2 == 0:
        for bit in range(qbits_per_word):
            for i in range(int(np.ceil(num_words / 2))):
                if bit % 2 != 0:
                    i_ = i * qbits_per_word * 2 + qbits_per_word
                    if i_ + bit + qbits_per_word > len(wires) - 1:
                        qml.CRY(params_[bit][i],
                                wires=[wires[i_ + bit], wires[bit]])
                        mask[bit][i] = 1
                    else:
                        qml.CRY(params_[bit][i],
                                wires=[
                                    wires[i_ + bit],
                                    wires[i_ + bit + qbits_per_word]
                                ])
                        mask[bit][i] = 1
                else:
                    i_ = i * qbits_per_word * 2
                    qml.CRY(params_[bit][i],
                            wires=[
                                wires[i_ + bit],
                                wires[i_ + bit + qbits_per_word]
                            ])
                    mask[bit][i] = 1

    #ODD number of qubits
    elif num_words % 2 == 1:
        for bit in range(qbits_per_word):
            if bit % 2 == 0:  #even bits: need to loop back the last CRot
                for i in range(int(np.ceil(num_words / 2))):
                    i_ = i * qbits_per_word * 2
                    if i_ + bit + qbits_per_word > len(wires) - 1:
                        if bit == qbits_per_word - 1:  #last layer
                            qml.CRY(params_[bit][i],
                                    wires=[wires[i_ + bit], wires[bit]])
                            mask[bit][i] = 1
                        else:
                            qml.CRY(params_[bit][i],
                                    wires=[wires[i_ + bit], wires[bit + 1]])
                            mask[bit][i] = 1
                    else:
                        qml.CRY(params_[bit][i],
                                wires=[
                                    wires[i_ + bit],
                                    wires[i_ + bit + qbits_per_word]
                                ])
                        mask[bit][i] = 1
            else:  #odd bits
                for i in range(int(np.floor(num_words / 2))):
                    i_ = i * qbits_per_word * 2 + qbits_per_word
                    qml.CRY(params_[bit][i],
                            wires=[
                                wires[i_ + bit],
                                wires[i_ + bit + qbits_per_word]
                            ])
                    mask[bit][i] = 1
Ejemplo n.º 21
0
 def clear(self) -> None:
     """Resets the mask to not mask anything."""
     self.mask = np.zeros_like(self.mask, dtype=self.mask.dtype, requires_grad=False)
def natural_gradient(params):
    """Calculate the natural gradient of the qnode() cost function.

    The code you write for this challenge should be completely contained within this function
    between the # QHACK # comment markers.

    You should evaluate the metric tensor and the gradient of the QNode, and then combine these
    together using the natural gradient definition. The natural gradient should be returned as a
    NumPy array.

    The metric tensor should be evaluated using the equation provided in the problem text. Hint:
    you will need to define a new QNode that returns the quantum state before measurement.

    Args:
        params (np.ndarray): Input parameters, of dimension 6

    Returns:
        np.ndarray: The natural gradient evaluated at the input parameters, of dimension 6
    """

    natural_grad = np.zeros(6)

    # QHACK #
    def variational_circuit_wire_list(params, wire_list=None):
        """A layered variational circuit composed of two parametrized layers of single qubit rotations
        interleaved with non-parameterized layers of fixed quantum gates specified by
        ``non_parametrized_layer``.

        The first parametrized layer uses the first three parameters of ``params``, while the second
        layer uses the final three parameters.
        """
        if wire_list is None:
            wire_list = [0, 1, 2]

        non_parametrized_layer()
        qml.RX(params[0], wires=wire_list[0])
        qml.RY(params[1], wires=wire_list[1])
        qml.RZ(params[2], wires=wire_list[2])
        non_parametrized_layer()
        qml.RX(params[3], wires=wire_list[0])
        qml.RY(params[4], wires=wire_list[1])
        qml.RZ(params[5], wires=wire_list[2])

    # FS metric TODO
    F = np.zeros([6, 6], dtype=np.float64)
    one = np.pi / 2
    dev_F = qml.device("default.qubit", wires=2 * n_wires)

    @qml.qnode(dev_F)
    def term(params, sign_term, sign_i, i, sign_j, j):
        qml.BasisState(np.array([0, 0, 0, 0, 0, 0]), wires=range(6))

        variational_circuit_wire_list(params)

        shift = np.zeros_like(params)
        shift[i] = sign_i * one
        shift[j] = sign_j * one
        variational_circuit_wire_list(params + shift, wire_list=[3, 4, 5])

        # return qml.DiagonalQubitUnitary([1] * 6, wires=[0, 1, 2]), qml.DiagonalQubitUnitary([1] * 6, wires=[3, 4, 5])
        # return [
        #     [qml.expval(qml.PauliX(wire)) for wire in [0, 1, 2]],
        #     [qml.expval(qml.PauliX(wire)) for wire in [3, 4, 5]],
        # ]
        return sign_term * np.abs(
            qml.dot(
                [qml.expval(qml.PauliX(wire)) for wire in [0, 1, 2]],
                [qml.expval(qml.PauliX(wire)) for wire in [3, 4, 5]],
            ))**2

    for i in range(len(params)):
        for j in range(len(params)):
            print(term(params, -1, 1, i, 1, j))
            # F[i, j] += term(params, -1, 1, i, 1, j)
            # F[i, j] += term(params, 1, 1, i, -1, j)
            # F[i, j] += term(params, 1, -1, i, 1, j)
            # F[i, j] += term(params, -1, -1, i, -1, j)
    F /= 8
    F_inverse = np.linalg.inv(F)

    # gradient
    gradient = np.zeros_like(params, dtype=np.float64)
    shift_mask = np.zeros_like(params)
    s = 1
    denominator = 2 * np.sin(s)
    for i in range(len(params)):
        shift_mask[i] = s
        gradient[i] = (qnode(params + shift_mask) -
                       qnode(params - shift_mask)) / denominator
        shift_mask[i] = 0

    # from https://pennylane.ai/qml/demos/tutorial_quantum_natural_gradient.html
    # print(np.round(qml.metric_tensor(circuit)(params), 8))

    np.set_printoptions(formatter={'float': lambda x: "{0:0.5f}".format(x)})
    print(f"gradient: {gradient}")
    natural_grad = F_inverse * gradient
    # QHACK #

    return natural_grad
def parameter_shift(weights):
    """Compute the gradient of the variational circuit given by the
    ansatz function using the parameter-shift rule.

    Write your code below between the # QHACK # markers—create a device with
    the correct number of qubits, create a QNode that applies the above ansatz,
    and compute the gradient of the provided ansatz using the parameter-shift rule.

    Args:
        weights (array): An array of floating-point numbers with size (2, 3).

    Returns:
        array: The gradient of the variational circuit. The shape should match
        the input weights array.
    """
    dev = qml.device("default.qubit", wires=3)

    @qml.qnode(dev, diff_method="parameter-shift")
    def circuit(weights):
        # for i in range(len(weights)):
        #     qml.RX(weights[i, 0], wires=0)
        #     qml.RY(weights[i, 1], wires=1)
        #     qml.RZ(weights[i, 2], wires=2)
        #
        #     qml.CNOT(wires=[0, 1])
        #     qml.CNOT(wires=[1, 2])
        #     qml.CNOT(wires=[2, 0])

        qml.RX(weights[0], wires=0)
        qml.RY(weights[1], wires=1)
        qml.RZ(weights[2], wires=2)

        qml.CNOT(wires=[0, 1])
        qml.CNOT(wires=[1, 2])
        qml.CNOT(wires=[2, 0])

        qml.RX(weights[3], wires=0)
        qml.RY(weights[4], wires=1)
        qml.RZ(weights[5], wires=2)

        qml.CNOT(wires=[0, 1])
        qml.CNOT(wires=[1, 2])
        qml.CNOT(wires=[2, 0])

        return qml.expval(qml.PauliY(0) @ qml.PauliZ(2))

    gradient = np.zeros_like(weights)

    # QHACK #
    def apply_parameter_shift(qnode, params, i):
        shifted = params.copy()
        shifted[i] += np.pi / 2
        forward = qnode(shifted)  # forward evaluation

        shifted[i] -= np.pi
        backward = qnode(shifted)  # backward evaluation

        return 0.5 * (forward - backward)

    params = []
    for weight_arr in weights:
        for val in weight_arr:
            params.append(float(val))

    gradient = [
        apply_parameter_shift(circuit, params, i) for i in range(len(params))
    ]

    # QHACK #

    return gradient
Ejemplo n.º 24
0
    def step(self, objective_fn, *args, **kwargs):
        """Update trainable arguments with one step of the optimizer.

        Args:
            objective_fn (function): the objective function for optimization
            *args: variable length argument list for objective function
            **kwargs: variable length of keyword arguments for the objective function

        Returns:
            list[array]: The new variable values :math:`x^{(t+1)}`.
            If single arg is provided, list[array] is replaced by array.
        """

        self.trainable_args = set()

        for index, arg in enumerate(args):
            if getattr(arg, "requires_grad", True):
                self.trainable_args |= {index}

        if self.s is None:
            # Number of shots per parameter
            self.s = [
                np.zeros_like(a, dtype=np.int64) + self.min_shots
                for i, a in enumerate(args)
                if i in self.trainable_args
            ]

        # keep track of the number of shots run
        s = np.concatenate([i.flatten() for i in self.s])
        self.max_shots = max(s)
        self.shots_used = int(2 * np.sum(s))
        self.total_shots_used += self.shots_used

        # compute the gradient, as well as the variance in the gradient,
        # using the number of shots determined by the array s.
        grads, grad_variances = self.compute_grad(objective_fn, args, kwargs)
        new_args = self.apply_grad(grads, args)

        if self.xi is None:
            self.chi = [np.zeros_like(g, dtype=np.float64) for g in grads]
            self.xi = [np.zeros_like(g, dtype=np.float64) for g in grads]

        # running average of the gradient
        self.chi = [self.mu * c + (1 - self.mu) * g for c, g in zip(self.chi, grads)]

        # running average of the gradient variance
        self.xi = [self.mu * x + (1 - self.mu) * v for x, v in zip(self.xi, grad_variances)]

        for idx, (c, x) in enumerate(zip(self.chi, self.xi)):
            xi = x / (1 - self.mu ** (self.k + 1))
            chi = c / (1 - self.mu ** (self.k + 1))

            # determine the new optimum shots distribution for the next
            # iteration of the optimizer
            s = np.ceil(
                (2 * self.lipschitz * self.stepsize * xi)
                / ((2 - self.lipschitz * self.stepsize) * (chi ** 2 + self.b * (self.mu ** self.k)))
            )

            # apply an upper and lower bound on the new shot distributions,
            # to avoid the number of shots reducing below min(2, min_shots),
            # or growing too significantly.
            gamma = (
                (self.stepsize - self.lipschitz * self.stepsize ** 2 / 2) * chi ** 2
                - xi * self.lipschitz * self.stepsize ** 2 / (2 * s)
            ) / s

            argmax_gamma = np.unravel_index(np.argmax(gamma), gamma.shape)
            smax = max(s[argmax_gamma], 2)
            self.s[idx] = np.squeeze(np.int64(np.clip(s, min(2, self.min_shots), smax)))

        self.k += 1

        # unwrap from list if one argument, cleaner return
        if len(new_args) == 1:
            return new_args[0]

        return new_args