def _unitary_(self) -> Union[np.ndarray, NotImplementedType]: qubits = cirq.LineQid.for_gate(self) op = self.sub_gate.on(*qubits[self.num_controls() :]) c_op = cop.ControlledOperation(qubits[: self.num_controls()], op, self.control_values) return protocols.unitary(c_op, default=NotImplemented)
def _unitary_(self) -> Union[np.ndarray, NotImplementedType]: return protocols.unitary(self.sub_operation, NotImplemented)
def _unitary_(self) -> np.ndarray: mat = np.eye(2) qubit = named_qubit.NamedQubit('arbitrary') for op in protocols.decompose_once_with_qubits(self, (qubit, )): mat = protocols.unitary(op).dot(mat) return mat
def _translate_one_qubit_cirq_operation_to_braket_instruction( op: Union[np.ndarray, "cirq.Operation"], target: Optional[int] = None, ) -> List[Instruction]: """Translates a one-qubit Cirq operation to a (sequence of) Braket instruction(s) according to the following rules: 1. Attempts to find a "standard translation" from Cirq to Braket. - e.g., checks if `op` is Pauli-X and, if so, returns the Braket X. 2. If (1) is not successful, decomposes the unitary of `op` to Rz(theta) Ry(phi) Rz(lambda) and returns the series of rotations as Braket instructions. Args: op: One-qubit Cirq operation to translate. target: Qubit index for the op to act on. Must be specified and if only if `op` is given as a numpy array. """ # Translate qubit index. if not isinstance(op, np.ndarray): target = op.qubits[0].x if target is None: raise ValueError( "Arg `target` must be specified when `op` is a matrix." ) # Check common single-qubit gates. if isinstance(op, cirq_ops.Operation): if isinstance(op.gate, cirq_ops.XPowGate): exponent = op.gate.exponent if np.isclose(exponent, 1.0) or np.isclose(exponent, -1.0): return [Instruction(braket_gates.X(), target)] elif np.isclose(exponent, 0.5): return [Instruction(braket_gates.V(), target)] elif np.isclose(exponent, -0.5): return [Instruction(braket_gates.Vi(), target)] return [Instruction(braket_gates.Rx(exponent * np.pi), target)] elif isinstance(op.gate, cirq_ops.YPowGate): exponent = op.gate.exponent if np.isclose(exponent, 1.0) or np.isclose(exponent, -1.0): return [Instruction(braket_gates.Y(), target)] return [Instruction(braket_gates.Ry(exponent * np.pi), target)] elif isinstance(op.gate, cirq_ops.ZPowGate): exponent = op.gate.exponent if np.isclose(exponent, 1.0) or np.isclose(exponent, -1.0): return [Instruction(braket_gates.Z(), target)] elif np.isclose(exponent, 0.5): return [Instruction(braket_gates.S(), target)] elif np.isclose(exponent, -0.5): return [Instruction(braket_gates.Si(), target)] elif np.isclose(exponent, 0.25): return [Instruction(braket_gates.T(), target)] elif np.isclose(exponent, -0.25): return [Instruction(braket_gates.Ti(), target)] return [Instruction(braket_gates.Rz(exponent * np.pi), target)] elif isinstance(op.gate, cirq_ops.HPowGate) and np.isclose( abs(op.gate.exponent), 1.0 ): return [Instruction(braket_gates.H(), target)] # Arbitrary single-qubit unitary decomposition. # TODO: This does not account for global phase. if isinstance(op, cirq_ops.Operation): unitary_matrix = protocols.unitary(op) else: unitary_matrix = op a, b, c = deconstruct_single_qubit_matrix_into_angles(unitary_matrix) return [ Instruction(braket_gates.Rz(a), target), Instruction(braket_gates.Ry(b), target), Instruction(braket_gates.Rz(c), target), ]
def _unitary_(self) -> np.ndarray: mat = np.eye(2) qubit = raw_types.QubitId() for op in op_tree.flatten_op_tree(self.default_decompose((qubit, ))): mat = protocols.unitary(op).dot(mat) return mat
def _unitary_(self) -> Union[np.ndarray, NotImplementedType]: sub_matrix = protocols.unitary(self.sub_operation, None) if sub_matrix is None: return NotImplemented return self._extend_matrix(sub_matrix)
def scatter_plot_normalized_kak_interaction_coefficients( interactions: Iterable[Union[np.ndarray, 'cirq.SupportsUnitary', 'KakDecomposition']], *, include_frame: bool = True, ax: Optional[plt.Axes] = None, **kwargs, ): r"""Plots the interaction coefficients of many two-qubit operations. Plots: A point for the (x, y, z) normalized interaction coefficients of each interaction from the given interactions. The (x, y, z) coordinates are normalized so that the maximum value is at 1 instead of at pi/4. If `include_frame` is set to True, then a black wireframe outline of the canonicalized normalized KAK coefficient space. The space is defined by the following two constraints: 0 <= abs(z) <= y <= x <= 1 if x = 1 then z >= 0 The wireframe includes lines along the surface of the space at z=0. The space is a prism with the identity at the origin, a crease along y=z=0 leading to the CZ/CNOT at x=1 and a vertical triangular face that contains the iswap at x=y=1,z=0 and the swap at x=y=z=1: (x=1,y=1,z=0) swap___iswap___swap (x=1,y=1,z=+-1) _/\ | / _/ \ | / _/ \ | / _/ \ | / _/ \|/ (x=0,y=0,z=0) I---------------CZ (x=1,y=0,z=0) Args: interactions: An iterable of two qubit unitary interactions. Each interaction can be specified as a raw 4x4 unitary matrix, or an object with a 4x4 unitary matrix according to `cirq.unitary` ( (e.g. `cirq.CZ` or a `cirq.KakDecomposition` or a `cirq.Circuit` over two qubits). include_frame: Determines whether or not to draw the kak space wireframe. Defaults to `True`. ax: A matplotlib 3d axes object to plot into. If not specified, a new figure is created, plotted, and shown. **kwargs: Arguments forwarded into the call to `scatter` that plots the points. Working arguments include color `c='blue'`, scale `s=2`, labelling `label="theta=pi/4"`, etc. For reference see the `matplotlib.pyplot.scatter` documentation: https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.scatter.html Returns: The matplotlib 3d axes object that was plotted into. Examples: >>> ax = None >>> for y in np.linspace(0, 0.5, 4): ... a, b = cirq.LineQubit.range(2) ... circuits = [ ... cirq.Circuit( ... cirq.CZ(a, b)**0.5, ... cirq.X(a)**y, cirq.X(b)**x, ... cirq.CZ(a, b)**0.5, ... cirq.X(a)**x, cirq.X(b)**y, ... cirq.CZ(a, b) ** 0.5, ... ) ... for x in np.linspace(0, 1, 25) ... ] ... ax = cirq.scatter_plot_normalized_kak_interaction_coefficients( ... circuits, ... include_frame=ax is None, ... ax=ax, ... s=1, ... label=f'y={y:0.2f}') >>> _ = ax.legend() >>> import matplotlib.pyplot as plt >>> plt.show() """ show_plot = not ax if not ax: fig = plt.figure() ax = fig.add_subplot(1, 1, 1, projection='3d') def coord_transform( pts: Sequence[Tuple[float, float, float]] ) -> Tuple[Iterable[float], Iterable[float], Iterable[float]]: if len(pts) == 0: return [], [], [] xs, ys, zs = zip(*pts) return xs, zs, ys if include_frame: envelope = [ (0, 0, 0), (1, 1, 1), (1, 1, -1), (0, 0, 0), (1, 1, 1), (1, 0, 0), (0, 0, 0), (1, 1, -1), (1, 0, 0), (0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 0, 0), ] ax.plot(*coord_transform(envelope), c='black', linewidth=1) # parse input and extract KAK vector if not isinstance(interactions, np.ndarray): interactions_extracted: List[np.ndarray] = [ a if isinstance(a, np.ndarray) else protocols.unitary(a) for a in interactions ] else: interactions_extracted = [interactions] points = kak_vector(interactions_extracted) * 4 / np.pi ax.scatter(*coord_transform(points), **kwargs) ax.set_xlim(0, +1) ax.set_ylim(-1, +1) ax.set_zlim(0, +1) if show_plot: fig.show() return ax
def _decompose_single_qubit(self, operation: 'cirq.Operation') -> ops.OP_TREE: qubit = operation.qubits[0] mat = protocols.unitary(operation) for gate in optimizers.single_qubit_matrix_to_gates(mat, self.atol): yield gate(qubit)
def _unitary_(self) -> Union[np.ndarray, type(NotImplemented)]: return protocols.unitary(self._gate, NotImplemented)
def _unitary_(self) -> Union[np.ndarray, NotImplementedType]: sub_matrix = protocols.unitary(self.sub_operation, None) if sub_matrix is None: return NotImplemented return linalg.block_diag( np.eye(pow(2, len(self.qubits)) - sub_matrix.shape[0]), sub_matrix)
def _circuit_as_layers(circuit: circuits.Circuit, grouping: _QubitGrouping) -> List[_TransformsThenCzs]: """Transforms a circuit into a series of GroupMatrix+CZ layers. Args: circuit: The circuit to transform. grouping: How the circuit's qubits are combined into groups. Returns: A list of layers. Each layer has a matrix to apply to each group of qubits, and a list of CZs to apply to pairs of qubits crossing between groups. """ frontier = {q: 0 for q in circuit.all_qubits()} layers = [] while True: # Pull within-group operations into per-group matrices. any_group_matrices = False group_matrices = [] for g in grouping.groups: # Scan for reachable operations contained within the group qubits. start_frontier = {q: frontier[q] for q in g} end_frontier = circuit.reachable_frontier_from(start_frontier) mergeable_ops = circuit.findall_operations_between(start_frontier, end_frontier) # Advance frontier. for q, v in end_frontier.items(): frontier[q] = v # Fold reachable operations into a single group matrix. group_matrix = np.eye(1 << len(g)).reshape((2, 2) * len(g)) if mergeable_ops: any_group_matrices = True for _, op in mergeable_ops: group_matrix = linalg.targeted_left_multiply( left_matrix=protocols.unitary(op).reshape( (2, 2) * len(op.qubits)), right_target=group_matrix, target_axes=[grouping.loc(q)[1] for q in op.qubits]) group_matrices.append(np.transpose(group_matrix.reshape( 1 << len(g), 1 << len(g)))) # Scan for reachable CZ operations between groups. end_frontier = circuit.reachable_frontier_from( frontier, is_blocker=lambda op: grouping.all_in_same_group(*op.qubits)) cz_ops = circuit.findall_operations_between(frontier, end_frontier) # Advance frontier. frontier = end_frontier # List out qubit index pairs for each CZ. cz_indices = [] for _, cz in cz_ops: a, b = cz.qubits assert cz == ops.CZ(a, b) cz_indices.append((grouping.ind(a), grouping.ind(b))) # Combine group and CZ operations into a simulation layer. if not any_group_matrices and not cz_indices: break layer = _TransformsThenCzs(group_matrices=group_matrices, cz_indices=cz_indices) layers.append(layer) # We should have processed the whole circuit. assert frontier == {q: len(circuit) for q in circuit.all_qubits()} return layers
def _decompose_two_qubit_operation(self, op: 'cirq.Operation', _) -> DecomposeResult: if protocols.has_unitary(op): return transformers.two_qubit_matrix_to_ion_operations( op.qubits[0], op.qubits[1], protocols.unitary(op) ) return NotImplemented
def _mixture_(self) -> Sequence[Tuple[float, np.ndarray]]: return ((self._p_ii, protocols.unitary(cirq.IdentityGate(2))), (self._p_xi, protocols.unitary(XIGate())), (self._p_xx, protocols.unitary(XXGate())), (self._p_xy, protocols.unitary(XYGate())), (self._p_xz, protocols.unitary(XZGate())), (self._p_yi, protocols.unitary(YIGate())), (self._p_yx, protocols.unitary(YXGate())), (self._p_yy, protocols.unitary(YYGate())), (self._p_yz, protocols.unitary(YZGate())), (self._p_zi, protocols.unitary(ZIGate())), (self._p_zx, protocols.unitary(ZXGate())), (self._p_zy, protocols.unitary(ZYGate())), (self._p_zz, protocols.unitary(ZZGate())), (self._p_ix, protocols.unitary(IXGate())), (self._p_iy, protocols.unitary(IYGate())), (self._p_iz, protocols.unitary(IZGate())))
def assert_qasm_is_consistent_with_unitary(val: Any): """Uses `val._unitary_` to check `val._qasm_`'s behavior.""" # Only test if qiskit is installed. try: import qiskit except ImportError: # coverage: ignore warnings.warn("Skipped assert_qasm_is_consistent_with_unitary because " "qiskit isn't installed to verify against.") return unitary = protocols.unitary(val, None) if unitary is None: # Vacuous consistency. return if isinstance(val, ops.Operation): qubits: Sequence[ops.Qid] = val.qubits op = val elif isinstance(val, ops.Gate): qid_shape = protocols.qid_shape(val) remaining_shape = list(qid_shape) controls = getattr(val, 'control_qubits', None) if controls is not None: for i, q in zip(reversed(range(len(controls))), reversed(controls)): if q is not None: remaining_shape.pop(i) qubits = devices.LineQid.for_qid_shape(remaining_shape) op = val.on(*qubits) else: raise NotImplementedError(f"Don't know how to test {val!r}") args = protocols.QasmArgs( qubit_id_map={q: f'q[{i}]' for i, q in enumerate(qubits)}) qasm = protocols.qasm(op, args=args, default=None) if qasm is None: return num_qubits = len(qubits) header = f""" OPENQASM 2.0; include "qelib1.inc"; qreg q[{num_qubits}]; """ qasm = header + qasm qasm_unitary = None try: result = qiskit.execute( qiskit.QuantumCircuit.from_qasm_str(qasm), backend=qiskit.Aer.get_backend('unitary_simulator'), ) qasm_unitary = result.result().get_unitary() qasm_unitary = _reorder_indices_of_matrix( qasm_unitary, list(reversed(range(num_qubits)))) lin_alg_utils.assert_allclose_up_to_global_phase(qasm_unitary, unitary, rtol=1e-8, atol=1e-8) except Exception as ex: p_unitary: Optional[np.ndarray] p_qasm_unitary: Optional[np.ndarray] if qasm_unitary is not None: p_unitary, p_qasm_unitary = linalg.match_global_phase( unitary, qasm_unitary) else: p_unitary = None p_qasm_unitary = None raise AssertionError( 'QASM not consistent with cirq.unitary(op) up to global phase.\n\n' 'op:\n{}\n\n' 'cirq.unitary(op):\n{}\n\n' 'Generated QASM:\n\n{}\n\n' 'Unitary of generated QASM:\n{}\n\n' 'Phased matched cirq.unitary(op):\n{}\n\n' 'Phased matched unitary of generated QASM:\n{}\n\n' 'Underlying error:\n{}'.format( _indent(repr(op)), _indent(repr(unitary)), _indent(qasm), _indent(repr(qasm_unitary)), _indent(repr(p_unitary)), _indent(repr(p_qasm_unitary)), _indent(str(ex)), ))
def assert_qasm_is_consistent_with_unitary(val: Any): """Uses `val._unitary_` to check `val._qasm_`'s behavior.""" # Only test if qiskit is installed. try: import qiskit except ImportError: # coverage: ignore warnings.warn("Skipped assert_qasm_is_consistent_with_unitary because " "qiskit isn't installed to verify against.") return unitary = protocols.unitary(val, None) if unitary is None: # Vacuous consistency. return controls = getattr(val, 'control_qubits', None) if controls is None: qubit_count = len(unitary).bit_length() - 1 else: qubit_count = len(unitary).bit_length() - 1 - (len(controls) - controls.count(None)) if isinstance(val, ops.Operation): qubits = val.qubits op = val elif isinstance(val, ops.Gate): qubits = tuple(line.LineQubit.range(qubit_count)) op = val.on(*qubits) else: raise NotImplementedError("Don't know how to test {!r}".format(val)) args = protocols.QasmArgs( qubit_id_map={q: 'q[{}]'.format(i) for i, q in enumerate(qubits)}) qasm = protocols.qasm(op, args=args, default=None) if qasm is None: return else: header = """ OPENQASM 2.0; include "qelib1.inc"; qreg q[{}]; """.format(len(qubits)) qasm = header + qasm qasm_unitary = None try: result = qiskit.execute( qiskit.load_qasm_string(qasm), backend=qiskit.Aer.get_backend('unitary_simulator')) qasm_unitary = result.result().get_unitary() qasm_unitary = _reorder_indices_of_matrix( qasm_unitary, list(reversed(range(len(qubits))))) lin_alg_utils.assert_allclose_up_to_global_phase(qasm_unitary, unitary, rtol=1e-8, atol=1e-8) except Exception as ex: if qasm_unitary is not None: p_unitary, p_qasm_unitary = linalg.match_global_phase( unitary, qasm_unitary) else: p_unitary = None p_qasm_unitary = None raise AssertionError( 'QASM be consistent with cirq.unitary(op) up to global phase.\n\n' 'op:\n{}\n\n' 'cirq.unitary(op):\n{}\n\n' 'Generated QASM:\n\n{}\n\n' 'Unitary of generated QASM:\n{}\n\n' 'Phased matched cirq.unitary(op):\n{}\n\n' 'Phased matched unitary of generated QASM:\n{}\n\n' 'Underlying error:\n{}'.format(_indent(repr(op)), _indent(repr(unitary)), _indent(qasm), _indent(repr(qasm_unitary)), _indent(repr(p_unitary)), _indent(repr(p_qasm_unitary)), _indent(str(ex))))
def _unitary_(self) -> Union[np.ndarray, NotImplementedType]: sub_matrix = protocols.unitary(self.sub_gate, None) if sub_matrix is None: return NotImplemented return linalg.block_diag(np.eye(sub_matrix.shape[0]), sub_matrix)
def _unitary_(self) -> Union[np.ndarray, type(NotImplemented)]: if isinstance(self.half_turns, value.Symbol): return NotImplemented return protocols.unitary(ops.Rot11Gate(half_turns=self.half_turns))
def apply_unitary(self, op: 'cirq.Operation'): """Applies a unitary operation, mutating the object to represent the new state. op: The operation that mutates the object. Note that currently, only 1- and 2- qubit operations are currently supported. """ old_inds = tuple( [self.i_str(self.qubit_map[qubit]) for qubit in op.qubits]) new_inds = tuple(['new_' + old_ind for old_ind in old_inds]) U = protocols.unitary(op).reshape( [qubit.dimension for qubit in op.qubits] * 2) U = qtn.Tensor(U, inds=(new_inds + old_inds)) # TODO(tonybruguier): Explore using the Quimb's tensor network natively. if len(op.qubits) == 1: n = self.grouping[op.qubits[0]] self.M[n] = (U @ self.M[n]).reindex({new_inds[0]: old_inds[0]}) elif len(op.qubits) == 2: n, p = [self.grouping[qubit] for qubit in op.qubits] if n == p: self.M[n] = (U @ self.M[n]).reindex({ new_inds[0]: old_inds[0], new_inds[1]: old_inds[1] }) else: self.num_svd_splits += 1 # This is the index on which we do the contraction. We need to add it iff it's # the first time that we do the joining for that specific pair. mu_ind = self.mu_str(n, p) if mu_ind not in self.M[n].inds: self.M[n].new_ind(mu_ind) if mu_ind not in self.M[p].inds: self.M[p].new_ind(mu_ind) T = U @ self.M[n] @ self.M[p] left_inds = tuple(set(T.inds) & set(self.M[n].inds)) + (new_inds[0], ) X, Y = T.split( left_inds, cutoff=self.rsum2_cutoff, cutoff_mode='rsum2', get='tensors', absorb='both', bond_ind=mu_ind, ) self.M[n] = X.reindex({new_inds[0]: old_inds[0]}) self.M[p] = Y.reindex({new_inds[1]: old_inds[1]}) else: # NOTE(tonybruguier): There could be a way to handle higher orders. I think this could # involve HOSVDs: # https://en.wikipedia.org/wiki/Higher-order_singular_value_decomposition # # TODO(tonybruguier): Evaluate whether it's even useful to implement and learn more # about HOSVDs. raise ValueError('Can only handle 1 and 2 qubit operations')
def _unitary_(self): return protocols.unitary(self.kak)
def _mixture_(self) -> Sequence[Tuple[float, np.ndarray]]: return ((self._p_i, protocols.unitary(common_gates.I)), (self._p_x, protocols.unitary(pauli_gates.X)), (self._p_y, protocols.unitary(pauli_gates.Y)), (self._p_z, protocols.unitary(pauli_gates.Z)))
def kak_decomposition( unitary_object: Union[np.ndarray, 'cirq.SupportsUnitary', 'cirq.Gate', 'cirq.Operation', KakDecomposition], *, rtol: float = 1e-5, atol: float = 1e-8, check_preconditions: bool = True, ) -> KakDecomposition: """Decomposes a 2-qubit unitary into 1-qubit ops and XX/YY/ZZ interactions. Args: unitary_object: The value to decompose. Can either be a 4x4 unitary matrix, or an object that has a 4x4 unitary matrix (via the `cirq.SupportsUnitary` protocol). rtol: Per-matrix-entry relative tolerance on equality. atol: Per-matrix-entry absolute tolerance on equality. check_preconditions: If set, verifies that the input corresponds to a 4x4 unitary before decomposing. Returns: A `cirq.KakDecomposition` canonicalized such that the interaction coefficients x, y, z satisfy: 0 ≤ abs(z2) ≤ y2 ≤ x2 ≤ π/4 if x2 = π/4, z2 >= 0 Raises: ValueError: Bad matrix. ArithmeticError: Failed to perform the decomposition. References: 'An Introduction to Cartan's KAK Decomposition for QC Programmers' https://arxiv.org/abs/quant-ph/0507171 """ if isinstance(unitary_object, KakDecomposition): return unitary_object if isinstance(unitary_object, np.ndarray): mat = unitary_object else: mat = protocols.unitary(unitary_object) if check_preconditions and ( mat.shape != (4, 4) or not predicates.is_unitary(mat, rtol=rtol, atol=atol)): raise ValueError( f'Input must correspond to a 4x4 unitary matrix. Received matrix:\n{mat}' ) # Diagonalize in magic basis. left, d, right = diagonalize.bidiagonalize_unitary_with_special_orthogonals( KAK_MAGIC_DAG @ mat @ KAK_MAGIC, atol=atol, rtol=rtol, check_preconditions=False) # Recover pieces. a1, a0 = so4_to_magic_su2s(left.T, atol=atol, rtol=rtol, check_preconditions=False) b1, b0 = so4_to_magic_su2s(right.T, atol=atol, rtol=rtol, check_preconditions=False) w, x, y, z = (KAK_GAMMA @ np.vstack(np.angle(d))).flatten() g = np.exp(1j * w) # Canonicalize. inner_cannon = kak_canonicalize_vector(x, y, z) b1 = np.dot(inner_cannon.single_qubit_operations_before[0], b1) b0 = np.dot(inner_cannon.single_qubit_operations_before[1], b0) a1 = np.dot(a1, inner_cannon.single_qubit_operations_after[0]) a0 = np.dot(a0, inner_cannon.single_qubit_operations_after[1]) return KakDecomposition( interaction_coefficients=inner_cannon.interaction_coefficients, global_phase=g * inner_cannon.global_phase, single_qubit_operations_before=(b1, b0), single_qubit_operations_after=(a1, a0), )
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from typing import Iterator, List, Optional, TYPE_CHECKING import math import numpy as np import scipy.linalg from cirq import circuits, google, linalg, ops, optimizers, protocols from cirq.google.ops import SycamoreGate from cirq.google.optimizers.two_qubit_gates.gate_compilation import GateTabulation if TYPE_CHECKING: import cirq UNITARY_ZZ = np.kron(protocols.unitary(ops.Z), protocols.unitary(ops.Z)) PAULI_OPS = [ np.eye(2), protocols.unitary(ops.X), protocols.unitary(ops.Y), protocols.unitary(ops.Z), ] class ConvertToSycamoreGates(circuits.PointOptimizer): """Attempts to convert non-native gates into SycamoreGates. First, checks if the given operation is already a native sycamore operation. Second, checks if the operation has a known unitary. If so, and the gate is a 1-qubit or 2-qubit gate, then performs circuit synthesis of the
def _translate_two_qubit_cirq_operation_to_braket_instruction( op: "cirq.Operation", ) -> List[Instruction]: """Translates a two-qubit Cirq operation to a (sequence of) Braket instruction(s) according to the following rules: 1. Attempts to find a "standard translation" from Cirq to Braket. - e.g., checks if `op` is a CNOT and, if so, returns the Braket CNOT. 2. If (1) is not successful, performs a KAK decomposition of the unitary of `op` to obtain a circuit ──A1──X^0.5───@───X^a───X──────────────────@───B1─── │ │ │ ──A2──────────X───Y^b───@───X^-0.5───Z^c───X───B2──── where A1, A2, B1, and B2 are arbitrary single-qubit unitaries and a, b, c are floats. Args: op: Two-qubit Cirq operation to translate. """ # Translate qubit indices. q1, q2 = [qubit.x for qubit in op.qubits] # Check common two-qubit gates. if isinstance(op.gate, cirq_ops.CNotPowGate) and np.isclose( abs(op.gate.exponent), 1.0 ): return [Instruction(braket_gates.CNot(), [q1, q2])] elif isinstance(op.gate, cirq_ops.CZPowGate) and np.isclose( abs(op.gate.exponent), 1.0 ): return [Instruction(braket_gates.CZ(), [q1, q2])] elif isinstance(op.gate, cirq_ops.ISwapPowGate) and np.isclose( op.gate.exponent, 1.0 ): return [Instruction(braket_gates.ISwap(), [q1, q2])] elif isinstance(op.gate, cirq_ops.XXPowGate): return [ Instruction(braket_gates.XX(op.gate.exponent * np.pi), [q1, q2]) ] elif isinstance(op.gate, cirq_ops.YYPowGate): return [ Instruction(braket_gates.YY(op.gate.exponent * np.pi), [q1, q2]) ] elif isinstance(op.gate, cirq_ops.ZZPowGate): return [ Instruction(braket_gates.ZZ(op.gate.exponent * np.pi), [q1, q2]) ] # Arbitrary two-qubit unitary decomposition. kak = kak_decomposition(protocols.unitary(op)) A1, A2 = kak.single_qubit_operations_before x, y, z = kak.interaction_coefficients a = x * -2 / np.pi + 0.5 b = y * -2 / np.pi + 0.5 c = z * -2 / np.pi + 0.5 B1, B2 = kak.single_qubit_operations_after return [ *_translate_one_qubit_cirq_operation_to_braket_instruction(A1, q1), *_translate_one_qubit_cirq_operation_to_braket_instruction(A2, q2), Instruction(braket_gates.Rx(0.5 * np.pi), q1), Instruction(braket_gates.CNot(), [q1, q2]), Instruction(braket_gates.Rx(a * np.pi), q1), Instruction(braket_gates.Ry(b * np.pi), q2), Instruction(braket_gates.CNot(), [q2, q1]), Instruction(braket_gates.Rx(-0.5 * np.pi), q2), Instruction(braket_gates.Rz(c * np.pi), q2), Instruction(braket_gates.CNot(), [q1, q2]), *_translate_one_qubit_cirq_operation_to_braket_instruction(B1, q1), *_translate_one_qubit_cirq_operation_to_braket_instruction(B2, q2), ]
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 apply_op(self, op: 'cirq.Operation', prng: np.random.RandomState): """Applies a unitary operation, mutating the object to represent the new state. op: The operation that mutates the object. Note that currently, only 1- and 2- qubit operations are currently supported. """ old_inds = tuple( [self.i_str(self.qubit_map[qubit]) for qubit in op.qubits]) new_inds = tuple(['new_' + old_ind for old_ind in old_inds]) if protocols.has_unitary(op): U = protocols.unitary(op) else: mixtures = protocols.mixture(op) mixture_idx = int( prng.choice(len(mixtures), p=[mixture[0] for mixture in mixtures])) U = mixtures[mixture_idx][1] U = qtn.Tensor(U.reshape([qubit.dimension for qubit in op.qubits] * 2), inds=(new_inds + old_inds)) # TODO(tonybruguier): Explore using the Quimb's tensor network natively. if len(op.qubits) == 1: n = self.grouping[op.qubits[0]] self.M[n] = (U @ self.M[n]).reindex({new_inds[0]: old_inds[0]}) elif len(op.qubits) == 2: n, p = [self.grouping[qubit] for qubit in op.qubits] if n == p: self.M[n] = (U @ self.M[n]).reindex({ new_inds[0]: old_inds[0], new_inds[1]: old_inds[1] }) else: # This is the index on which we do the contraction. We need to add it iff it's # the first time that we do the joining for that specific pair. mu_ind = self.mu_str(n, p) if mu_ind not in self.M[n].inds: self.M[n].new_ind(mu_ind) if mu_ind not in self.M[p].inds: self.M[p].new_ind(mu_ind) T = U @ self.M[n] @ self.M[p] left_inds = tuple(set(T.inds) & set(self.M[n].inds)) + (new_inds[0], ) X, Y = T.split( left_inds, method=self.simulation_options.method, max_bond=self.simulation_options.max_bond, cutoff=self.simulation_options.cutoff, cutoff_mode=self.simulation_options.cutoff_mode, get='tensors', absorb='both', bond_ind=mu_ind, ) # Equations (13), (14), and (15): # TODO(tonybruguier): When Quimb 2.0.0 is released, the split() # function should have a 'renorm' that, when set to None, will # allow to compute e_n exactly as: # np.sum(abs((X @ Y).data) ** 2).real / np.sum(abs(T) ** 2).real # # The renormalization would then have to be done manually. # # However, for now, e_n are just the estimated value. e_n = self.simulation_options.cutoff self.estimated_gate_error_list.append(e_n) self.M[n] = X.reindex({new_inds[0]: old_inds[0]}) self.M[p] = Y.reindex({new_inds[1]: old_inds[1]}) else: # NOTE(tonybruguier): There could be a way to handle higher orders. I think this could # involve HOSVDs: # https://en.wikipedia.org/wiki/Higher-order_singular_value_decomposition # # TODO(tonybruguier): Evaluate whether it's even useful to implement and learn more # about HOSVDs. raise ValueError('Can only handle 1 and 2 qubit operations') return True
def _gate_seq_to_mats(gate_seq: Sequence[ops.Gate]) -> np.ndarray: mat_rep = protocols.unitary(gate_seq[0]) for gate in gate_seq[1:]: mat_rep = np.dot(protocols.unitary(gate), mat_rep) return mat_rep
def _unitary_(self) -> Optional[np.ndarray]: if not self._has_unitary_(): return None return linalg.kron(self.coefficient, *[protocols.unitary(self[q]) for q in self.qubits])
def _unitary_(self): if not self._has_unitary_(): return NotImplemented return self.coefficient * linalg.kron( *[protocols.unitary(PAULI_GATES[p]) for p in self.pauli_mask])
def _unitary_(self) -> Union[np.ndarray, NotImplementedType]: return protocols.unitary(self.gate, default=None)
def _unitary_(self): return protocols.unitary(self.to_circuit())