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 _fix_single_qubit_gates_around_kak_interaction( *, desired: 'cirq.KakDecomposition', operations: List['cirq.Operation'], qubits: Sequence['cirq.Qid'], ) -> Iterator['cirq.Operation']: """Adds single qubit operations to complete a desired interaction. Args: desired: The kak decomposition of the desired operation. qubits: The pair of qubits that is being operated on. operations: A list of operations that composes into the desired kak interaction coefficients, but may not have the desired before/after single qubit operations or the desired global phase. Returns: A list of operations whose kak decomposition approximately equals the desired kak decomposition. """ actual = linalg.kak_decomposition(circuits.Circuit(operations)) def dag(a: np.ndarray) -> np.ndarray: return np.transpose(np.conjugate(a)) for k in range(2): g = ops.MatrixGate( dag(actual.single_qubit_operations_before[k]) @ desired.single_qubit_operations_before[k]) yield g(qubits[k]) yield from operations for k in range(2): g = ops.MatrixGate(desired.single_qubit_operations_after[k] @ dag( actual.single_qubit_operations_after[k])) yield g(qubits[k]) yield ops.GlobalPhaseOperation(desired.global_phase / actual.global_phase)
def two_qubit_matrix_to_operations( q0: 'cirq.Qid', q1: 'cirq.Qid', mat: np.ndarray, allow_partial_czs: bool, atol: 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. atol: A limit on the amount of absolute 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, atol=atol) operations = _kak_decomposition_to_operations(q0, q1, kak, allow_partial_czs, atol=atol) if clean_operations: return _cleanup_operations(operations) return operations
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 _outer_locals_for_unitary( target: np.ndarray, base: np.ndarray ) -> Tuple[_SingleQubitGatePair, _SingleQubitGatePair, np.ndarray]: """Local unitaries mapping between locally equivalent 2-local unitaries. Finds the left and right 1-local unitaries kL, kR such that U_target = kL @ U_base @ kR Args: target: The unitary to which we want to map. base: The base unitary which maps to target. Returns: kR: The right 1-local unitaries in the equation above, expressed as 2-tuples of (2x2) single qubit unitaries. kL: The left 1-local unitaries in the equation above, expressed as 2-tuples of (2x2) single qubit unitaries. actual: The outcome of kL @ base @ kR """ target_decomp = linalg.kak_decomposition(target) base_decomp = linalg.kak_decomposition(base) # From the KAK decomposition, we have # kLt At kRt = kL kLb Ab KRb kR # If At=Ab, we can solve for kL and kR as # kLt = kL kLb --> kL = kLt kLb^\dagger # kRt = kRb kR --> kR = kRb\dagger kRt # 0 and 1 are qubit indices. kLt0, kLt1 = target_decomp.single_qubit_operations_after kLb0, kLb1 = base_decomp.single_qubit_operations_after kL = kLt0 @ kLb0.conj().T, kLt1 @ kLb1.conj().T kRt0, kRt1 = target_decomp.single_qubit_operations_before kRb0, kRb1 = base_decomp.single_qubit_operations_before kR = kRb0.conj().T @ kRt0, kRb1.conj().T @ kRt1 actual = np.kron(*kL) @ base actual = actual @ np.kron(*kR) actual *= np.conj(target_decomp.global_phase) return kR, kL, actual
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, atol=atol) return QasmTwoQubitGate(kak)
def _decompose_two_qubit_interaction_into_two_b_gates( interaction: Union['cirq.Operation', 'cirq.Gate', np.ndarray, Any], *, qubits: Sequence['cirq.Qid']) -> List['cirq.Operation']: kak = linalg.kak_decomposition(interaction) result = _decompose_interaction_into_two_b_gates_ignoring_single_qubit_ops( qubits, kak.interaction_coefficients) return list( _fix_single_qubit_gates_around_kak_interaction(desired=kak, qubits=qubits, operations=result))
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 _decompose_b_gate_into_two_fsims( *, fsim_gate: 'cirq.FSimGate', qubits: Sequence['cirq.Qid']) -> List['cirq.Operation']: kak = linalg.kak_decomposition(_B) result = _decompose_xx_yy_into_two_fsims_ignoring_single_qubit_ops( qubits=qubits, fsim_gate=fsim_gate, canonical_x_kak_coefficient=kak.interaction_coefficients[0], canonical_y_kak_coefficient=kak.interaction_coefficients[1]) return list( _fix_single_qubit_gates_around_kak_interaction(desired=kak, qubits=qubits, operations=result))
def two_qubit_matrix_to_ion_operations( q0: 'cirq.Qid', q1: 'cirq.Qid', mat: np.ndarray, atol: float = 1e-8 ) -> List[ops.Operation]: """Decomposes a two-qubit operation into MS/single-qubit rotation 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. atol: A limit on the amount of error introduced by the construction. Returns: A list of operations implementing the matrix. """ kak = linalg.kak_decomposition(mat, atol=atol) operations = _kak_decomposition_to_operations(q0, q1, kak, atol) return _cleanup_operations(operations)
def _decompose_two_qubit(self, operation: 'cirq.Operation') -> ops.OP_TREE: """Decomposes a two qubit gate into XXPow, YYPow, and ZZPow plus single qubit gates.""" mat = protocols.unitary(operation) kak = linalg.kak_decomposition(mat, check_preconditions=False) for qubit, mat in zip(operation.qubits, kak.single_qubit_operations_before): gates = optimizers.single_qubit_matrix_to_gates(mat, self.atol) for gate in gates: yield gate(qubit) two_qubit_gates = [parity_gates.XX, parity_gates.YY, parity_gates.ZZ] for two_qubit_gate, coefficient in zip(two_qubit_gates, kak.interaction_coefficients): yield (two_qubit_gate**(-coefficient * 2 / np.pi))(*operation.qubits) for qubit, mat in zip(operation.qubits, kak.single_qubit_operations_after): for gate in optimizers.single_qubit_matrix_to_gates( mat, self.atol): yield gate(qubit)
def test_kak_decomposition(m): g, (a1, a0), (x, y, z), (b1, b0) = linalg.kak_decomposition(m) m2 = recompose_kak(g, (a1, a0), (x, y, z), (b1, b0)) assert np.allclose(m, m2)
def decompose_two_qubit_interaction_into_four_fsim_gates_via_b( interaction: Union['cirq.Operation', 'cirq.Gate', np.ndarray, Any], *, fsim_gate: 'cirq.FSimGate', qubits: Sequence['cirq.Qid'] = None) -> 'cirq.Circuit': """Decomposes operations into an FSimGate near theta=pi/2, phi=0. This decomposition is guaranteed to use exactly four of the given FSim gates. It works by decomposing into two B gates and then decomposing each B gate into two of the given FSim gate. This decomposition only works for FSim gates with a theta (iswap angle) between 3/8π and 5/8π (i.e. within 22.5° of maximum strength) and a phi (cphase angle) between -π/4 and +π/4 (i.e. within 45° of minimum strength). Args: interaction: The two qubit operation to synthesize. This can either be a cirq object (such as a gate, operation, or circuit) or a raw numpy array specifying the 4x4 unitary matrix. fsim_gate: The only two qubit gate that is permitted to appear in the output. Must satisfy 3/8π < phi < 5/8π and abs(theta) < pi/4. qubits: The qubits that the resulting operations should apply the desired interaction to. If not set then defaults to either the qubits of the given interaction (if it is a `cirq.Operation`) or else to `cirq.LineQubit.range(2)`. Returns: A list of operations implementing the desired two qubit unitary. The list will include four operations of the given fsim gate, various single qubit operations, and a global phase operation. """ if not 3 / 8 * np.pi <= fsim_gate.theta <= 5 / 8 * np.pi: raise ValueError('Must have 3π/8 ≤ fsim_gate.theta ≤ 5π/8') if abs(fsim_gate.phi) > np.pi / 4: raise ValueError('Must have abs(fsim_gate.phi) ≤ π/4') if qubits is None: if isinstance(interaction, ops.Operation): qubits = interaction.qubits else: qubits = devices.LineQubit.range(2) if len(qubits) != 2: raise ValueError(f'Expected a pair of qubits, but got {qubits!r}.') kak = linalg.kak_decomposition(interaction) result_using_b_gates = _decompose_two_qubit_interaction_into_two_b_gates( kak, qubits=qubits) b_decomposition = _decompose_b_gate_into_two_fsims(fsim_gate=fsim_gate, qubits=qubits) result = [] for op in result_using_b_gates: if isinstance(op.gate, _BGate): result.extend(b_decomposition) else: result.append(op) circuit = circuits.Circuit(result) merge_single_qubit_gates.MergeSingleQubitGates().optimize_circuit(circuit) drop_empty_moments.DropEmptyMoments().optimize_circuit(circuit) return circuit
def _decomp_2sqrt_iswap_matrices( kak: 'cirq.KakDecomposition', atol: float = 1e-8, ) -> Tuple[Sequence[Tuple[np.ndarray, np.ndarray]], complex]: """Returns the single-qubit matrices for the 2-SQRT_ISWAP decomposition. Assumes canonical x, y, z and x >= y + |z| within tolerance. For x, y, z that violate this inequality, three sqrt-iSWAP gates are required. References: Towards ultra-high fidelity quantum operations: SQiSW gate as a native two-qubit gate https://arxiv.org/abs/2105.06074 """ # Follows the if-branch of procedure DECOMP(U) in Algorithm 1 of the paper x, y, z = kak.interaction_coefficients b0, b1 = kak.single_qubit_operations_before a0, a1 = kak.single_qubit_operations_after # Computed gate parameters: Eq. 4, 6, 7, 8 of the paper # range limits added for robustness to numerical error def safe_arccos(v): return np.arccos(np.clip(v, -1, 1)) def nonzero_sign(v): return -1 if v < 0 else 1 _c = np.clip( np.sin(x + y - z) * np.sin(x - y + z) * np.sin(-x - y - z) * np.sin(-x + y + z), 0, 1 ) alpha = safe_arccos(np.cos(2 * x) - np.cos(2 * y) + np.cos(2 * z) + 2 * np.sqrt(_c)) beta = safe_arccos(np.cos(2 * x) - np.cos(2 * y) + np.cos(2 * z) - 2 * np.sqrt(_c)) # Don't need to limit this value because it will always be positive and the clip in the # following `safe_arccos` handles the cases where this could be slightly greater than 1. _4ccs = 4 * (np.cos(x) * np.cos(z) * np.sin(y)) ** 2 # Intermediate value gamma = safe_arccos( nonzero_sign(z) * np.sqrt(_4ccs / (_4ccs + np.clip(np.cos(2 * x) * np.cos(2 * y) * np.cos(2 * z), 0, 1))) ) # Inner single-qubit gates: Fig. 4 of the paper # Gate angles here are multiplied by -2 to adjust for non-standard gate definitions in the paper c0 = ( protocols.unitary(ops.rz(-gamma)) @ protocols.unitary(ops.rx(-alpha)) @ protocols.unitary(ops.rz(-gamma)) ) c1 = protocols.unitary(ops.rx(-beta)) # Compute KAK on the decomposition to determine outer single-qubit gates # There is no known closed form solution for these gates u_sqrt_iswap = protocols.unitary(ops.SQRT_ISWAP) u = u_sqrt_iswap @ np.kron(c0, c1) @ u_sqrt_iswap # Unitary of decomposition kak_fix = linalg.kak_decomposition(u, atol=atol / 10, rtol=0, check_preconditions=False) e0, e1 = kak_fix.single_qubit_operations_before d0, d1 = kak_fix.single_qubit_operations_after return [ # Pairs of single-qubit unitaries, SQRT_ISWAP between each is implied (e0.T.conj() @ b0, e1.T.conj() @ b1), (c0, c1), (a0 @ d0.T.conj(), a1 @ d1.T.conj()), ], kak.global_phase / kak_fix.global_phase
def two_qubit_matrix_to_sqrt_iswap_operations( q0: 'cirq.Qid', q1: 'cirq.Qid', mat: np.ndarray, *, required_sqrt_iswap_count: Optional[int] = None, use_sqrt_iswap_inv: bool = False, atol: float = 1e-8, check_preconditions: bool = True, clean_operations: bool = False, ) -> Sequence['cirq.Operation']: """Decomposes a two-qubit operation into ZPow/XPow/YPow/sqrt-iSWAP gates. This method uses the KAK decomposition of the matrix to determine how many sqrt-iSWAP gates are needed and which single-qubit gates to use in between each sqrt-iSWAP. All operations can be synthesized with exactly three sqrt-iSWAP gates and about 79% of operations (randomly chosen under the Haar measure) can also be synthesized with two sqrt-iSWAP gates. Only special cases locally equivalent to identity or sqrt-iSWAP can be synthesized with zero or one sqrt-iSWAP gates respectively. Unless ``required_sqrt_iswap_count`` is specified, the fewest possible number of sqrt-iSWAP will be used. 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. required_sqrt_iswap_count: When specified, exactly this many sqrt-iSWAP gates will be used even if fewer is possible (maximum 3). Raises ``ValueError`` if impossible. use_sqrt_iswap_inv: If True, returns a decomposition using ``SQRT_ISWAP_INV`` gates instead of ``SQRT_ISWAP``. This decomposition is identical except for the addition of single-qubit Z gates. atol: A limit on the amount of absolute error introduced by the construction. check_preconditions: If set, verifies that the input corresponds to a 4x4 unitary before decomposing. clean_operations: Merges runs of single qubit gates to a single `cirq.PhasedXZGate` in the resulting operations list. Returns: A list of operations implementing the matrix including at most three ``SQRT_ISWAP`` (sqrt-iSWAP) gates and ZPow, XPow, and YPow single-qubit gates. Raises: ValueError: If ``required_sqrt_iswap_count`` is specified, the minimum number of sqrt-iSWAP gates needed to decompose the given matrix is greater than ``required_sqrt_iswap_count``. References: Towards ultra-high fidelity quantum operations: SQiSW gate as a native two-qubit gate https://arxiv.org/abs/2105.06074 """ kak = linalg.kak_decomposition( mat, atol=atol / 10, rtol=0, check_preconditions=check_preconditions ) operations = _kak_decomposition_to_sqrt_iswap_operations( q0, q1, kak, required_sqrt_iswap_count, use_sqrt_iswap_inv, atol=atol ) return ( [*merge_single_qubit_gates_to_phxz(circuits.Circuit(operations)).all_operations()] if clean_operations else operations )
def decompose_two_qubit_interaction_into_four_fsim_gates( interaction: Union['cirq.SupportsUnitary', np.ndarray], *, fsim_gate: Union['cirq.FSimGate', 'cirq.ISwapPowGate'], qubits: Sequence['cirq.Qid'] = None, ) -> 'cirq.Circuit': """Decomposes operations into an FSimGate near theta=pi/2, phi=0. This decomposition is guaranteed to use exactly four of the given FSim gates. It works by decomposing into two B gates and then decomposing each B gate into two of the given FSim gate. This decomposition only works for FSim gates with a theta (iswap angle) between 3/8π and 5/8π (i.e. within 22.5° of maximum strength) and a phi (cphase angle) between -π/4 and +π/4 (i.e. within 45° of minimum strength). Args: interaction: The two qubit operation to synthesize. This can either be a cirq object (such as a gate, operation, or circuit) or a raw numpy array specifying the 4x4 unitary matrix. fsim_gate: The only two qubit gate that is permitted to appear in the output. Must satisfy 3/8π < phi < 5/8π and abs(theta) < pi/4. qubits: The qubits that the resulting operations should apply the desired interaction to. If not set then defaults to either the qubits of the given interaction (if it is a `cirq.Operation`) or else to `cirq.LineQubit.range(2)`. Returns: A list of operations implementing the desired two qubit unitary. The list will include four operations of the given fsim gate, various single qubit operations, and a global phase operation. Raises: ValueError: If the `fsim_gate` has invalid angles or is parameterized, or if the supplied target to synthesize acts on more than two qubits. """ if protocols.is_parameterized(fsim_gate): raise ValueError( "FSimGate must not have parameterized values for angles.") if isinstance(fsim_gate, ops.ISwapPowGate): mapped_gate = ops.FSimGate(-fsim_gate.exponent * np.pi / 2, 0) else: mapped_gate = fsim_gate if not 3 / 8 * np.pi <= abs(mapped_gate.theta) <= 5 / 8 * np.pi: raise ValueError('Must have 3π/8 ≤ |fsim_gate.theta| ≤ 5π/8') if abs(mapped_gate.phi) > np.pi / 4: raise ValueError('Must have abs(fsim_gate.phi) ≤ π/4') if qubits is None: if isinstance(interaction, ops.Operation): qubits = interaction.qubits else: qubits = devices.LineQubit.range(2) if len(qubits) != 2: raise ValueError(f'Expected a pair of qubits, but got {qubits!r}.') kak = linalg.kak_decomposition(interaction) result_using_b_gates = _decompose_two_qubit_interaction_into_two_b_gates( kak, qubits=qubits) b_decomposition = _decompose_b_gate_into_two_fsims(fsim_gate=mapped_gate, qubits=qubits) b_decomposition = [ fsim_gate(*op.qubits) if op.gate == mapped_gate else op for op in b_decomposition ] result = circuits.Circuit() for op in result_using_b_gates: if isinstance(op.gate, _BGate): result.append(b_decomposition) else: result.append(op) return result