def test_substitute_circuit_one_middle(self): """The method substitute_node_with_dag() replaces a in-the-middle node with a DAG.""" cx_node = self.dag.get_op_nodes(op=CnotGate).pop() flipped_cx_circuit = DAGCircuit() v = QuantumRegister(2, "v") flipped_cx_circuit.add_qreg(v) flipped_cx_circuit.add_basis_element("cx", 2) flipped_cx_circuit.add_basis_element("h", 1) flipped_cx_circuit.apply_operation_back(HGate(v[0])) flipped_cx_circuit.apply_operation_back(HGate(v[1])) flipped_cx_circuit.apply_operation_back(CnotGate(v[1], v[0])) flipped_cx_circuit.apply_operation_back(HGate(v[0])) flipped_cx_circuit.apply_operation_back(HGate(v[1])) self.dag.substitute_node_with_dag(cx_node, flipped_cx_circuit, wires=[v[0], v[1]]) self.assertEqual(self.dag.count_ops()['h'], 5)
def test_apply_operation_back(self): """The apply_operation_back() method.""" self.dag.apply_operation_back(HGate(), [self.qubit0], [], condition=None) self.dag.apply_operation_back(CnotGate(), [self.qubit0, self.qubit1], [], condition=None) self.dag.apply_operation_back(Measure(), [self.qubit1, self.clbit1], [], condition=None) self.dag.apply_operation_back(XGate(), [self.qubit1], [], condition=self.condition) self.dag.apply_operation_back(Measure(), [self.qubit0, self.clbit0], [], condition=None) self.dag.apply_operation_back(Measure(), [self.qubit1, self.clbit1], [], condition=None) self.assertEqual(len(list(self.dag.nodes())), 16) self.assertEqual(len(list(self.dag.edges())), 17)
def test_instructions_equal(self): """Test equality of two instructions. """ qr = QuantumRegister(3) cr = ClassicalRegister(3) hop1 = Instruction('h', [], qr, cr) hop2 = Instruction('s', [], qr, cr) hop3 = Instruction('h', [], qr, cr) uop1 = Instruction('u', [0.4, 0.5, 0.5], qr, cr) uop2 = Instruction('u', [0.4, 0.6, 0.5], qr, cr) uop3 = Instruction('v', [0.4, 0.5, 0.5], qr, cr) uop4 = Instruction('u', [0.4, 0.5, 0.5], qr, cr) self.assertFalse(hop1 == hop2) self.assertTrue(hop1 == hop3) self.assertFalse(uop1 == uop2) self.assertTrue(uop1 == uop4) self.assertFalse(uop1 == uop3) self.assertTrue(HGate(qr[0]) == HGate(qr[1])) self.assertFalse(HGate(qr[0]) == CnotGate(qr[0], qr[1])) self.assertFalse(hop1 == HGate(qr[2]))
def test_quantum_predecessors(self): """The method dag.quantum_predecessors() returns predecessors connected by quantum edges""" self.dag.apply_operation_back(Reset(), [self.qubit0], []) self.dag.apply_operation_back(CnotGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(Measure(), [self.qubit1, self.clbit1], []) predecessor_measure = self.dag.quantum_predecessors( self.dag.named_nodes('measure').pop()) cnot_node = next(predecessor_measure) with self.assertRaises(StopIteration): next(predecessor_measure) self.assertIsInstance(cnot_node.op, CnotGate) predecessor_cnot = self.dag.quantum_predecessors(cnot_node) self.assertIsInstance(next(predecessor_cnot).op, Reset) self.assertEqual(next(predecessor_cnot).type, 'in') with self.assertRaises(StopIteration): next(predecessor_cnot)
def _define(self): """ gate ch a,b { s b; h b; t b; cx a, b; tdg b; h b; sdg b; } """ definition = [] q = QuantumRegister(2, "q") rule = [(SGate(), [q[1]], []), (HGate(), [q[1]], []), (TGate(), [q[1]], []), (CnotGate(), [q[0], q[1]], []), (TdgGate(), [q[1]], []), (HGate(), [q[1]], []), (SdgGate(), [q[1]], [])] for inst in rule: definition.append(inst) self.definition = definition
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_angles = [euler_angles_1q(x) for x in decomposition] q = QuantumRegister(2) return_circuit = QuantumCircuit(q) for i in range(best_nbasis): return_circuit.append(U3Gate(*decomposition_angles[2*i]), [q[0]]) return_circuit.append(U3Gate(*decomposition_angles[2*i+1]), [q[1]]) return_circuit.append(CnotGate(), [q[0], q[1]]) return_circuit.append(U3Gate(*decomposition_angles[2*best_nbasis]), [q[0]]) return_circuit.append(U3Gate(*decomposition_angles[2*best_nbasis+1]), [q[1]]) return return_circuit
def test_dag_nodes_on_wire(self): """Test that listing the gates on a qubit/classical bit gets the correct gates""" self.dag.apply_operation_back(CnotGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(HGate(), [self.qubit0], []) qbit = self.dag.qubits()[0] self.assertEqual([1, 11, 12, 2], [i._node_id for i in self.dag.nodes_on_wire(qbit)]) self.assertEqual( [11, 12], [i._node_id for i in self.dag.nodes_on_wire(qbit, only_ops=True)]) cbit = self.dag.clbits()[0] self.assertEqual([7, 8], [i._node_id for i in self.dag.nodes_on_wire(cbit)]) self.assertEqual( [], [i._node_id for i in self.dag.nodes_on_wire(cbit, only_ops=True)]) with self.assertRaises(DAGCircuitError): next(self.dag.nodes_on_wire((qbit.register, 7)))
def test_layers_basic(self): """ The layers() method returns a list of layers, each of them with a list of nodes.""" qreg = QuantumRegister(2, 'qr') creg = ClassicalRegister(2, 'cr') qubit0 = qreg[0] qubit1 = qreg[1] clbit0 = creg[0] clbit1 = creg[1] condition = (creg, 3) dag = DAGCircuit() dag.add_basis_element('h', 1, 0, 0) dag.add_basis_element('cx', 2, 0, 0) dag.add_basis_element('x', 1, 0, 0) dag.add_basis_element('measure', 1, 1, 0) dag.add_qreg(qreg) dag.add_creg(creg) dag.apply_operation_back(HGate(qubit0)) dag.apply_operation_back(CnotGate(qubit0, qubit1), condition=None) dag.apply_operation_back(Measure(qubit1, clbit1), condition=None) dag.apply_operation_back(XGate(qubit1), condition=condition) dag.apply_operation_back(Measure(qubit0, clbit0), condition=None) dag.apply_operation_back(Measure(qubit1, clbit1), condition=None) layers = list(dag.layers()) self.assertEqual(5, len(layers)) name_layers = [ [node[1]["op"].name for node in layer["graph"].multi_graph.nodes(data=True) if node[1]["type"] == "op"] for layer in layers] self.assertEqual([ ['h'], ['cx'], ['measure'], ['x'], ['measure', 'measure'] ], name_layers)
def test_substituting_node_preserves_args_condition(self, inplace): """Verify args and condition are preserved by a substitution.""" dag = DAGCircuit() qr = QuantumRegister(2) cr = ClassicalRegister(1) dag.add_qreg(qr) dag.add_creg(cr) dag.apply_operation_back(HGate(), [qr[1]]) node_to_be_replaced = dag.apply_operation_back(CnotGate(), [qr[1], qr[0]], condition=(cr, 1)) dag.apply_operation_back(HGate(), [qr[1]]) replacement_node = dag.substitute_node(node_to_be_replaced, CzGate(), inplace=inplace) raise_if_dagcircuit_invalid(dag) self.assertEqual(replacement_node.name, 'cz') self.assertEqual(replacement_node.qargs, [qr[1], qr[0]]) self.assertEqual(replacement_node.cargs, []) self.assertEqual(replacement_node.condition, (cr, 1)) self.assertEqual(replacement_node is node_to_be_replaced, inplace)
def setUp(self): self.dag = DAGCircuit() qreg = QuantumRegister(3, 'qr') creg = ClassicalRegister(2, 'cr') self.dag.add_qreg(qreg) self.dag.add_creg(creg) self.dag.add_basis_element(name='h', number_qubits=1, number_classical=0, number_parameters=0) self.dag.add_basis_element('cx', 2, 0, 0) self.dag.add_basis_element('x', 1, 0, 0) self.dag.add_basis_element('measure', 1, 1, 0) self.dag.add_basis_element('reset', 1, 0, 0) self.qubit0 = qreg[0] self.qubit1 = qreg[1] self.qubit2 = qreg[2] self.clbit0 = creg[0] self.clbit1 = creg[1] self.condition = (creg, 3) self.dag.apply_operation_back(HGate(self.qubit0)) self.dag.apply_operation_back(CnotGate(self.qubit0, self.qubit1)) self.dag.apply_operation_back(XGate(self.qubit1))
def _define(self): """ gate ccx a,b,c { h c; cx b,c; tdg c; cx a,c; t c; cx b,c; tdg c; cx a,c; t b; t c; h c; cx a,b; t a; tdg b; cx a,b;} """ definition = [] q = QuantumRegister(3, "q") rule = [(HGate(), [q[2]], []), (CnotGate(), [q[1], q[2]], []), (TdgGate(), [q[2]], []), (CnotGate(), [q[0], q[2]], []), (TGate(), [q[2]], []), (CnotGate(), [q[1], q[2]], []), (TdgGate(), [q[2]], []), (CnotGate(), [q[0], q[2]], []), (TGate(), [q[1]], []), (TGate(), [q[2]], []), (HGate(), [q[2]], []), (CnotGate(), [q[0], q[1]], []), (TGate(), [q[0]], []), (TdgGate(), [q[1]], []), (CnotGate(), [q[0], q[1]], [])] for inst in rule: definition.append(inst) self.definition = definition
def _define_decompositions(self): """ gate ccx a,b,c { h c; cx b,c; tdg c; cx a,c; t c; cx b,c; tdg c; cx a,c; t b; t c; h c; cx a,b; t a; tdg b; cx a,b;} """ decomposition = DAGCircuit() q = QuantumRegister(3, "q") decomposition.add_qreg(q) decomposition.add_basis_element("h", 1, 0, 0) decomposition.add_basis_element("cx", 2, 0, 0) decomposition.add_basis_element("t", 1, 0, 0) decomposition.add_basis_element("tdg", 1, 0, 0) decomposition.add_basis_element("s", 1, 0, 0) decomposition.add_basis_element("sdg", 1, 0, 0) rule = [ HGate(q[2]), CnotGate(q[1], q[2]), TdgGate(q[2]), CnotGate(q[0], q[2]), TGate(q[2]), CnotGate(q[1], q[2]), TdgGate(q[2]), CnotGate(q[0], q[2]), TGate(q[1]), TGate(q[2]), HGate(q[2]), CnotGate(q[0], q[1]), TGate(q[0]), TdgGate(q[1]), CnotGate(q[0], q[1]) ] for inst in rule: decomposition.apply_operation_back(inst) self._decompositions = [decomposition]
def _multiplex(self, bottom_gate, bottom_qubit_index, list_of_angles): """ Internal recursive method to create gates to perform rotations on the imaginary qubits: works by rotating LSB (and hence ALL imaginary qubits) by combo angle and then flipping sign (by flipping the bit, hence moving the complex amplitudes) of half the imaginary qubits (CNOT) followed by another combo angle on LSB, therefore executing conditional (on MSB) rotations, thereby disentangling LSB. """ list_len = len(list_of_angles) target_qubit = self.nth_qubit_from_least_sig_qubit(bottom_qubit_index) # Case of no multiplexing = base case for recursion if list_len == 1: return bottom_gate(list_of_angles[0], target_qubit) local_num_qubits = int(math.log2(list_len)) + 1 control_qubit = self.nth_qubit_from_least_sig_qubit(local_num_qubits - 1 + bottom_qubit_index) # calc angle weights, assuming recursion (that is the lower-level # requested angles have been correctly implemented by recursion angle_weight = scipy.kron([[0.5, 0.5], [0.5, -0.5]], numpy.identity(2**(local_num_qubits - 2))) # calc the combo angles list_of_angles = ( angle_weight * numpy.matrix(list_of_angles).transpose()).reshape(-1).tolist()[0] combine_composite_gates = CompositeGate( "multiplex" + local_num_qubits.__str__(), [], self.arg) # recursive step on half the angles fulfilling the above assumption combine_composite_gates._attach( self._multiplex(bottom_gate, bottom_qubit_index, list_of_angles[0:(list_len // 2)])) # combine_composite_gates.cx(control_qubit,target_qubit) -> does not # work as expected because checks circuit # so attach CNOT as follows, thereby flipping the LSB qubit combine_composite_gates._attach(CnotGate(control_qubit, target_qubit)) # implement extra efficiency from the paper of cancelling adjacent # CNOTs (by leaving out last CNOT and reversing (NOT inverting) the # second lower-level multiplex) sub_gate = self._multiplex(bottom_gate, bottom_qubit_index, list_of_angles[(list_len // 2):]) if isinstance(sub_gate, CompositeGate): combine_composite_gates._attach(sub_gate.reverse()) else: combine_composite_gates._attach(sub_gate) # outer multiplex keeps final CNOT, because no adjacent CNOT to cancel # with if self.num_qubits == local_num_qubits + bottom_qubit_index: combine_composite_gates._attach( CnotGate(control_qubit, target_qubit)) return combine_composite_gates
return_circuit.append(U3Gate(*decomposition_angles[2 * best_nbasis]), [q[0]]) return_circuit.append( U3Gate(*decomposition_angles[2 * best_nbasis + 1]), [q[1]]) return return_circuit def num_basis_gates(self, unitary): """ Computes the number of basis gates needed in a decomposition of input unitary """ if hasattr(unitary, 'to_operator'): unitary = unitary.to_operator().data if hasattr(unitary, 'to_matrix'): unitary = unitary.to_matrix() unitary = np.asarray(unitary, dtype=complex) a, b, c = weyl_coordinates(unitary)[:] traces = [ 4 * (np.cos(a) * np.cos(b) * np.cos(c) + 1j * np.sin(a) * np.sin(b) * np.sin(c)), 4 * (np.cos(np.pi / 4 - a) * np.cos(self.basis.b - b) * np.cos(c) + 1j * np.sin(np.pi / 4 - a) * np.sin(self.basis.b - b) * np.sin(c)), 4 * np.cos(c), 4 ] return np.argmax([ trace_to_fid(traces[i]) * self.basis_fidelity**i for i in range(4) ]) two_qubit_cnot_decompose = TwoQubitBasisDecomposer(CnotGate())
def create_dag_op(self, name, args, qubits): """Create a DAG op node. """ if name == "u0": op = U0Gate(args[0], qubits[0]) elif name == "u1": op = U1Gate(args[0], qubits[0]) elif name == "u2": op = U2Gate(args[0], args[1], qubits[0]) elif name == "u3": op = U3Gate(args[0], args[1], args[2], qubits[0]) elif name == "x": op = XGate(qubits[0]) elif name == "y": op = YGate(qubits[0]) elif name == "z": op = ZGate(qubits[0]) elif name == "t": op = TGate(qubits[0]) elif name == "tdg": op = TdgGate(qubits[0]) elif name == "s": op = SGate(qubits[0]) elif name == "sdg": op = SdgGate(qubits[0]) elif name == "swap": op = SwapGate(qubits[0], qubits[1]) elif name == "rx": op = RXGate(args[0], qubits[0]) elif name == "ry": op = RYGate(args[0], qubits[0]) elif name == "rz": op = RZGate(args[0], qubits[0]) elif name == "rzz": op = RZZGate(args[0], qubits[0], qubits[1]) elif name == "id": op = IdGate(qubits[0]) elif name == "h": op = HGate(qubits[0]) elif name == "cx": op = CnotGate(qubits[0], qubits[1]) elif name == "cy": op = CyGate(qubits[0], qubits[1]) elif name == "cz": op = CzGate(qubits[0], qubits[1]) elif name == "ch": op = CHGate(qubits[0], qubits[1]) elif name == "crz": op = CrzGate(args[0], qubits[0], qubits[1]) elif name == "cu1": op = Cu1Gate(args[0], qubits[0], qubits[1]) elif name == "cu3": op = Cu3Gate(args[0], args[1], args[2], qubits[0], qubits[1]) elif name == "ccx": op = ToffoliGate(qubits[0], qubits[1], qubits[2]) elif name == "cswap": op = FredkinGate(qubits[0], qubits[1], qubits[2]) else: raise BackendError("unknown operation for name ast node name %s" % name) self.circuit.add_basis_element(op.name, len(op.qargs), len(op.cargs), len(op.param)) self.start_gate(op) self.end_gate(op)
def _multiplex(self, target_gate, list_of_angles): """ Return a recursive implementation of a multiplexor circuit, where each instruction itself has a decomposition based on smaller multiplexors. The LSB is the multiplexor "data" and the other bits are multiplexor "select". Args: target_gate (Gate): Ry or Rz gate to apply to target qubit, multiplexed over all other "select" qubits list_of_angles (list[float]): list of rotation angles to apply Ry and Rz Returns: DAGCircuit: the circuit implementing the multiplexor's action """ list_len = len(list_of_angles) local_num_qubits = int(math.log2(list_len)) + 1 q = QuantumRegister(local_num_qubits) circuit = QuantumCircuit(q, name="multiplex" + local_num_qubits.__str__()) lsb = q[0] msb = q[local_num_qubits - 1] # case of no multiplexing: base case for recursion if local_num_qubits == 1: circuit.append(target_gate(list_of_angles[0]), [q[0]]) return circuit # calc angle weights, assuming recursion (that is the lower-level # requested angles have been correctly implemented by recursion angle_weight = scipy.kron([[0.5, 0.5], [0.5, -0.5]], np.identity(2**(local_num_qubits - 2))) # calc the combo angles list_of_angles = angle_weight.dot(np.array(list_of_angles)).tolist() # recursive step on half the angles fulfilling the above assumption multiplex_1 = self._multiplex(target_gate, list_of_angles[0:(list_len // 2)]) circuit.append(multiplex_1.to_instruction(), q[0:-1]) # attach CNOT as follows, thereby flipping the LSB qubit circuit.append(CnotGate(), [msb, lsb]) # implement extra efficiency from the paper of cancelling adjacent # CNOTs (by leaving out last CNOT and reversing (NOT inverting) the # second lower-level multiplex) multiplex_2 = self._multiplex(target_gate, list_of_angles[(list_len // 2):]) if list_len > 1: circuit.append(multiplex_2.to_instruction().mirror(), q[0:-1]) else: circuit.append(multiplex_2.to_instruction(), q[0:-1]) # attach a final CNOT circuit.append(CnotGate(), [msb, lsb]) return circuit
classical_r = ClassicalRegister(4, "cr") circuit = QuantumCircuit(quantum_r, classical_r) circuit.h(quantum_r[0]) circuit.rx(0, quantum_r[0]) circuit.cx(quantum_r[0], quantum_r[1]) circuit.cx(quantum_r[0], quantum_r[1]) circuit.h(quantum_r[0]) circuit.cx(quantum_r[0], quantum_r[1]) composite_gate_1 = CompositeGate("composite1", [], [quantum_r[x] for x in range(4)]) composite_gate_1._attach(CnotGate(quantum_r[0], quantum_r[1])) circuit._attach(composite_gate_1) circuit.h(quantum_r[0]) composite_gate_2 = CompositeGate("composite2", [], [quantum_r[x] for x in range(4)]) composite_gate_2._attach(CnotGate(quantum_r[0], quantum_r[1])) circuit._attach(composite_gate_2) circuit.cx(quantum_r[0], quantum_r[1]) circuit.h(quantum_r[0]) composite_gate_3 = CompositeGate("composite3", [], [quantum_r[x] for x in range(4)]) composite_gate_3._attach(CnotGate(quantum_r[0], quantum_r[1]))
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