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 = [_DECOMPOSER1Q.angles(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(self.gate, [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 _define(self): """ gate ms2(theta, phi) a,b { rz(phi) a; rz(phi+pi/2) b; CX b,a; rz(-pi/2) a; ry(theta+pi/2) b; CX a,b; ry(-pi/2) b; CX b,a; rz(-phi-pi/2) a; rz(-phi) b; } """ definition = [] q = QuantumRegister(2, "q") theta, phi = tuple(self.params) # rule = [ # (U3Gate(0, 0, phi), [q[0]], []), # (U3Gate(0, 0, phi+pi/2), [q[1]], []), # (CnotGate(), [q[1], q[0]], []), # (U3Gate(0, 0, -pi/2), [q[0]], []), # (U3Gate(theta+pi/2,0,0), [q[1]], []), # (CnotGate(), [q[0], q[1]], []), # (U3Gate(-pi/2,0,0), [q[1]], []), # (CnotGate(), [q[1], q[0]], []), # (U3Gate(0, 0, -phi-pi/2), [q[0]], []), # (U3Gate(0, 0, -phi), [q[1]], []), # ] rule = [ (U3Gate(0, 0, phi), [q[0]], []), (U3Gate(0, 0, phi), [q[1]], []), (RXXGate(theta), [q[0], q[1]], []), (U3Gate(0, 0, -phi), [q[0]], []), (U3Gate(0, 0, -phi), [q[1]], []), ] for inst in rule: definition.append(inst) self.definition = definition
def _process_node(self, node): """Carry out the action associated with a node.""" if node.type == "program": self._process_children(node) elif node.type == "qreg": qreg = QuantumRegister(node.index, node.name) self.dag.add_qreg(qreg) elif node.type == "creg": creg = ClassicalRegister(node.index, node.name) self.dag.add_creg(creg) elif node.type == "id": raise QiskitError("internal error: _process_node on id") elif node.type == "int": raise QiskitError("internal error: _process_node on int") elif node.type == "real": raise QiskitError("internal error: _process_node on real") elif node.type == "indexed_id": raise QiskitError("internal error: _process_node on indexed_id") elif node.type == "id_list": # We process id_list nodes when they are leaves of barriers. return [self._process_bit_id(node_children) for node_children in node.children] elif node.type == "primary_list": # We should only be called for a barrier. return [self._process_bit_id(m) for m in node.children] elif node.type == "gate": self._process_gate(node) elif node.type == "custom_unitary": self._process_custom_unitary(node) elif node.type == "universal_unitary": args = self._process_node(node.children[0]) qid = self._process_bit_id(node.children[1]) for element in qid: u3_gate = U3Gate(*args, element) u3_gate.condition = self.condition self.dag.apply_operation_back(u3_gate) elif node.type == "cnot": self._process_cnot(node) elif node.type == "expression_list": return node.children elif node.type == "binop": raise QiskitError("internal error: _process_node on binop") elif node.type == "prefix": raise QiskitError("internal error: _process_node on prefix") elif node.type == "measure": self._process_measure(node) elif node.type == "format": self.version = node.version() elif node.type == "barrier": ids = self._process_node(node.children[0]) qubits = [] for qubit in ids: for j, _ in enumerate(qubit): qubits.append(qubit[j]) self.dag.apply_operation_back(Barrier(len(qubits)), qubits, []) elif node.type == "reset": id0 = self._process_bit_id(node.children[0]) for i, _ in enumerate(id0): reset = Reset() reset.condition = self.condition self.dag.apply_operation_back(reset, [id0[i]], []) elif node.type == "if": self._process_if(node) elif node.type == "opaque": self._process_gate(node, opaque=True) elif node.type == "external": raise QiskitError("internal error: _process_node on external") else: raise QiskitError("internal error: undefined node type", node.type, "line=%s" % node.line, "file=%s" % node.file) return None
def run(self, dag): """Run the Optimize1qGates pass on `dag`. Args: dag (DAGCircuit): the DAG to be optimized. Returns: DAGCircuit: the optimized DAG. Raises: TranspilerError: if YZY and ZYZ angles do not give same rotation matrix. """ use_u = 'u' in self.basis use_p = 'p' in self.basis runs = dag.collect_runs(["u1", "u2", "u3", "u", 'p']) runs = _split_runs_on_parameters(runs) for run in runs: if use_p: right_name = "p" else: right_name = "u1" right_parameters = (0, 0, 0) # (theta, phi, lambda) right_global_phase = 0 for current_node in run: left_name = current_node.name if (current_node.condition is not None or len(current_node.qargs) != 1 or left_name not in ["p", "u1", "u2", "u3", 'u', "id"]): raise TranspilerError("internal error") if left_name in ("u1", "p"): left_parameters = (0, 0, current_node.op.params[0]) elif left_name == "u2": left_parameters = (np.pi / 2, current_node.op.params[0], current_node.op.params[1]) elif left_name in ("u3", 'u'): left_parameters = tuple(current_node.op.params) else: if use_p: left_name = "p" else: left_name = "u1" # replace id with u1 left_parameters = (0, 0, 0) if (current_node.op.definition is not None and current_node.op.definition.global_phase): right_global_phase += current_node.op.definition.global_phase # If there are any sympy objects coming from the gate convert # to numpy. left_parameters = tuple([float(x) for x in left_parameters]) # Compose gates name_tuple = (left_name, right_name) if name_tuple in (("u1", "u1"), ("p", "p")): # u1(lambda1) * u1(lambda2) = u1(lambda1 + lambda2) right_parameters = (0, 0, right_parameters[2] + left_parameters[2]) elif name_tuple in (("u1", "u2"), ("p", "u2")): # u1(lambda1) * u2(phi2, lambda2) = u2(phi2 + lambda1, lambda2) right_parameters = (np.pi / 2, right_parameters[1] + left_parameters[2], right_parameters[2]) elif name_tuple in (("u2", "u1"), ("u2", "p")): # u2(phi1, lambda1) * u1(lambda2) = u2(phi1, lambda1 + lambda2) right_name = "u2" right_parameters = (np.pi / 2, left_parameters[1], right_parameters[2] + left_parameters[2]) elif name_tuple in (("u1", "u3"), ("u1", "u"), ("p", "u3"), ("p", "u")): # u1(lambda1) * u3(theta2, phi2, lambda2) = # u3(theta2, phi2 + lambda1, lambda2) right_parameters = (right_parameters[0], right_parameters[1] + left_parameters[2], right_parameters[2]) elif name_tuple in (("u3", "u1"), ('u', 'u1'), ("u3", "p"), ("u", "p")): # u3(theta1, phi1, lambda1) * u1(lambda2) = # u3(theta1, phi1, lambda1 + lambda2) if use_u: right_name = 'u' else: right_name = "u3" right_parameters = (left_parameters[0], left_parameters[1], right_parameters[2] + left_parameters[2]) elif name_tuple == ("u2", "u2"): # Using Ry(pi/2).Rz(2*lambda).Ry(pi/2) = # Rz(pi/2).Ry(pi-2*lambda).Rz(pi/2), # u2(phi1, lambda1) * u2(phi2, lambda2) = # u3(pi - lambda1 - phi2, phi1 + pi/2, lambda2 + pi/2) if use_u: right_name = 'u' else: right_name = "u3" right_parameters = (np.pi - left_parameters[2] - right_parameters[1], left_parameters[1] + np.pi / 2, right_parameters[2] + np.pi / 2) elif name_tuple[1] == "nop": right_name = left_name right_parameters = left_parameters else: # For composing u3's or u2's with u3's, use # u2(phi, lambda) = u3(pi/2, phi, lambda) # together with the qiskit.mapper.compose_u3 method. if use_u: right_name = 'u' else: right_name = "u3" # Evaluate the symbolic expressions for efficiency right_parameters = Optimize1qGates.compose_u3(left_parameters[0], left_parameters[1], left_parameters[2], right_parameters[0], right_parameters[1], right_parameters[2]) # Why evalf()? This program: # OPENQASM 2.0; # include "qelib1.inc"; # qreg q[2]; # creg c[2]; # u3(0.518016983430947*pi,1.37051598592907*pi,1.36816383603222*pi) q[0]; # u3(1.69867232277986*pi,0.371448347747471*pi,0.461117217930936*pi) q[0]; # u3(0.294319836336836*pi,0.450325871124225*pi,1.46804720442555*pi) q[0]; # measure q -> c; # took >630 seconds (did not complete) to optimize without # calling evalf() at all, 19 seconds to optimize calling # evalf() AFTER compose_u3, and 1 second to optimize # calling evalf() BEFORE compose_u3. # 1. Here down, when we simplify, we add f(theta) to lambda to # correct the global phase when f(theta) is 2*pi. This isn't # necessary but the other steps preserve the global phase, so # we continue in that manner. # 2. The final step will remove Z rotations by 2*pi. # 3. Note that is_zero is true only if the expression is exactly # zero. If the input expressions have already been evaluated # then these final simplifications will not occur. # TODO After we refactor, we should have separate passes for # exact and approximate rewriting. # Y rotation is 0 mod 2*pi, so the gate is a u1 if abs(np.mod(right_parameters[0], (2 * np.pi))) < self.eps and right_name != "u1" \ and right_name != "p": if use_p: right_name = "p" else: right_name = "u1" right_parameters = (0, 0, right_parameters[1] + right_parameters[2] + right_parameters[0]) # Y rotation is pi/2 or -pi/2 mod 2*pi, so the gate is a u2 if right_name == "u3" or 'u': # theta = pi/2 + 2*k*pi right_angle = right_parameters[0] - np.pi / 2 if abs(right_angle) < self.eps: right_angle = 0 if abs(np.mod((right_angle), 2 * np.pi)) < self.eps: right_name = "u2" right_parameters = (np.pi / 2, right_parameters[1], right_parameters[2] + (right_parameters[0] - np.pi / 2)) # theta = -pi/2 + 2*k*pi right_angle = right_parameters[0] + np.pi / 2 if abs(right_angle) < self.eps: right_angle = 0 if abs(np.mod(right_angle, 2 * np.pi)) < self.eps: right_name = "u2" right_parameters = (np.pi / 2, right_parameters[1] + np.pi, right_parameters[2] - np.pi + (right_parameters[0] + np.pi / 2)) # u1 and lambda is 0 mod 2*pi so gate is nop (up to a global phase) if right_name in ("u1", "p") and abs( np.mod(right_parameters[2], 2 * np.pi)) < self.eps: right_name = "nop" if right_name == "u2" and "u2" not in self.basis: if use_u: right_name = 'u' else: right_name = "u3" if right_name in ("u1", "p") and right_name not in self.basis: if use_u: right_name = 'u' else: right_name = "u3" new_op = Gate(name="", num_qubits=1, params=[]) if right_name == "u1": new_op = U1Gate(right_parameters[2]) if right_name == "p": new_op = PhaseGate(right_parameters[2]) if right_name == "u2": new_op = U2Gate(right_parameters[1], right_parameters[2]) if right_name == "u": if "u" in self.basis: new_op = UGate(*right_parameters) if right_name == "u3": if "u3" in self.basis: new_op = U3Gate(*right_parameters) else: raise TranspilerError('It was not possible to use the basis %s' % self.basis) dag.global_phase += right_global_phase if right_name != 'nop': dag.substitute_node(run[0], new_op, inplace=True) # Delete the other nodes in the run for current_node in run[1:]: dag.remove_op_node(current_node) if right_name == "nop": dag.remove_op_node(run[0]) return dag