def target_alignment( X, Y, kernel, assume_normalized_kernel=False, rescale_class_labels=True, ): """Kernel-target alignment between kernel and labels.""" K = qml.kernels.square_kernel_matrix( X, kernel, assume_normalized_kernel=assume_normalized_kernel, ) if rescale_class_labels: nplus = np.count_nonzero(np.array(Y) == 1) nminus = len(Y) - nplus _Y = np.array([y / nplus if y == 1 else y / nminus for y in Y]) else: _Y = np.array(Y) T = np.outer(_Y, _Y) inner_product = np.sum(K * T) norm = np.sqrt(np.sum(K * K) * np.sum(T * T)) inner_product = inner_product / norm return inner_product
def test_fock_density_matrix(self, tol): """Test that the FockDensityMatrix gate works correctly""" dm = np.outer(psi, psi.conj()) wires = [0] gate_name = "FockDensityMatrix" operation = qml.FockDensityMatrix cutoff_dim = 10 dev = qml.device("strawberryfields.fock", wires=2, cutoff_dim=cutoff_dim) sf_operation = dev._operation_map[gate_name] assert dev.supports_operation(gate_name) @qml.qnode(dev) def circuit(*args): qml.TwoModeSqueezing(0.1, 0, wires=[0, 1]) operation(*args, wires=wires) return qml.expval(qml.NumberOperator(0)), qml.expval(qml.NumberOperator(1)) res = circuit(dm) sf_res = SF_gate_reference(sf_operation, cutoff_dim, wires, dm) assert np.allclose(res, sf_res, atol=tol, rtol=0)
def model_cost(params, E_A, E_B, E_C, E_D): """Compute the model cost for relative parameters and given function data. Args: params (array[float]): Relative parameters at which to evaluate the model. E_A (float): Coefficients E^(A) in the model. E_B (array[float]): Coefficients E^(B) in the model. E_C (array[float]): Coefficients E^(C) in the model. E_D (array[float]): Coefficients E^(D) in the model. The lower left triangular part and diagonal must be 0. Returns: cost (float): The model cost at the relative parameters. """ A = np.prod(np.cos(0.5 * params)**2) # For the other terms we only compute the prefactor relative to A B_over_A = 2 * np.tan(0.5 * params) C_over_A = B_over_A**2 / 2 D_over_A = np.outer(B_over_A, B_over_A) all_terms_over_A = [ E_A, np.dot(E_B, B_over_A), np.dot(E_C, C_over_A), np.dot(B_over_A, E_D @ B_over_A), ] cost = A * np.sum(all_terms_over_A) return cost
def density_matrix(alpha): """Creates a density matrix from a pure state.""" # DO NOT MODIFY anything in this code block psi = alpha * np.array([1, 0], dtype=float) + np.sqrt(1 - alpha**2) * np.array( [0, 1], dtype=float ) psi = np.kron(psi, np.array([1, 0, 0, 0], dtype=float)) return np.outer(psi, np.conj(psi))
def mitigate_depolarizing_noise(K, num_wires, method, use_entries=None): r"""Estimate depolarizing noise rate(s) using on the diagonal entries of a kernel matrix and mitigate the noise, assuming a global depolarizing noise model. Args: K (array[float]): Noisy kernel matrix. num_wires (int): Number of wires/qubits of the quantum embedding kernel. method (``'single'`` | ``'average'`` | ``'split_channel'``): Strategy for mitigation * ``'single'``: An alias for ``'average'`` with ``len(use_entries)=1``. * ``'average'``: Estimate a global noise rate based on the average of the diagonal entries in ``use_entries``, which need to be measured on the quantum computer. * ``'split_channel'``: Estimate individual noise rates per embedding, requiring all diagonal entries to be measured on the quantum computer. use_entries (array[int]): Diagonal entries to use if method in ``['single', 'average']``. If ``None``, defaults to ``[0]`` (``'single'``) or ``range(len(K))`` (``'average'``). Returns: array[float]: Mitigated kernel matrix. Reference: This method is introduced in Section V in `arXiv:2105.02276 <https://arxiv.org/abs/2105.02276>`_. **Example:** For an example usage of ``mitigate_depolarizing_noise`` please refer to the `PennyLane demo on the kernel module <https://github.com/PennyLaneAI/qml/tree/master/demonstrations/tutorial_kernel_module.py>`_ or `the postprocessing demo for arXiv:2105.02276 <https://github.com/thubregtsen/qhack/blob/master/paper/post_processing_demo.py>`_. """ dim = 2**num_wires if method == "single": if use_entries is None: use_entries = (0, ) diagonal_element = K[use_entries[0], use_entries[0]] noise_rate = (1 - diagonal_element) * dim / (dim - 1) mitigated_matrix = (K - noise_rate / dim) / (1 - noise_rate) elif method == "average": if use_entries is None: diagonal_elements = np.diag(K) else: diagonal_elements = np.diag(K)[np.array(use_entries)] noise_rates = (1 - diagonal_elements) * dim / (dim - 1) mean_noise_rate = np.mean(noise_rates) mitigated_matrix = (K - mean_noise_rate / dim) / (1 - mean_noise_rate) elif method == "split_channel": eff_noise_rates = np.clip((1 - np.diag(K)) * dim / (dim - 1), 0.0, 1.0) noise_rates = 1 - np.sqrt(1 - eff_noise_rates) inverse_noise = (-np.outer(noise_rates, noise_rates) + noise_rates.reshape( (1, len(K))) + noise_rates.reshape((len(K), 1))) mitigated_matrix = (K - inverse_noise / dim) / (1 - inverse_noise) return mitigated_matrix
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))
def _reduced_row_echelon(binary_matrix): r"""Returns the reduced row echelon form (RREF) of a matrix in a binary finite field :math:`\mathbb{Z}_2`. Args: binary_matrix (array[int]): binary matrix representation of the Hamiltonian Returns: array[int]: reduced row-echelon form of the given `binary_matrix` **Example** >>> binary_matrix = np.array([[1, 0, 0, 0, 0, 1, 0, 0], ... [1, 0, 1, 0, 0, 0, 1, 0], ... [0, 0, 0, 1, 1, 0, 0, 1]]) >>> _reduced_row_echelon(binary_matrix) array([[1, 0, 0, 0, 0, 1, 0, 0], [0, 0, 1, 0, 0, 1, 1, 0], [0, 0, 0, 1, 1, 0, 0, 1]]) """ rref_mat = binary_matrix.copy() shape = rref_mat.shape icol = 0 for irow in range(shape[0]): while icol < shape[1] and not rref_mat[irow][icol]: # get the nonzero indices in the remainder of column icol non_zero_idx = rref_mat[irow:, icol].nonzero()[0] if len(non_zero_idx ) == 0: # if remainder of column icol is all zero icol += 1 else: # find value and index of largest element in remainder of column icol krow = irow + non_zero_idx[0] # swap rows krow and irow rref_mat[irow, icol:], rref_mat[krow, icol:] = ( rref_mat[krow, icol:].copy(), rref_mat[irow, icol:].copy(), ) if icol < shape[1] and rref_mat[irow][icol]: # store remainder right hand side columns of the pivot row irow rpvt_cols = rref_mat[irow, icol:].copy() # get the column icol and set its irow element to 0 to avoid XORing pivot row with itself currcol = rref_mat[:, icol].copy() currcol[irow] = 0 # XOR the right hand side of the pivot row irow with all of the other rows rref_mat[:, icol:] ^= np.outer(currcol, rpvt_cols) icol += 1 return rref_mat.astype(int)
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))))
def test_two_qubit_observable(self): """Tests expval for two-qubit observables """ self.logTestName() dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(x, target_observable=None): qml.RX(x[0], wires=0) qml.RY(x[1], wires=0) qml.RZ(x[2], wires=0) qml.CNOT(wires=[0, 1]) return qml.expval(qml.Hermitian(target_observable, wires=[0, 1])) target_state = 1 / np.sqrt(2) * np.array([1, 0, 0, 1]) target_herm_op = np.outer(target_state.conj(), target_state) weights = np.array([0.5, 0.1, 0.2]) expval = circuit(weights, target_observable=target_herm_op) self.assertAlmostEqual(expval, 0.590556, delta=self.tol)
def test_fock_density_matrix_unsupported(self, tol): """Test that the FockDensityMatrix gate is unsupported for the gaussian simulator""" dm = np.outer(psi, psi.conj()) wires = [0] gate_name = "FockDensityMatrix" operation = qml.FockDensityMatrix dev = qml.device("strawberryfields.gaussian", wires=2) @qml.qnode(dev) def circuit(*args): operation(*args, wires=wires) return qml.expval(qml.NumberOperator(0)), qml.expval(qml.NumberOperator(1)) with pytest.raises( qml.DeviceError, match="Gate {} not supported " "on device strawberryfields.gaussian".format(gate_name), ): circuit(dm)
def polarity( X, Y, kernel, assume_normalized_kernel=False, rescale_class_labels=True, normalize=False, ): r"""Polarity of a given kernel function. For a dataset with feature vectors :math:`\{x_i\}` and associated labels :math:`\{y_i\}`, the polarity of the kernel function :math:`k` is given by .. math :: \operatorname{P}(k) = \sum_{i,j=1}^n y_i y_j k(x_i, x_j) If the dataset is unbalanced, that is if the numbers of datapoints in the two classes :math:`n_+` and :math:`n_-` differ, ``rescale_class_labels=True`` will apply a rescaling according to :math:`\tilde{y}_i = \frac{y_i}{n_{y_i}}`. This is activated by default and only results in a prefactor that depends on the size of the dataset for balanced datasets. The keyword argument ``assume_normalized_kernel`` is passed to :func:`~.kernels.square_kernel_matrix`, for the computation :func:`~.utils.frobenius_inner_product` is used. Args: X (list[datapoint]): List of datapoints. Y (list[float]): List of class labels of datapoints, assumed to be either -1 or 1. kernel ((datapoint, datapoint) -> float): Kernel function that maps datapoints to kernel value. assume_normalized_kernel (bool, optional): Assume that the kernel is normalized, i.e. the kernel evaluates to 1 when both arguments are the same datapoint. rescale_class_labels (bool, optional): Rescale the class labels. This is important to take care of unbalanced datasets. normalize (bool): If True, rescale the polarity to the target_alignment. Returns: float: The kernel polarity. **Example:** Consider a simple kernel function based on :class:`~.templates.embeddings.AngleEmbedding`: .. code-block :: python dev = qml.device('default.qubit', wires=2, shots=None) @qml.qnode(dev) def circuit(x1, x2): qml.templates.AngleEmbedding(x1, wires=dev.wires) qml.adjoint(qml.templates.AngleEmbedding)(x2, wires=dev.wires) return qml.probs(wires=dev.wires) kernel = lambda x1, x2: circuit(x1, x2)[0] We can then compute the polarity on a set of 4 (random) feature vectors ``X`` with labels ``Y`` via >>> X = np.random.random((4, 2)) >>> Y = np.array([-1, -1, 1, 1]) >>> qml.kernels.polarity(X, Y, kernel) tensor(0.04361349, requires_grad=True) """ K = square_kernel_matrix(X, kernel, assume_normalized_kernel=assume_normalized_kernel) if rescale_class_labels: nplus = np.count_nonzero(np.array(Y) == 1) nminus = len(Y) - nplus _Y = np.array([y / nplus if y == 1 else y / nminus for y in Y]) else: _Y = np.array(Y) T = np.outer(_Y, _Y) return frobenius_inner_product(K, T, normalize=normalize)
def test_supported_fock_gates(self): """Test that all supported gates work correctly""" self.logTestName() cutoff_dim = 10 a = 0.312 b = 0.123 c = 0.532 d = 0.124 dev = qml.device('strawberryfields.fock', wires=2, cutoff_dim=cutoff_dim) gates = list(dev._operation_map.items()) for g, sfop in gates: log.info('\tTesting gate {}...'.format(g)) self.assertTrue(dev.supports_operation(g)) op = getattr(qml.ops, g) if g == "Interferometer": wires = [0, 1] elif op.num_wires is (qml.operation.Wires.Any or qml.operation.Wires.All): wires = [0] else: wires = list(range(op.num_wires)) @qml.qnode(dev) def circuit(*args): qml.TwoModeSqueezing(0.1, 0, wires=[0, 1]) op(*args, wires=wires) return qml.expval(qml.NumberOperator(0)), qml.expval( qml.NumberOperator(1)) # compare to reference SF engine def SF_reference(*args): """SF reference circuit""" eng = sf.Engine("fock", backend_options={"cutoff_dim": cutoff_dim}) prog = sf.Program(2) with prog.context as q: sf.ops.S2gate(0.1) | q sfop(*args) | [q[i] for i in wires] state = eng.run(prog).state return state.mean_photon(0)[0], state.mean_photon(1)[0] if g == 'GaussianState': r = np.array([0, 0]) V = np.array([[0.5, 0], [0, 2]]) self.assertAllEqual(circuit(V, r), SF_reference(V, r)) elif g == 'Interferometer': self.assertAllEqual(circuit(U), SF_reference(U)) elif g == 'FockDensityMatrix': dm = np.outer(psi, psi.conj()) self.assertAllEqual(circuit(dm), SF_reference(dm)) elif g == 'FockStateVector': self.assertAllEqual(circuit(psi), SF_reference(psi)) elif g == 'FockState': self.assertAllEqual(circuit(1), SF_reference(1)) elif g == "DisplacedSqueezedState": self.assertAllEqual(circuit(a, b, c, d), SF_reference(a * np.exp(1j * b), c, d)) elif g == "CatState": self.assertAllEqual(circuit(a, b, c), SF_reference(a * np.exp(1j * b), c)) elif op.num_params == 1: self.assertAllEqual(circuit(a), SF_reference(a)) elif op.num_params == 2: self.assertAllEqual(circuit(a, b), SF_reference(a, b))
# + tags=[] # Merge matrix sets kernel_matrices = {} for bnr in noise_probabilities: sub_fn = f"{sub_filename.split('.')[0]}_{float(bnr)}_{str([0]+shot_numbers).replace(' ', '').replace(',', '_')}_{num_reps}.dill" try: sub_mats = load(open(sub_fn, 'rb+')) kernel_matrices.update(sub_mats) except: print(sub_fn) # pure_np_kernel_matrices = {key: pure_np.asarray(mat) for key, mat in kernel_matrices.items()} # dump(pure_np_kernel_matrices, open(filename, 'wb+')) # + tags=[] target = np.outer(y_train, y_train) TA = {} A = {} no_noise_mat = kernel_matrices[(0.0, 0)] for bnr in noise_probabilities: no_shot_mat = kernel_matrices[(bnr, 0)] for shots in shot_numbers: these_mats = [kernel_matrices[(bnr, shots, i)] for i in range(num_reps)] these_TA = [qml.math.frobenius_inner_product(mat, target, normalize=True) for mat in these_mats] these_A = [qml.math.frobenius_inner_product(mat, no_noise_mat, normalize=True) for mat in these_mats] TA[(bnr, shots)] = qml.math.stack(these_TA) A[(bnr, shots)] = qml.math.stack(these_A) TA[(bnr, 0)] = qml.math.frobenius_inner_product(no_shot_mat, target, normalize=True) A[(bnr, 0)] = qml.math.frobenius_inner_product(no_shot_mat, no_noise_mat, normalize=True) # + tags=[]
import pennylane as qml from pennylane import numpy as np from tqdm.notebook import tqdm # ## Pennylane version # # Define the device `default.qubit` and a circuit where one layer contains a general rotation $R(\phi, \theta, \omega) = R_z(\omega)R_x(\theta)R_z(\phi)$ on each qubit, followed by entangling gates. We apply 2 layers. The $R(\phi, \theta, \omega)$ gate is a native in pennylane `qml.Rot()`. We use 4 qubits. # In[2]: dev1 = qml.device("default.qubit", wires=4) # In[3]: target_state = np.ones(2**4) / np.sqrt(2**4) density = np.outer(target_state, target_state) @qml.qnode(dev1) def circuit(params): for j in range(2): # 2 layers for i in range(4): # 4 qubits qml.Rot(*params[j][i], wires=i) qml.CNOT(wires=[0, 1]) qml.CNOT(wires=[2, 3]) qml.CNOT(wires=[1, 2]) return qml.expval(qml.Hermitian(density, wires=[0, 1, 2, 3])) # Define a cost function. In our case we want the overlap of the circuit output to be maximal with the targe_state. Therefore we minimize $1-\frac{1}{\sqrt{|\Sigma|}}\sum_{\sigma_i}\langle \sigma_i | V(\theta) | 0000\rangle$
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)