def two_qubit_matrix_to_operations( q0: ops.QubitId, q1: ops.QubitId, mat: np.ndarray, allow_partial_czs: bool, tolerance: float = 1e-8, clean_operations: bool = True, ) -> List[ops.Operation]: """Decomposes a two-qubit operation into Z/XY/CZ gates. Args: q0: The first qubit being operated on. q1: The other qubit being operated on. mat: Defines the operation to apply to the pair of qubits. allow_partial_czs: Enables the use of Partial-CZ gates. tolerance: A limit on the amount of error introduced by the construction. clean_operations: Enables optimizing resulting operation list by merging operations and ejecting phased Paulis and Z operations. Returns: A list of operations implementing the matrix. """ kak = linalg.kak_decomposition(mat, linalg.Tolerance(atol=tolerance)) operations = _kak_decomposition_to_operations(q0, q1, kak, allow_partial_czs, tolerance) if clean_operations: return _cleanup_operations(operations) else: return operations
def two_qubit_matrix_to_operations(q0: ops.QubitId, q1: ops.QubitId, mat: np.ndarray, allow_partial_czs: bool, tolerance: float = 1e-8 ) -> List[ops.Operation]: """Decomposes a two-qubit operation into Z/XY/CZ gates. Args: q0: The first qubit being operated on. q1: The other qubit being operated on. mat: Defines the operation to apply to the pair of qubits. allow_partial_czs: Enables the use of Partial-CZ gates. tolerance: A limit on the amount of error introduced by the construction. Returns: A list of operations implementing the matrix. """ _, (a0, a1), (x, y, z), (b0, b1) = linalg.kak_decomposition( mat, linalg.Tolerance(atol=tolerance)) # TODO: Clean up angles before returning return _kak_decomposition_to_operations(q0, q1, a0, a1, x, y, z, b0, b1, allow_partial_czs, tolerance)
def from_matrix(mat: np.array, tolerance=1e-8) -> 'QasmTwoQubitGate': _, (a1, a0), (x, y, z), (b1, b0) = linalg.kak_decomposition( mat, linalg.Tolerance(atol=tolerance)) before0 = QasmUGate.from_matrix(b0) before1 = QasmUGate.from_matrix(b1) after0 = QasmUGate.from_matrix(a0) after1 = QasmUGate.from_matrix(a1) return QasmTwoQubitGate(before0, before1, x, y, z, after0, after1)
def from_matrix(mat: np.array, atol=1e-8) -> 'QasmTwoQubitGate': """Creates a QasmTwoQubitGate from the given matrix. Args: mat: The unitary matrix of the two qubit gate. atol: Absolute error tolerance when decomposing. Returns: A QasmTwoQubitGate implementing the matrix. """ kak = linalg.kak_decomposition(mat, linalg.Tolerance(atol=atol)) return QasmTwoQubitGate(kak)
def from_matrix(mat: np.array, tolerance=1e-8) -> 'QasmTwoQubitGate': kak = linalg.kak_decomposition(mat, linalg.Tolerance(atol=tolerance)) a1, a0 = kak.single_qubit_operations_after b1, b0 = kak.single_qubit_operations_before x, y, z = kak.interaction_coefficients before0 = QasmUGate.from_matrix(b0) before1 = QasmUGate.from_matrix(b1) after0 = QasmUGate.from_matrix(a0) after1 = QasmUGate.from_matrix(a1) return QasmTwoQubitGate(before0, before1, x, y, z, after0, after1)
def __init__(self, ignore_failures: bool = False, tolerance: float = 0) -> None: """ Args: ignore_failures: If set, gates that fail to convert are forwarded unchanged. If not set, conversion failures raise a TypeError. tolerance: Maximum absolute error tolerance. The optimization is permitted to round angles with a threshold determined by this tolerance. """ super().__init__() self.ignore_failures = ignore_failures self.tolerance = tolerance self._tol = linalg.Tolerance(atol=tolerance)
def __init__(self, ignore_failures: bool = False, tolerance: float = 0, extensions: extension.Extensions = None) -> None: """ Args: ignore_failures: If set, gates that fail to convert are forwarded unchanged. If not set, conversion failures raise a TypeError. tolerance: Maximum absolute error tolerance. The optimization is permitted to round angles with a threshold determined by this tolerance. extensions: The extensions instance to use when trying to cast gates to known types. """ self.extensions = extensions or extension.Extensions() self.ignore_failures = ignore_failures self.tolerance = tolerance self._tol = linalg.Tolerance(atol=tolerance)
def single_qubit_matrix_to_pauli_rotations( mat: np.ndarray, tolerance: float = 0) -> List[Tuple[ops.Pauli, float]]: """Implements a single-qubit operation with few rotations. Args: mat: The 2x2 unitary matrix of the operation to implement. tolerance: A limit on the amount of error introduced by the construction. Returns: A list of (Pauli, half_turns) tuples that, when applied in order, perform the desired operation. """ tol = linalg.Tolerance(atol=tolerance) def is_clifford_rotation(half_turns): return tol.all_near_zero_mod(half_turns, 0.5) def to_quarter_turns(half_turns): return round(2 * half_turns) % 4 def is_quarter_turn(half_turns): return (is_clifford_rotation(half_turns) and to_quarter_turns(half_turns) % 2 == 1) def is_half_turn(half_turns): return (is_clifford_rotation(half_turns) and to_quarter_turns(half_turns) == 2) def is_no_turn(half_turns): return (is_clifford_rotation(half_turns) and to_quarter_turns(half_turns) == 0) # Decompose matrix z_rad_before, y_rad, z_rad_after = ( linalg.deconstruct_single_qubit_matrix_into_angles(mat)) z_ht_before = z_rad_before / np.pi - 0.5 m_ht = y_rad / np.pi m_pauli = ops.Pauli.X z_ht_after = z_rad_after / np.pi + 0.5 # Clean up angles if is_clifford_rotation(z_ht_before): if ((is_quarter_turn(z_ht_before) or is_quarter_turn(z_ht_after)) ^ (is_half_turn(m_ht) and is_no_turn(z_ht_before - z_ht_after))): z_ht_before += 0.5 z_ht_after -= 0.5 m_pauli = ops.Pauli.Y if is_half_turn(z_ht_before) or is_half_turn(z_ht_after): z_ht_before -= 1 z_ht_after += 1 m_ht = -m_ht if is_no_turn(m_ht): z_ht_before += z_ht_after z_ht_after = 0 elif is_half_turn(m_ht): z_ht_after -= z_ht_before z_ht_before = 0 # Generate operations rotation_list = [(ops.Pauli.Z, z_ht_before), (m_pauli, m_ht), (ops.Pauli.Z, z_ht_after)] return [(pauli, ht) for pauli, ht in rotation_list if not is_no_turn(ht)]
def converted_gate_set(circuit: circuits.Circuit, atol: float = 1e-7) -> circuits.Circuit: """Returns a new, equivalent circuit using the gate set {CliffordGate, PauliInteractionGate, PauliStringPhasor}. The circuit structure may differ because it is optimized during conversion. """ xmon_circuit = google.optimized_for_xmon(circuit, allow_partial_czs=False) qubits = circuit.all_qubits() tol = linalg.Tolerance(atol=atol) def is_clifford_rotation(half_turns): return tol.all_near_zero_mod(half_turns, 0.5) def to_quarter_turns(half_turns): return round(2 * half_turns) % 4 def is_quarter_turn(half_turns): return (is_clifford_rotation(half_turns) and to_quarter_turns(half_turns) % 2 == 1) def is_half_turn(half_turns): return (is_clifford_rotation(half_turns) and to_quarter_turns(half_turns) == 2) def is_no_turn(half_turns): return (is_clifford_rotation(half_turns) and to_quarter_turns(half_turns) == 0) def rotation_to_clifford_op(pauli, qubit, half_turns): quarter_turns = to_quarter_turns(half_turns) if quarter_turns == 0: return ops.CliffordGate.I(qubit) elif quarter_turns == 2: return ops.CliffordGate.from_pauli(pauli)(qubit) else: gate = ops.CliffordGate.from_pauli(pauli, True)(qubit) if quarter_turns == 3: gate = gate.inverse() return gate def rotation_to_non_clifford_op(pauli, qubit, half_turns): return PauliStringPhasor(ops.PauliString.from_single(qubit, pauli), half_turns=half_turns) def single_qubit_matrix_to_ops(mat, qubit): # Decompose matrix z_rad_before, y_rad, z_rad_after = ( linalg.deconstruct_single_qubit_matrix_into_angles(mat)) z_ht_before = z_rad_before / np.pi - 0.5 m_ht = y_rad / np.pi m_pauli = ops.Pauli.X z_ht_after = z_rad_after / np.pi + 0.5 # Clean up angles if is_clifford_rotation(z_ht_before): if is_quarter_turn(z_ht_before) or is_quarter_turn(z_ht_after): z_ht_before += 0.5 z_ht_after -= 0.5 m_pauli = ops.Pauli.Y if is_half_turn(z_ht_before) or is_half_turn(z_ht_after): z_ht_before -= 1 z_ht_after += 1 m_ht = -m_ht if is_no_turn(m_ht): z_ht_before += z_ht_after z_ht_after = 0 elif is_half_turn(m_ht): z_ht_after -= z_ht_before z_ht_before = 0 # Generate operations rotation_list = [(ops.Pauli.Z, z_ht_before), (m_pauli, m_ht), (ops.Pauli.Z, z_ht_after)] is_clifford_list = [ is_clifford_rotation(ht) for pauli, ht in rotation_list ] op_list = [ rotation_to_clifford_op(pauli, qubit, ht) if is_clifford else rotation_to_non_clifford_op(pauli, qubit, ht) for is_clifford, (pauli, ht) in zip(is_clifford_list, rotation_list) ] # Merge adjacent Clifford gates for i in reversed(range(len(op_list) - 1)): if is_clifford_list[i] and is_clifford_list[i + 1]: op_list[i] = op_list[i].gate.merged_with( op_list[i + 1].gate)(qubit) is_clifford_list.pop(i + 1) op_list.pop(i + 1) # Yield non-identity ops for is_clifford, op in zip(is_clifford_list, op_list): if is_clifford and op.gate == ops.CliffordGate.I: continue yield op def generate_ops(): conv_cz = ops.PauliInteractionGate(ops.Pauli.Z, False, ops.Pauli.Z, False) matrices = {qubit: np.eye(2) for qubit in qubits} def clear_matrices(qubits): for qubit in qubits: yield single_qubit_matrix_to_ops(matrices[qubit], qubit) matrices[qubit] = np.eye(2) for op in xmon_circuit.all_operations(): # Assumes all Xmon operations are GateOperation gate = op.gate if isinstance(gate, google.XmonMeasurementGate): yield clear_matrices(op.qubits) yield ops.MeasurementGate(gate.key, gate.invert_mask)(*op.qubits) elif len(op.qubits) == 1: # Collect all one qubit rotations # Assumes all Xmon gates implement KnownMatrix qubit, = op.qubits matrices[qubit] = op.matrix().dot(matrices[qubit]) elif isinstance(gate, google.Exp11Gate): yield clear_matrices(op.qubits) if gate.half_turns != 1: # coverage: ignore raise ValueError('Unexpected partial CZ: {}'.format(op)) yield conv_cz(*op.qubits) else: # coverage: ignore raise TypeError('Unknown Xmon operation: {}'.format(op)) yield clear_matrices(qubits) return circuits.Circuit.from_ops(generate_ops(), strategy=circuits.InsertStrategy.EARLIEST)