Spanish
Idiomas
English
Bengali
French
German
Japanese
Korean
Portuguese
Spanish
Tamil

Código fuente para qiskit.transpiler.synthesis.aqc.cnot_unit_objective

# This code is part of Qiskit.
#
# (C) Copyright IBM 2021.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""
A definition of the approximate circuit compilation optimization problem based on CNOT unit
definition.
"""
from abc import ABC

import numpy as np
from numpy import linalg as la

from .approximate import ApproximatingObjective
from .elementary_operations import ry_matrix, rz_matrix, place_unitary, place_cnot, rx_matrix


[documentos]class CNOTUnitObjective(ApproximatingObjective, ABC): """ A base class for a problem definition based on CNOT unit. This class may have different subclasses for objective and gradient computations. """ def __init__(self, num_qubits: int, cnots: np.ndarray) -> None: """ Args: num_qubits: number of qubits. cnots: a CNOT structure to be used in the optimization procedure. """ super().__init__() self._num_qubits = num_qubits self._cnots = cnots self._num_cnots = cnots.shape[1] @property def num_cnots(self): """ Returns: A number of CNOT units to be used by the approximate circuit. """ return self._num_cnots @property def num_thetas(self): """ Returns: Number of parameters (angles) of rotation gates in this circuit. """ return 3 * self._num_qubits + 4 * self._num_cnots
[documentos]class DefaultCNOTUnitObjective(CNOTUnitObjective): """A naive implementation of the objective function based on CNOT units.""" def __init__(self, num_qubits: int, cnots: np.ndarray) -> None: """ Args: num_qubits: number of qubits. cnots: a CNOT structure to be used in the optimization procedure. """ super().__init__(num_qubits, cnots) # last objective computations to be re-used by gradient self._last_thetas = None self._cnot_right_collection = None self._cnot_left_collection = None self._rotation_matrix = None self._cnot_matrix = None
[documentos] def objective(self, param_values: np.ndarray) -> float: # rename parameters just to make shorter and make use of our dictionary thetas = param_values n = self._num_qubits d = int(2**n) cnots = self._cnots num_cnots = self.num_cnots # to save intermediate computations we define the following matrices # this is the collection of cnot unit matrices ordered from left to # right as in the circuit, not matrix product cnot_unit_collection = np.zeros((d, d * num_cnots), dtype=complex) # this is the collection of matrix products of the cnot units up # to the given position from the right of the circuit cnot_right_collection = np.zeros((d, d * num_cnots), dtype=complex) # this is the collection of matrix products of the cnot units up # to the given position from the left of the circuit cnot_left_collection = np.zeros((d, d * num_cnots), dtype=complex) # first, we construct each cnot unit matrix for cnot_index in range(num_cnots): theta_index = 4 * cnot_index # cnot qubit indices for the cnot unit identified by cnot_index q1 = int(cnots[0, cnot_index]) q2 = int(cnots[1, cnot_index]) # rotations that are applied on the q1 qubit ry1 = ry_matrix(thetas[0 + theta_index]) rz1 = rz_matrix(thetas[1 + theta_index]) # rotations that are applied on the q2 qubit ry2 = ry_matrix(thetas[2 + theta_index]) rx2 = rx_matrix(thetas[3 + theta_index]) # combine the rotations on qubits q1 and q2 single_q1 = np.dot(rz1, ry1) single_q2 = np.dot(rx2, ry2) # we place single qubit matrices at the corresponding locations in the (2^n, 2^n) matrix full_q1 = place_unitary(single_q1, n, q1) full_q2 = place_unitary(single_q2, n, q2) # we place a cnot matrix at the qubits q1 and q2 in the full matrix cnot_q1q2 = place_cnot(n, q1, q2) # compute the cnot unit matrix and store in cnot_unit_collection cnot_unit_collection[:, d * cnot_index : d * (cnot_index + 1)] = la.multi_dot( [full_q2, full_q1, cnot_q1q2] ) # this is the matrix corresponding to the intermediate matrix products # it will end up being the matrix product of all the cnot unit matrices # first we multiply from the right-hand side of the circuit cnot_matrix = np.eye(d) for cnot_index in range(num_cnots - 1, -1, -1): cnot_matrix = np.dot( cnot_matrix, cnot_unit_collection[:, d * cnot_index : d * (cnot_index + 1)] ) cnot_right_collection[:, d * cnot_index : d * (cnot_index + 1)] = cnot_matrix # now we multiply from the left-hand side of the circuit cnot_matrix = np.eye(d) for cnot_index in range(num_cnots): cnot_matrix = np.dot( cnot_unit_collection[:, d * cnot_index : d * (cnot_index + 1)], cnot_matrix ) cnot_left_collection[:, d * cnot_index : d * (cnot_index + 1)] = cnot_matrix # this is the matrix corresponding to the initial rotations # we start with 1 and kronecker product each qubit's rotations rotation_matrix = 1 for q in range(n): theta_index = 4 * num_cnots + 3 * q rz0 = rz_matrix(thetas[0 + theta_index]) ry1 = ry_matrix(thetas[1 + theta_index]) rz2 = rz_matrix(thetas[2 + theta_index]) rotation_matrix = np.kron(rotation_matrix, la.multi_dot([rz0, ry1, rz2])) # the matrix corresponding to the full circuit is the cnot part and # rotation part multiplied together circuit_matrix = np.dot(cnot_matrix, rotation_matrix) # compute error error = 0.5 * (la.norm(circuit_matrix - self._target_matrix, "fro") ** 2) # cache computations for gradient self._last_thetas = thetas self._cnot_left_collection = cnot_left_collection self._cnot_right_collection = cnot_right_collection self._rotation_matrix = rotation_matrix self._cnot_matrix = cnot_matrix return error
[documentos] def gradient(self, param_values: np.ndarray) -> np.ndarray: # just to make shorter thetas = param_values # if given thetas are the same as used at the previous objective computations, then # we re-use computations, otherwise we have to re-compute objective if not np.all(np.isclose(thetas, self._last_thetas)): self.objective(thetas) # the partial derivative of the circuit with respect to an angle # is the same circuit with the corresponding pauli gate, multiplied # by a global phase of -1j / 2, next to the rotation gate (it commutes) pauli_x = np.multiply(-1j / 2, np.asarray([[0, 1], [1, 0]])) pauli_y = np.multiply(-1j / 2, np.asarray([[0, -1j], [1j, 0]])) pauli_z = np.multiply(-1j / 2, np.asarray([[1, 0], [0, -1]])) n = self._num_qubits d = int(2**n) cnots = self._cnots num_cnots = self.num_cnots # the partial derivative of the cost function is -Re<V',U> # where V' is the partial derivative of the circuit # first we compute the partial derivatives in the cnot part der = np.zeros(4 * num_cnots + 3 * n) for cnot_index in range(num_cnots): theta_index = 4 * cnot_index # cnot qubit indices for the cnot unit identified by cnot_index q1 = int(cnots[0, cnot_index]) q2 = int(cnots[1, cnot_index]) # rotations that are applied on the q1 qubit ry1 = ry_matrix(thetas[0 + theta_index]) rz1 = rz_matrix(thetas[1 + theta_index]) # rotations that are applied on the q2 qubit ry2 = ry_matrix(thetas[2 + theta_index]) rx2 = rx_matrix(thetas[3 + theta_index]) # combine the rotations on qubits q1 and q2 # note we have to insert an extra pauli gate to take the derivative # of the appropriate rotation gate for i in range(4): if i == 0: single_q1 = la.multi_dot([rz1, pauli_y, ry1]) single_q2 = np.dot(rx2, ry2) elif i == 1: single_q1 = la.multi_dot([pauli_z, rz1, ry1]) single_q2 = np.dot(rx2, ry2) elif i == 2: single_q1 = np.dot(rz1, ry1) single_q2 = la.multi_dot([rx2, pauli_y, ry2]) else: single_q1 = np.dot(rz1, ry1) single_q2 = la.multi_dot([pauli_x, rx2, ry2]) # we place single qubit matrices at the corresponding locations in # the (2^n, 2^n) matrix full_q1 = place_unitary(single_q1, n, q1) full_q2 = place_unitary(single_q2, n, q2) # we place a cnot matrix at the qubits q1 and q2 in the full matrix cnot_q1q2 = place_cnot(n, q1, q2) # partial derivative of that particular cnot unit, size of (2^n, 2^n) der_cnot_unit = la.multi_dot([full_q2, full_q1, cnot_q1q2]) # der_cnot_unit is multiplied by the matrix product of cnot units to the left # of it (if there are any) and to the right of it (if there are any) if cnot_index == 0: der_cnot_matrix = np.dot( self._cnot_right_collection[:, d : 2 * d], der_cnot_unit, ) elif num_cnots - 1 == cnot_index: der_cnot_matrix = np.dot( der_cnot_unit, self._cnot_left_collection[:, d * (num_cnots - 2) : d * (num_cnots - 1)], ) else: der_cnot_matrix = la.multi_dot( [ self._cnot_right_collection[ :, d * (cnot_index + 1) : d * (cnot_index + 2) ], der_cnot_unit, self._cnot_left_collection[:, d * (cnot_index - 1) : d * cnot_index], ] ) # the matrix corresponding to the full circuit partial derivative # is the partial derivative of the cnot part multiplied by the usual # rotation part der_circuit_matrix = np.dot(der_cnot_matrix, self._rotation_matrix) # we compute the partial derivative of the cost function der[i + theta_index] = -np.real( np.trace(np.dot(der_circuit_matrix.conj().T, self._target_matrix)) ) # now we compute the partial derivatives in the rotation part # we start with 1 and kronecker product each qubit's rotations for i in range(3 * n): der_rotation_matrix = 1 for q in range(n): theta_index = 4 * num_cnots + 3 * q rz0 = rz_matrix(thetas[0 + theta_index]) ry1 = ry_matrix(thetas[1 + theta_index]) rz2 = rz_matrix(thetas[2 + theta_index]) # for the appropriate rotation gate that we are taking # the partial derivative of, we have to insert the # corresponding pauli matrix if i - 3 * q == 0: rz0 = np.dot(pauli_z, rz0) elif i - 3 * q == 1: ry1 = np.dot(pauli_y, ry1) elif i - 3 * q == 2: rz2 = np.dot(pauli_z, rz2) der_rotation_matrix = np.kron(der_rotation_matrix, la.multi_dot([rz0, ry1, rz2])) # the matrix corresponding to the full circuit partial derivative # is the usual cnot part multiplied by the partial derivative of # the rotation part der_circuit_matrix = np.dot(self._cnot_matrix, der_rotation_matrix) # we compute the partial derivative of the cost function der[4 * num_cnots + i] = -np.real( np.trace(np.dot(der_circuit_matrix.conj().T, self._target_matrix)) ) return der