def density_matrix(state):
    """Calculates the density matrix representation of a state.

    Args:
        state (array[complex]): array representing a quantum state vector

    Returns:
        dm: (array[complex]): array representing the density matrix
    """
    return state * np.conj(state).T
Exemplo n.º 2
0
 def BuildDensityBasisState(self, params, num=0):
     '''
     Build density matrix associated
     to params and initial basis state
     '''
     # Get device state
     self.ThermalQNode(params, i=Dec2nbitBin(num, self.num_spins))
     state = self.device.state
     # Return density matrix
     return np.outer(state, np.conj(state))
Exemplo n.º 3
0
 def squared_term(a, r, phi):
     """Analytic expression for <N^2>"""
     magnitude_squared = np.abs(a)**2
     squared_term = (
         -magnitude_squared + magnitude_squared**2 +
         2 * magnitude_squared * np.cosh(2 * r) -
         np.exp(-1j * phi) * a**2 * np.cosh(r) * np.sinh(r) -
         np.exp(1j * phi) * np.conj(a)**2 * np.cosh(r) * np.sinh(r) +
         np.sinh(r)**4 + np.cosh(r) * np.sinh(r) * np.sinh(2 * r))
     return squared_term
def fidelity(state1, state2):
    """
    Calculates the fidelity between two state vectors

    Args:
        state1 (array[float]): State vector representation
        state2 (array[float]): State vector representation

    Returns:
        float: fidelity between `state1` and `state2`
    """
    return np.abs(np.dot(np.conj(state1), state2))
Exemplo n.º 5
0
def matrix_norm(mixed_state, pure_state):
    """Computes the matrix one-norm of the difference between mixed and pure states.

    Args:
        - mixed_state (np.tensor): A density matrix
        - pure_state (np.tensor): A pure state

    Returns:
        - (float): The matrix one-norm
    """

    return np.sum(np.abs(mixed_state - np.outer(pure_state, np.conj(pure_state))))
Exemplo n.º 6
0
def cost_loc(weights):
    """Local version of the cost function, which tends to zero when A |x> is proportional to |b>."""
    mu_sum = 0.0

    for l in range(0, len(c)):
        for lp in range(0, len(c)):
            for j in range(0, n_qubits):
                mu_sum = mu_sum + c[l] * np.conj(c[lp]) * mu(weights, l, lp, j)

    mu_sum = abs(mu_sum)

    # Cost function C_L
    return 0.5 - 0.5 * mu_sum / (n_qubits * psi_norm(weights))
Exemplo n.º 7
0
    def test_correct_state(self, tol):
        """Test that the device state is correct after applying a
        quantum function on the device"""

        dev = qml.device("default.qubit.autograd", wires=2)

        state = dev.state
        expected = np.array([1, 0, 0, 0])
        assert np.allclose(state, expected, atol=tol, rtol=0)

        @qml.qnode(dev, interface="autograd", diff_method="backprop")
        def circuit():
            qml.Hadamard(wires=0)
            qml.RZ(np.pi / 4, wires=0)
            return qml.expval(qml.PauliZ(0))

        circuit()
        state = dev.state

        amplitude = np.exp(-1j * np.pi / 8) / np.sqrt(2)

        expected = np.array([amplitude, 0, np.conj(amplitude), 0])
        assert np.allclose(state, expected, atol=tol, rtol=0)
Exemplo n.º 8
0
def fourier_function(freq_dict, x):
    r"""Function of the form :math:`\sum c_n e^{inx}` to construct
    and evaluate a Fourier series given
    a dictionary of frequencies and their coefficients.

    Args:
        freq_dict (dict[int, float]): Pairs of positive integer frequencies and corresponding
            Fourier coefficients.

    Returns:
        float: output of the function
    """
    result = 0

    # Handle 0 coefficient separately
    if 0 in freq_dict.keys():
        result = freq_dict[0]

    result += sum([
        coeff * np.exp(1j * freq * x) + np.conj(coeff) * np.exp(-1j * freq * x)
        for freq, coeff in freq_dict.items() if freq != 0
    ])
    return result.real
    def mt(*params):
        state = qnode(*params)
        rqnode = lambda *params: np.real(qnode(*params))
        iqnode = lambda *params: np.imag(qnode(*params))
        rjac = qml.jacobian(rqnode)(*params)
        ijac = qml.jacobian(iqnode)(*params)

        if isinstance(rjac, tuple):
            out = []
            for rc, ic in zip(rjac, ijac):
                c = rc + 1j * ic
                psidpsi = np.tensordot(np.conj(state), c, axes=([0], [0]))
                out.append(
                    np.real(
                        np.tensordot(np.conj(c), c, axes=([0], [0])) -
                        np.tensordot(np.conj(psidpsi), psidpsi, axes=0)))
            return tuple(out)

        jac = rjac + 1j * ijac
        psidpsi = np.tensordot(np.conj(state), jac, axes=([0], [0]))
        return np.real(
            np.tensordot(np.conj(jac), jac, axes=([0], [0])) -
            np.tensordot(np.conj(psidpsi), psidpsi, axes=0))
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 #
    gradient = np.zeros([6], dtype=np.float64)

    for i in range(6):
        params[i] += 0.1
        forward = qnode(params)
        params[i] -= 0.2
        backward = qnode(params)
        params[i] += 0.1
        gradient[i] = (forward - backward) / (2 * np.sin(0.1))

    @qml.qnode(dev)
    def fubini_qnode(params):
        variational_circuit(params)
        return qml.state()

    fubini = np.zeros([6, 6], dtype=np.float64)

    base = np.conj(fubini_qnode(params))

    def fubini_calculate():
        for i in range(6):
            for j in range(6):
                params[i] += np.pi / 2
                params[j] += np.pi / 2
                plusplus = np.abs(np.dot(base, fubini_qnode(params)))**2
                params[j] -= np.pi
                plusminus = np.abs(np.dot(base, fubini_qnode(params)))**2
                params[i] -= np.pi
                minusminus = np.abs(np.dot(base, fubini_qnode(params)))**2
                params[j] += np.pi
                minusplus = np.abs(np.dot(base, fubini_qnode(params)))**2
                fubini[i, j] = (-plusplus - minusminus + plusminus +
                                minusplus) / 8
                params[i] += np.pi / 2
                params[j] -= np.pi / 2

    fubini_calculate()

    natural_grad = np.matmul(np.linalg.inv(fubini), gradient)
    # QHACK #

    return natural_grad
Exemplo n.º 11
0
def test_conj(t):
    res = fn.conj(t)
    assert fn.allequal(res, np.conj(t))
Exemplo n.º 12
0
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 #
    s = 0.1
    gradient = np.zeros([6], dtype=np.float64)

    for i in range(6):
        params[i] += s
        plus_s = qnode(params)
        params[i] -= 2 * s
        minus_s = qnode(params)
        gradient[i] = (plus_s - minus_s) / (2 * np.sin(s))
        params[i] += s

    @qml.qnode(dev)
    def my_qnode(params):
        variational_circuit(params)
        return qml.state()

    s = np.pi / 2

    ket_state = my_qnode(params)
    bra_state = np.conj(ket_state)

    F = np.zeros([6, 6], dtype=np.float64)

    def get_F(qnode, params):
        for i in range(6):
            for j in range(6):
                params[i] += s
                params[j] += s
                pp_state = qnode(params)
                pp = np.abs(np.dot(bra_state, pp_state))**2
                params[j] -= 2 * s
                pm_state = qnode(params)
                pm = np.abs(np.dot(bra_state, pm_state))**2
                params[i] -= 2 * s
                params[j] += 2 * s
                mp_state = qnode(params)
                mp = np.abs(np.dot(bra_state, mp_state))**2
                params[j] -= 2 * s
                mm_state = qnode(params)
                mm = np.abs(np.dot(bra_state, mm_state))**2
                F[i, j] = (-pp + pm + mp - mm) / 8
                params[i] += s
                params[j] += s

    get_F(my_qnode, params)

    natural_grad = np.matmul(np.linalg.inv(F), gradient)
    # QHACK #

    return natural_grad
Exemplo n.º 13
0
class AutogradBox(qml.math.TensorBox):
    """Implements the :class:`~.TensorBox` API for ``pennylane.numpy`` tensors.

    For more details, please refer to the :class:`~.TensorBox` documentation.
    """

    abs = wrap_output(lambda self: np.abs(self.data))
    angle = wrap_output(lambda self: np.angle(self.data))
    arcsin = wrap_output(lambda self: np.arcsin(self.data))
    cast = wrap_output(lambda self, dtype: np.tensor(self.data, dtype=dtype))
    conj = wrap_output(lambda self: np.conj(self.data))
    diag = staticmethod(wrap_output(lambda values, k=0: np.diag(values, k=k)))
    expand_dims = wrap_output(lambda self, axis: np.expand_dims(self.data, axis=axis))
    gather = wrap_output(lambda self, indices: self.data[indices])
    ones_like = wrap_output(lambda self: np.ones_like(self.data))
    reshape = wrap_output(lambda self, shape: np.reshape(self.data, shape))
    sqrt = wrap_output(lambda self: np.sqrt(self.data))
    sum = wrap_output(
        lambda self, axis=None, keepdims=False: np.sum(self.data, axis=axis, keepdims=keepdims)
    )
    T = wrap_output(lambda self: self.data.T)
    squeeze = wrap_output(lambda self: self.data.squeeze())

    @staticmethod
    def astensor(tensor):
        return np.tensor(tensor)

    @staticmethod
    @wrap_output
    def block_diag(values):
        return block_diag(*AutogradBox.unbox_list(values))

    @staticmethod
    @wrap_output
    def concatenate(values, axis=0):
        return np.concatenate(AutogradBox.unbox_list(values), axis=axis)

    @staticmethod
    @wrap_output
    def dot(x, y):
        x, y = AutogradBox.unbox_list([x, y])

        if x.ndim == 0 and y.ndim == 0:
            return x * y

        if x.ndim == 2 and y.ndim == 2:
            return x @ y

        return np.dot(x, y)

    @property
    def interface(self):
        return "autograd"

    def numpy(self):
        if hasattr(self.data, "_value"):
            # Catches the edge case where the data is an Autograd arraybox,
            # which only occurs during backpropagation.
            return self.data._value

        return self.data.numpy()

    @property
    def requires_grad(self):
        return self.data.requires_grad

    @wrap_output
    def scatter_element_add(self, index, value):
        size = self.data.size
        flat_index = np.ravel_multi_index(index, self.shape)
        t = [0] * size
        t[flat_index] = value
        self.data = self.data + np.array(t).reshape(self.shape)
        return self.data

    @property
    def shape(self):
        return self.data.shape

    @staticmethod
    @wrap_output
    def stack(values, axis=0):
        return np.stack(AutogradBox.unbox_list(values), axis=axis)

    @wrap_output
    def take(self, indices, axis=None):
        indices = self.astensor(indices)

        if axis is None:
            return self.data.flatten()[indices]

        fancy_indices = [slice(None)] * axis + [indices]
        return self.data[tuple(fancy_indices)]

    @staticmethod
    @wrap_output
    def where(condition, x, y):
        return np.where(condition, *AutogradBox.unbox_list([x, y]))
Exemplo n.º 14
0
def density_matrix(state):
    """  Calculates the density matrix representation of a state (a state is a complex vector
         in the canonical basis representation). The density matrix is a Hermitian operator.
    """
    return np.outer(np.conj(state), state)
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 #
    import math

    # Create qnode for quantum state

    gradient = np.zeros([len(params)], dtype=np.float64)
    behaviour_matrix = np.zeros([len(params), len(params)], dtype=np.float64)

    # Obtain gradient
    def parameter_shift_term(qnode, params, shift_value, index):
        shifted = params.copy()
        shifted[index] += shift_value
        forward = qnode(shifted)  # forward evaluation

        shifted[index] -= shift_value*2
        backward = qnode(shifted)  # backward evaluation

        return (forward - backward)/(2*math.sin(shift_value))

    for i in range(len(params)):
        gradient[i] = parameter_shift_term(qnode, params, math.pi/2, i)

    # print(gradient)

    # Metric tensor

    # First of all, reset state
    dev.reset()

    @qml.qnode(dev)
    def circuit(params):
        variational_circuit(params)
        return qml.state()

    # Execution with original params
    original_params_state = np.conj(circuit(params))
    dev.reset()

    def shifter(params, shift_value, row, col, signs):
        r""" This function executes the shift needed for the Hessian calculation.

        Args:
            row (int): First index of the shift
            col (int): Second index of the shift
            signs (tuple): Tuple with signs (+1,-1) os the shift to be made

        Returns: 
            array: Shifted params 
        """
        shifted = params.copy()
        shifted[row] += shift_value*signs[0]
        shifted[col] += shift_value*signs[1]

        return shifted

    def magnitude_calculator(bra, ket):
        return pow(np.abs(np.inner(bra, ket)), 2)

    def parameter_behave_terms(qnode, params, shift_value, row, col):
        # Reference for future review: https://arxiv.org/pdf/2008.06517.pdf
        dev.reset()
        qnode(shifter(params, shift_value, row, col, [1, 1]))
        step_1 = magnitude_calculator(original_params_state, dev.state)

        dev.reset()
        qnode(shifter(params, shift_value, row, col, [1, -1]))
        step_2 = magnitude_calculator(original_params_state, dev.state)

        dev.reset()
        qnode(shifter(params, shift_value, row, col, [-1, 1]))
        step_3 = magnitude_calculator(original_params_state, dev.state)

        dev.reset()
        qnode(shifter(params, shift_value, row, col, [-1, -1]))
        step_4 = magnitude_calculator(original_params_state, dev.state)

        return (-step_1 + step_2 + step_3 - step_4)/8

    for i in range(len(params)):
        for j in range(i, len(params)):
            behaviour_matrix[i][j] = parameter_behave_terms(
                circuit, params, math.pi/2, i, j)
            behaviour_matrix[j][i] = behaviour_matrix[i][j]

    # print(behaviour_matrix)

    # qml.metric_tenser return a block diagonal matrix.
    # Can be used for comparison, but it is not to be used for this task
    #print(np.round(qml.metric_tensor(qnode)(params), 8))

    inv_behaviour_matrix = np.linalg.inv(behaviour_matrix)
    # print(inv_behaviour_matrix)

    natural_grad = inv_behaviour_matrix.dot(gradient)

    # QHACK #

    return natural_grad