def test_global_phase(self, num_ctrl_qubits): """test global phase""" mat1 = np.array([[0, 1], [1, 0]], dtype=float) mat2 = np.exp(1j) * mat1 gate1 = UnitaryGate(mat1) gate2 = UnitaryGate(mat2) cgate1 = gate1.q_if(num_ctrl_qubits) cgate2 = gate2.q_if(num_ctrl_qubits) cop_mat1 = _compute_control_matrix(mat1, num_ctrl_qubits) cop_mat2 = _compute_control_matrix(mat2, num_ctrl_qubits, phase=1) self.assertTrue(is_unitary_matrix(mat1)) self.assertTrue(is_unitary_matrix(mat2)) self.assertTrue(is_unitary_matrix(cop_mat1)) self.assertTrue(is_unitary_matrix(cop_mat2)) self.assertTrue(Operator(cgate1).equiv(Operator(cgate2))) self.assertTrue(matrix_equal(cop_mat1, cop_mat2, ignore_phase=True))
def get_stinespring_unitary(choi, target_choi, target_unitary, n_qubits): """ get the Stinespring dilation unitary given the Choi matrix choi""" unitary = Stinespring(Choi(choi)).data # this fixes some numerical issues if np.linalg.norm(choi.data - target_choi) < 1e-8: unitary = target_unitary if type(unitary) == tuple: assert np.linalg.norm(unitary[0] - unitary[1]) < 1e-8 unitary = unitary[0] assert unitary.shape[1] == 2**n_qubits # increase hilbert space size to be a power of two next_pow_2 = int(np.power(2, np.ceil(np.log(unitary.shape[0]) / np.log(2)))) if unitary.shape[0] != next_pow_2: diff = next_pow_2 - unitary.shape[0] unitary = np.vstack([unitary, np.zeros((diff, 2**n_qubits))]) assert unitary.shape[0] == next_pow_2 assert unitary.shape[1] == 2**n_qubits num_anc = int(np.log2(unitary.shape[0])) - n_qubits completed = complete_unitary(unitary) # due to numerical issues, the matrix is not always perfectly unitary if not is_unitary_matrix(completed, atol=1e-12): completed, _ = scipy.linalg.polar(completed) return completed, num_anc
def make_unitary_instruction(mat, qubits, standard_gates=True): """Return a qobj instruction for a unitary matrix gate. Args: mat (matrix): A square or diagonal unitary matrix. qubits (list[int]): The qubits the matrix is applied to. standard_gates (bool): Check if the matrix instruction is a standard instruction. Returns: dict: The qobj instruction object. Raises: NoiseError: if the input is not a unitary matrix. """ warnings.warn( 'make_unitary_instruction has been deprecated as of qiskit-aer 0.10.0' ' and will be removed no earlier than 3 months from that release date.', DeprecationWarning, stacklevel=2) if not is_unitary_matrix(mat): raise NoiseError("Input matrix is not unitary.") if isinstance(qubits, int): qubits = [qubits] instruction = {"name": "unitary", "qubits": qubits, "params": [mat]} if standard_gates: return standard_gate_instruction(instruction) else: return [instruction]
def __init__(self, data, label=None): """Create a gate from a numeric unitary matrix. Args: data (matrix or Operator): unitary operator. label (str): unitary name for backend [Default: None]. Raises: ExtensionError: if input data is not an N-qubit unitary operator. """ if hasattr(data, 'to_matrix'): # If input is Gate subclass or some other class object that has # a to_matrix method this will call that method. data = data.to_matrix() elif hasattr(data, 'to_operator'): # If input is a BaseOperator subclass this attempts to convert # the object to an Operator so that we can extract the underlying # numpy matrix from `Operator.data`. data = data.to_operator().data # Convert to numpy array in case not already an array data = numpy.array(data, dtype=complex) # Check input is unitary if not is_unitary_matrix(data): raise ExtensionError("Input matrix is not unitary.") # Check input is N-qubit matrix input_dim, output_dim = data.shape num_qubits = int(numpy.log2(input_dim)) if input_dim != output_dim or 2**num_qubits != input_dim: raise ExtensionError("Input matrix is not an N-qubit operator.") self._qasm_name = None self._qasm_definition = None self._qasm_def_written = False # Store instruction params super().__init__('unitary', num_qubits, [data], label=label)
def is_unitary(self, atol=None, rtol=None): """Return True if operator is a unitary matrix.""" if atol is None: atol = self.atol if rtol is None: rtol = self.rtol return is_unitary_matrix(self._data, rtol=rtol, atol=atol)
def make_unitary_instruction(mat, qubits, standard_gates=True): """Return a qobj instruction for a unitary matrix gate. Args: mat (matrix): A square or diagonal unitary matrix. qubits (list[int]): The qubits the matrix is applied to. standard_gates (bool): Check if the matrix instruction is a standard instruction. Returns: dict: The qobj instruction object. Raises: NoiseError: if the input is not a unitary matrix. """ if not is_unitary_matrix(mat): raise NoiseError("Input matrix is not unitary.") if isinstance(qubits, int): qubits = [qubits] instruction = {"name": "unitary", "qubits": qubits, "params": [mat]} if standard_gates: return standard_gate_instruction(instruction) else: return [instruction]
def __call__(self, unitary, simplify=True, atol=DEFAULT_ATOL): """Decompose single qubit gate into a circuit. Args: unitary (Operator or Gate or array): 1-qubit unitary matrix simplify (bool): reduce gate count in decomposition [Default: True]. atol (float): absolute tolerance for checking angles when simplifying returned circuit [Default: 1e-12]. Returns: QuantumCircuit: the decomposed single-qubit gate circuit Raises: QiskitError: if input is invalid or synthesis fails. """ if hasattr(unitary, "to_operator"): # If input is a BaseOperator subclass this attempts to convert # the object to an Operator so that we can extract the underlying # numpy matrix from `Operator.data`. unitary = unitary.to_operator().data elif hasattr(unitary, "to_matrix"): # If input is Gate subclass or some other class object that has # a to_matrix method this will call that method. unitary = unitary.to_matrix() # Convert to numpy array in case not already an array unitary = np.asarray(unitary, dtype=complex) # Check input is a 2-qubit unitary if unitary.shape != (2, 2): raise QiskitError( "OneQubitEulerDecomposer: expected 2x2 input matrix") if not is_unitary_matrix(unitary): raise QiskitError( "OneQubitEulerDecomposer: input matrix is not unitary.") return self._decompose(unitary, simplify=simplify, atol=atol)
def addCustomGates(): receivedDictionary=request.get_json() matrix=receivedDictionary["matrix"] matrix=f.strToComplex(matrix) isUnitary=is_unitary_matrix(matrix) if isUnitary: matrix=c.reversedMatrix(matrix,int(log2(len(matrix)))) c.gatesObjects[receivedDictionary["gateName"]]=f.matrixToGateObject(matrix,receivedDictionary["gateName"]) return jsonify({"isUnitary":isUnitary})
def __call__(self, target, basis_fidelity=None): """Decompose a two-qubit unitary over fixed basis + SU(2) using the best approximation given that each basis application has a finite fidelity. """ basis_fidelity = basis_fidelity or self.basis_fidelity if hasattr(target, 'to_operator'): # If input is a BaseOperator subclass this attempts to convert # the object to an Operator so that we can extract the underlying # numpy matrix from `Operator.data`. target = target.to_operator().data if hasattr(target, 'to_matrix'): # If input is Gate subclass or some other class object that has # a to_matrix method this will call that method. target = target.to_matrix() # Convert to numpy array incase not already an array target = np.asarray(target, dtype=complex) # Check input is a 2-qubit unitary if target.shape != (4, 4): raise QiskitError( "TwoQubitBasisDecomposer: expected 4x4 matrix for target") if not is_unitary_matrix(target): raise QiskitError( "TwoQubitBasisDecomposer: target matrix is not unitary.") target_decomposed = TwoQubitWeylDecomposition(target) traces = self.traces(target_decomposed) expected_fidelities = [ trace_to_fid(traces[i]) * basis_fidelity**i for i in range(4) ] best_nbasis = np.argmax(expected_fidelities) decomposition = self.decomposition_fns[best_nbasis](target_decomposed) decomposition_euler = [ self._decomposer1q._decompose(x) for x in decomposition ] q = QuantumRegister(2) return_circuit = QuantumCircuit(q) return_circuit.global_phase = target_decomposed.global_phase return_circuit.global_phase -= best_nbasis * self.basis.global_phase if best_nbasis == 2: return_circuit.global_phase += np.pi for i in range(best_nbasis): return_circuit.compose(decomposition_euler[2 * i], [q[0]], inplace=True) return_circuit.compose(decomposition_euler[2 * i + 1], [q[1]], inplace=True) return_circuit.append(self.gate, [q[0], q[1]]) return_circuit.compose(decomposition_euler[2 * best_nbasis], [q[0]], inplace=True) return_circuit.compose(decomposition_euler[2 * best_nbasis + 1], [q[1]], inplace=True) return return_circuit
def __init__(self, u, mode="ZYZ", up_to_diagonal=False): if mode != "ZYZ": raise QiskitError("The decomposition mode is not known.") # Check if the matrix u has the right dimensions and if it is a unitary if not u.shape == (2, 2): raise QiskitError("The dimension of the input matrix is not equal to (2,2).") if not is_unitary_matrix(u): raise QiskitError("The 2*2 matrix is not unitary.") self.mode = mode self.up_to_diagonal = up_to_diagonal # Create new gate super().__init__("unitary", 1, [u])
def mixed_unitary_error(noise_ops, standard_gates=True): """ Mixed unitary quantum error channel. The input should be a list of pairs (U[j], p[j]), where `U[j]` is a unitary matrix and `p[j]` is a probability. All probabilities must sum to 1 for the input ops to be valid. Args: noise_ops (list[pair[matrix, double]]): unitary error matricies. standard_gates (bool): Check if input matrices are standard gates. Returns: QuantumError: The quantum error object. Raises: NoiseError: if error parameters are invalid. """ # Error checking if not isinstance(noise_ops, (list, tuple, zip)): raise NoiseError("Input noise ops is not a list.") # Convert to numpy arrays noise_ops = [(np.array(op, dtype=complex), p) for op, p in noise_ops] if not noise_ops: raise NoiseError("Input noise list is empty.") # Check for identity unitaries prob_identity = 0. instructions = [] instructions_probs = [] num_qubits = qubits_from_mat(noise_ops[0][0]) qubits = list(range(num_qubits)) for unitary, prob in noise_ops: # Check unitary if qubits_from_mat(unitary) != num_qubits: raise NoiseError("Input matrices different size.") if not is_unitary_matrix(unitary): raise NoiseError("Input matrix is not unitary.") if is_identity_matrix(unitary): prob_identity += prob else: instr = make_unitary_instruction(unitary, qubits, standard_gates=standard_gates) instructions.append(instr) instructions_probs.append(prob) if prob_identity > 0: instructions.append([{"name": "id", "qubits": [0]}]) instructions_probs.append(prob_identity) return QuantumError(zip(instructions, instructions_probs))
def __init__(self, unitary_matrix, mode='ZYZ', up_to_diagonal=False, u=None): """Create a new single qubit gate based on the unitary ``u``.""" if mode not in ['ZYZ']: raise QiskitError("The decomposition mode is not known.") # Check if the matrix u has the right dimensions and if it is a unitary if unitary_matrix.shape != (2, 2): raise QiskitError("The dimension of the input matrix is not equal to (2,2).") if not is_unitary_matrix(unitary_matrix): raise QiskitError("The 2*2 matrix is not unitary.") self.mode = mode self.up_to_diagonal = up_to_diagonal self._diag = None # Create new gate super().__init__("unitary", 1, [unitary_matrix])
def unitary_from_hermitian(hermitian): """Generates a unitary matrix from a hermitian matrix. The formula is U = e^(i*H). Args: hermitian: A hermitian matrix. Returns: unitary: The resulting unitarian matrix. Raises: AssertionError: If the resulting matrix is not unitarian. """ unitary = np.matrix(expm(1j * hermitian)) assert is_unitary_matrix(unitary) return unitary
def test_controlled_unitary(self, num_ctrl_qubits): """test controlled unitary""" num_target = 1 q_target = QuantumRegister(num_target) qc1 = QuantumCircuit(q_target) # for h-rx(pi/2) theta, phi, lamb = 1.57079632679490, 0.0, 4.71238898038469 qc1.u3(theta, phi, lamb, q_target[0]) base_gate = qc1.to_gate() # get UnitaryGate version of circuit base_op = Operator(qc1) base_mat = base_op.data cgate = base_gate.control(num_ctrl_qubits) test_op = Operator(cgate) cop_mat = _compute_control_matrix(base_mat, num_ctrl_qubits) self.assertTrue(is_unitary_matrix(base_mat)) self.assertTrue(matrix_equal(cop_mat, test_op.data, ignore_phase=True))
def subCircuitCustomGate(): if request.method=='POST': receivedDictionary=request.get_json() c2=Circuit() c2.gatesObjects=c.gatesObjects c2.subCircuitSetter(receivedDictionary) try: circuit=c2.createDraggableCircuit() except Exception as e: return jsonify({"conditionalLoopError":str(e)}) r=Results(circuit) matrix=r.matrixRepresentation() complexMatrix=f.strToComplex(matrix) isUnitary=is_unitary_matrix(complexMatrix) if isUnitary: c.gatesObjects[receivedDictionary["gateName"]]=f.matrixToGateObject(complexMatrix,receivedDictionary["gateName"]) return jsonify({"isUnitary":isUnitary})
def __init__(self, gate_list, up_to_diagonal=False): """UCGate Gate initializer. Args: gate_list (list[ndarray]): list of two qubit unitaries [U_0,...,U_{2^k-1}], where each single-qubit unitary U_i is given as a 2*2 numpy array. up_to_diagonal (bool): determines if the gate is implemented up to a diagonal. or if it is decomposed completely (default: False). If the UCGate u is decomposed up to a diagonal d, this means that the circuit implements a unitary u' such that d.u'=u. Raises: QiskitError: in case of bad input to the constructor """ # check input format if not isinstance(gate_list, list): raise QiskitError( "The single-qubit unitaries are not provided in a list.") for gate in gate_list: if not gate.shape == (2, 2): raise QiskitError( "The dimension of a controlled gate is not equal to (2,2)." ) if not gate_list: raise QiskitError("The gate list cannot be empty.") # Check if number of gates in gate_list is a positive power of two num_contr = math.log2(len(gate_list)) if num_contr < 0 or not num_contr.is_integer(): raise QiskitError( "The number of controlled single-qubit gates is not a " "non-negative power of 2.") # Check if the single-qubit gates are unitaries for gate in gate_list: if not is_unitary_matrix(gate, _EPS): raise QiskitError("A controlled gate is not unitary.") # Create new gate. super().__init__("multiplexer", int(num_contr) + 1, gate_list) self.up_to_diagonal = up_to_diagonal
def __call__(self, unitary_mat, simplify=True, atol=DEFAULT_ATOL): """Decompose single qubit gate into a circuit. Args: unitary_mat (array_like): 1-qubit unitary matrix simplify (bool): remove zero-angle rotations [Default: True] atol (float): absolute tolerance for checking angles zero. Returns: QuantumCircuit: the decomposed single-qubit gate circuit Raises: QiskitError: if input is invalid or synthesis fails. """ if hasattr(unitary_mat, 'to_operator'): # If input is a BaseOperator subclass this attempts to convert # the object to an Operator so that we can extract the underlying # numpy matrix from `Operator.data`. unitary_mat = unitary_mat.to_operator().data if hasattr(unitary_mat, 'to_matrix'): # If input is Gate subclass or some other class object that has # a to_matrix method this will call that method. unitary_mat = unitary_mat.to_matrix() # Convert to numpy array incase not already an array unitary_mat = np.asarray(unitary_mat, dtype=complex) # Check input is a 2-qubit unitary if unitary_mat.shape != (2, 2): raise QiskitError("OneQubitEulerDecomposer: " "expected 2x2 input matrix") if not is_unitary_matrix(unitary_mat): raise QiskitError("OneQubitEulerDecomposer: " "input matrix is not unitary.") circuit = self._circuit(unitary_mat, simplify=simplify, atol=atol) # Check circuit is correct if not Operator(circuit).equiv(Operator(unitary_mat)): raise QiskitError("OneQubitEulerDecomposer: " "synthesis failed within required accuracy.") return circuit
def _make_unitary_instruction(mat, qubits, standard_gates=True): """Temporary function to create Instruction objects from a unitary matrix, which is necessary for creating a new QuantumError with the deprecated standard_gates option. Note that the type of returned object is different from the deprecated make_unitary_instruction. TODO: to be removed after deprecation period. Args: mat (matrix): A square or diagonal unitary matrix. qubits (list[int]): The qubits the matrix is applied to. standard_gates (bool): Check if the matrix instruction is a standard instruction. Returns: list: The list of instructions. Raises: NoiseError: if the input is not a unitary matrix. """ if not is_unitary_matrix(mat): raise NoiseError("Input matrix is not unitary.") if isinstance(qubits, int): qubits = [qubits] instruction = {"name": "unitary", "qubits": qubits, "params": [mat]} if standard_gates: if isinstance(instruction, dict): with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=DeprecationWarning, module="qiskit.providers.aer.noise.errors.errorutils") res = _standard_gate_instruction(instruction) else: res = [instruction] return res else: return [instruction]
def kraus2instructions(kraus_ops, standard_gates, atol=ATOL_DEFAULT): """ Convert a list of Kraus matrices into qobj circuits. If any Kraus operators are a unitary matrix they will be converted into unitary qobj instructions. Identity unitary matrices will also be converted into identity qobj instructions. Args: kraus_ops (list[matrix]): A list of Kraus matrices for a CPTP map. standard_gates (bool): Check if the matrix instruction is a standard instruction (default: True). atol (double): Threshold for testing if probabilities are zero. Returns: list: A list of pairs (p, circuit) where `circuit` is a list of qobj instructions, and `p` is the probability of that circuit for the given error. Raises: NoiseError: If the input Kraus channel is not CPTP. """ # Check threshold if atol < 0: raise NoiseError("atol cannot be negative") if atol > 1e-5: raise NoiseError( "atol value is too large. It should be close to zero.") # Check CPTP if not Kraus(kraus_ops).is_cptp(atol=atol): raise NoiseError("Input Kraus channel is not CPTP.") # Get number of qubits num_qubits = int(np.log2(len(kraus_ops[0]))) if len(kraus_ops[0]) != 2**num_qubits: raise NoiseError("Input Kraus channel is not a multi-qubit channel.") # Check if each matrix is a: # 1. scaled identity matrix # 2. scaled non-identity unitary matrix # 3. a non-unitary Kraus operator # Probabilities prob_identity = 0 prob_unitary = 0 # total probability of all unitary ops (including id) prob_kraus = 0 # total probability of non-unitary ops probabilities = [] # initialize with probability of Identity # Matrices unitaries = [] # non-identity unitaries non_unitaries = [] # non-unitary Kraus matrices for mat in kraus_ops: # Get the value of the maximum diagonal element # of op.H * op for rescaling prob = abs(max(np.diag(np.conj(np.transpose(mat)).dot(mat)))) if prob > 0.0: if abs(prob - 1) > 0.0: # Rescale the operator by square root of prob rescaled_mat = np.array(mat) / np.sqrt(prob) else: rescaled_mat = mat # Check if identity operator if is_identity_matrix(rescaled_mat, ignore_phase=True): prob_identity += prob prob_unitary += prob # Check if unitary elif is_unitary_matrix(rescaled_mat): probabilities.append(prob) prob_unitary += prob unitaries.append(rescaled_mat) # Non-unitary op else: non_unitaries.append(mat) # Check probabilities prob_kraus = 1 - prob_unitary if prob_unitary - 1 > atol: raise NoiseError("Invalid kraus matrices: unitary probability " "{} > 1".format(prob_unitary)) if prob_unitary < -atol: raise NoiseError("Invalid kraus matrices: unitary probability " "{} < 1".format(prob_unitary)) if prob_identity - 1 > atol: raise NoiseError("Invalid kraus matrices: identity probability " "{} > 1".format(prob_identity)) if prob_identity < -atol: raise NoiseError("Invalid kraus matrices: identity probability " "{} < 1".format(prob_identity)) if prob_kraus - 1 > atol: raise NoiseError("Invalid kraus matrices: non-unitary probability " "{} > 1".format(prob_kraus)) if prob_kraus < -atol: raise NoiseError("Invalid kraus matrices: non-unitary probability " "{} < 1".format(prob_kraus)) # Build qobj instructions instructions = [] qubits = list(range(num_qubits)) # Add unitary instructions for unitary in unitaries: instructions.append( make_unitary_instruction( unitary, qubits, standard_gates=standard_gates)) # Add identity instruction if prob_identity > atol: if abs(prob_identity - 1) < atol: probabilities.append(1) else: probabilities.append(prob_identity) instructions.append([{"name": "id", "qubits": [0]}]) # Add Kraus if prob_kraus < atol: # No Kraus operators return zip(instructions, probabilities) if prob_kraus < 1: # Rescale kraus operators by probabilities non_unitaries = [ np.array(op) / np.sqrt(prob_kraus) for op in non_unitaries ] instructions.append(make_kraus_instruction(non_unitaries, qubits)) probabilities.append(prob_kraus) # Normalize probabilities to account for any rounding errors probabilities = list(np.array(probabilities) / np.sum(probabilities)) return zip(instructions, probabilities)
def mixed_unitary_error(noise_ops, standard_gates=None): """ Return a mixed unitary quantum error channel. The input should be a list of pairs ``(U[j], p[j])``, where ``U[j]`` is a unitary matrix and ``p[j]`` is a probability. All probabilities must sum to 1 for the input ops to be valid. Args: noise_ops (list[pair[matrix, double]]): unitary error matrices. standard_gates (bool): DEPRECATED, Check if input matrices are standard gates. Returns: QuantumError: The quantum error object. Raises: NoiseError: if error parameters are invalid. """ if standard_gates is not None: warnings.warn( '"standard_gates" option has been deprecated as of qiskit-aer 0.10.0' ' and will be removed no earlier than 3 months from that release date.' ' Use directly init e.g. QuantumError([(IGate(), prob1), (ZGate(), prob2)]) instead.', DeprecationWarning, stacklevel=2) # Error checking if not isinstance(noise_ops, (list, tuple, zip)): raise NoiseError("Input noise ops is not a list.") # Convert to numpy arrays noise_ops = [(np.array(op, dtype=complex), p) for op, p in noise_ops] if not noise_ops: raise NoiseError("Input noise list is empty.") # Check for identity unitaries prob_identity = 0. instructions = [] instructions_probs = [] num_qubits = int(np.log2(noise_ops[0][0].shape[0])) if noise_ops[0][0].shape != (2**num_qubits, 2**num_qubits): raise NoiseError( "A unitary matrix in input noise_ops is not a multi-qubit matrix.") for unitary, prob in noise_ops: # Check unitary if unitary.shape != noise_ops[0][0].shape: raise NoiseError("Input matrices different size.") if not is_unitary_matrix(unitary): raise NoiseError("Input matrix is not unitary.") if is_identity_matrix(unitary): prob_identity += prob else: if standard_gates: # TODO: to be removed after deprecation period qubits = list(range(num_qubits)) instr = _make_unitary_instruction( unitary, qubits, standard_gates=standard_gates) else: instr = UnitaryGate(unitary) instructions.append(instr) instructions_probs.append(prob) if prob_identity > 0: if standard_gates: # TODO: to be removed after deprecation period instructions.append([{"name": "id", "qubits": [0]}]) else: instructions.append(IGate()) instructions_probs.append(prob_identity) return QuantumError(zip(instructions, instructions_probs))
def is_unitary(self): """Return True if operator is a unitary matrix.""" return is_unitary_matrix(self._data, rtol=self._rtol, atol=self._atol)
def two_qubit_kak(unitary): """Decompose a two-qubit gate over SU(2)+CNOT using the KAK decomposition. Args: unitary (Operator): a 4x4 unitary operator to decompose. Returns: QuantumCircuit: a circuit implementing the unitary over SU(2)+CNOT Raises: QiskitError: input not a unitary, or error in KAK decomposition. """ if hasattr(unitary, 'to_operator'): # If input is a BaseOperator subclass this attempts to convert # the object to an Operator so that we can extract the underlying # numpy matrix from `Operator.data`. unitary = unitary.to_operator().data if hasattr(unitary, 'to_matrix'): # If input is Gate subclass or some other class object that has # a to_matrix method this will call that method. unitary = unitary.to_matrix() # Convert to numpy array incase not already an array unitary_matrix = np.array(unitary, dtype=complex) # Check input is a 2-qubit unitary if unitary_matrix.shape != (4, 4): raise QiskitError("two_qubit_kak: Expected 4x4 matrix") if not is_unitary_matrix(unitary_matrix): raise QiskitError("Input matrix is not unitary.") phase = la.det(unitary_matrix)**(-1.0 / 4.0) # Make it in SU(4), correct phase at the end U = phase * unitary_matrix # B changes to the Bell basis B = (1.0 / math.sqrt(2)) * np.array( [[1, 1j, 0, 0], [0, 0, 1j, 1], [0, 0, 1j, -1], [1, -1j, 0, 0]], dtype=complex) # We also need B.conj().T below Bdag = B.conj().T # U' = Bdag . U . B Uprime = Bdag.dot(U.dot(B)) # M^2 = trans(U') . U' M2 = Uprime.T.dot(Uprime) # Diagonalize M2 # Must use diagonalization routine which finds a real orthogonal matrix P # when M2 is real. D, P = la.eig(M2) D = np.diag(D) # If det(P) == -1 then in O(4), apply a swap to make P in SO(4) if abs(la.det(P) + 1) < 1e-5: swap = np.array( [[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]], dtype=complex) P = P.dot(swap) D = swap.dot(D.dot(swap)) Q = np.sqrt(D) # array from elementwise sqrt # Want to take square root so that Q has determinant 1 if abs(la.det(Q) + 1) < 1e-5: Q[0, 0] = -Q[0, 0] # Q^-1*P.T = P' -> QP' = P.T (solve for P' using Ax=b) Pprime = la.solve(Q, P.T) # K' now just U' * P * P' Kprime = Uprime.dot(P.dot(Pprime)) K1 = B.dot(Kprime.dot(P.dot(Bdag))) A = B.dot(Q.dot(Bdag)) K2 = B.dot(P.T.dot(Bdag)) # KAK = K1 * A * K2 KAK = K1.dot(A.dot(K2)) # Verify decomp matches input unitary. if la.norm(KAK - U) > 1e-6: raise QiskitError("two_qubit_kak: KAK decomposition " + "does not return input unitary.") # Compute parameters alpha, beta, gamma so that # A = exp(i * (alpha * XX + beta * YY + gamma * ZZ)) xx = np.array([[0, 0, 0, 1], [0, 0, 1, 0], [0, 1, 0, 0], [1, 0, 0, 0]], dtype=complex) yy = np.array([[0, 0, 0, -1], [0, 0, 1, 0], [0, 1, 0, 0], [-1, 0, 0, 0]], dtype=complex) zz = np.array([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]], dtype=complex) A_real_tr = A.real.trace() alpha = math.atan2(A.dot(xx).imag.trace(), A_real_tr) beta = math.atan2(A.dot(yy).imag.trace(), A_real_tr) gamma = math.atan2(A.dot(zz).imag.trace(), A_real_tr) # K1 = kron(U1, U2) and K2 = kron(V1, V2) # Find the matrices U1, U2, V1, V2 # Find a block in K1 where U1_ij * [U2] is not zero L = K1[0:2, 0:2] if la.norm(L) < 1e-9: L = K1[0:2, 2:4] if la.norm(L) < 1e-9: L = K1[2:4, 2:4] # Remove the U1_ij prefactor Q = L.dot(L.conj().T) U2 = L / math.sqrt(Q[0, 0].real) # Now grab U1 given we know U2 R = K1.dot(np.kron(np.identity(2), U2.conj().T)) U1 = np.zeros((2, 2), dtype=complex) U1[0, 0] = R[0, 0] U1[0, 1] = R[0, 2] U1[1, 0] = R[2, 0] U1[1, 1] = R[2, 2] # Repeat K1 routine for K2 L = K2[0:2, 0:2] if la.norm(L) < 1e-9: L = K2[0:2, 2:4] if la.norm(L) < 1e-9: L = K2[2:4, 2:4] Q = np.dot(L, np.transpose(L.conjugate())) V2 = L / np.sqrt(Q[0, 0]) R = np.dot(K2, np.kron(np.identity(2), np.transpose(V2.conjugate()))) V1 = np.zeros_like(U1) V1[0, 0] = R[0, 0] V1[0, 1] = R[0, 2] V1[1, 0] = R[2, 0] V1[1, 1] = R[2, 2] if la.norm(np.kron(U1, U2) - K1) > 1e-4: raise QiskitError("two_qubit_kak: K1 != U1 x U2") if la.norm(np.kron(V1, V2) - K2) > 1e-4: raise QiskitError("two_qubit_kak: K2 != V1 x V2") test = la.expm(1j * (alpha * xx + beta * yy + gamma * zz)) if la.norm(A - test) > 1e-4: raise QiskitError("two_qubit_kak: " + "Matrix A does not match xx,yy,zz decomposition.") # Circuit that implements K1 * A * K2 (up to phase), using # Vatan and Williams Fig. 6 of quant-ph/0308006v3 # Include prefix and suffix single-qubit gates into U2, V1 respectively. V2 = np.array([[np.exp(1j * np.pi / 4), 0], [0, np.exp(-1j * np.pi / 4)]], dtype=complex).dot(V2) U1 = U1.dot( np.array([[np.exp(-1j * np.pi / 4), 0], [0, np.exp(1j * np.pi / 4)]], dtype=complex)) # Corrects global phase: exp(ipi/4)*phase' U1 = U1.dot( np.array([[np.exp(1j * np.pi / 4), 0], [0, np.exp(1j * np.pi / 4)]], dtype=complex)) U1 = phase.conjugate() * U1 # Test g1 = np.kron(V1, V2) g2 = np.array([[1, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0], [0, 1, 0, 0]], dtype=complex) theta = 2 * gamma - np.pi / 2 Ztheta = np.array( [[np.exp(1j * theta / 2), 0], [0, np.exp(-1j * theta / 2)]], dtype=complex) kappa = np.pi / 2 - 2 * alpha Ykappa = np.array( [[math.cos(kappa / 2), math.sin(kappa / 2)], [-math.sin(kappa / 2), math.cos(kappa / 2)]], dtype=complex) g3 = np.kron(Ztheta, Ykappa) g4 = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]], dtype=complex) zeta = 2 * beta - np.pi / 2 Yzeta = np.array( [[math.cos(zeta / 2), math.sin(zeta / 2)], [-math.sin(zeta / 2), math.cos(zeta / 2)]], dtype=complex) g5 = np.kron(np.identity(2), Yzeta) g6 = g2 g7 = np.kron(U1, U2) V = g2.dot(g1) V = g3.dot(V) V = g4.dot(V) V = g5.dot(V) V = g6.dot(V) V = g7.dot(V) if la.norm(V - U * phase.conjugate()) > 1e-6: raise QiskitError("two_qubit_kak: " + "sequence incorrect, unknown error") v1_param = euler_angles_1q(V1) v2_param = euler_angles_1q(V2) u1_param = euler_angles_1q(U1) u2_param = euler_angles_1q(U2) v1_gate = U3Gate(v1_param[0], v1_param[1], v1_param[2]) v2_gate = U3Gate(v2_param[0], v2_param[1], v2_param[2]) u1_gate = U3Gate(u1_param[0], u1_param[1], u1_param[2]) u2_gate = U3Gate(u2_param[0], u2_param[1], u2_param[2]) q = QuantumRegister(2) return_circuit = QuantumCircuit(q) return_circuit.append(v1_gate, [q[1]]) return_circuit.append(v2_gate, [q[0]]) return_circuit.append(CnotGate(), [q[0], q[1]]) gate = U3Gate(0.0, 0.0, -2.0 * gamma + np.pi / 2.0) return_circuit.append(gate, [q[1]]) gate = U3Gate(-np.pi / 2.0 + 2.0 * alpha, 0.0, 0.0) return_circuit.append(gate, [q[0]]) return_circuit.append(CnotGate(), [q[1], q[0]]) gate = U3Gate(-2.0 * beta + np.pi / 2.0, 0.0, 0.0) return_circuit.append(gate, [q[0]]) return_circuit.append(CnotGate(), [q[0], q[1]]) return_circuit.append(u1_gate, [q[1]]) return_circuit.append(u2_gate, [q[0]]) return return_circuit
def is_cptp(self): """Return True if completely-positive trace-preserving.""" # If the matrix is a unitary matrix the channel is CPTP return is_unitary_matrix(self._data, rtol=self.rtol, atol=self.atol)