def test_is_unitary_tolerance(): atol = 0.5 # Pays attention to specified tolerance. assert cirq.is_unitary(np.array([[1, 0], [-0.5, 1]]), atol=atol) assert not cirq.is_unitary(np.array([[1, 0], [-0.6, 1]]), atol=atol) # Error isn't accumulated across entries. assert cirq.is_unitary(np.array([[1.2, 0, 0], [0, 1.2, 0], [0, 0, 1.2]]), atol=atol) assert not cirq.is_unitary(np.array([[1.2, 0, 0], [0, 1.3, 0], [0, 0, 1.2]]), atol=atol)
def two_level_decompose_gray(m): """Decomposes a unitary matrix into two level operations, if possible and returns list matrices which multiply to A with indexing. Args: m: Matrix to be decomposed Returns: result: Returns list of decomposed matrices (single bit acting) idx: Returns the matrix indexes Raises: AssertionError: Matrix m must be a power of 2 Matrix m must be a square matrix Matrix m is not a unitary matrix """ n = m.shape[0] assert is_power_of_two(n) assert m.shape == (n, n), "Matrix must be square." assert cirq.is_unitary(m) perm = [x ^ (x // 2) for x in range(n)] # Gray code. result, idxs = two_level_decompose(permute_matrix(m, perm)) for i in range(len(idxs)): index1, index2 = idxs[i] idxs[i] = perm[index1], perm[index2] return result, idxs
def three_qubit_matrix_to_operations( q0: ops.Qid, q1: ops.Qid, q2: ops.Qid, u: np.ndarray, atol: float = 1e-8 ) -> Sequence[ops.Operation]: """Returns operations for a 3 qubit unitary. The algorithm is described in Shende et al.: Synthesis of Quantum Logic Circuits. Tech. rep. 2006, https://arxiv.org/abs/quant-ph/0406176 Args: q0: first qubit q1: second qubit q2: third qubit u: unitary matrix atol: A limit on the amount of absolute error introduced by the construction. Returns: The resulting operations will have only known two-qubit and one-qubit gates based operations, namely CZ, CNOT and rx, ry, PhasedXPow gates. Raises: ValueError: If the u matrix is non-unitary or not of shape (8,8). """ if np.shape(u) != (8, 8): raise ValueError(f"Expected unitary matrix with shape (8,8) got {np.shape(u)}") if not cirq.is_unitary(u, atol=atol): raise ValueError(f"Matrix is not unitary: {u}") try: from scipy.linalg import cossin except ImportError: # coverage: ignore # coverage: ignore raise ImportError( "cirq.three_qubit_unitary_to_operations requires " "SciPy 1.5.0+, as it uses the cossin function. Please" " upgrade scipy in your environment to use this " "function!" ) (u1, u2), theta, (v1h, v2h) = cossin(u, 4, 4, separate=True) cs_ops = _cs_to_ops(q0, q1, q2, theta) if len(cs_ops) > 0 and cs_ops[-1] == cirq.CZ(q2, q0): # optimization A.1 - merging the last CZ from the end of CS into UD # cz = cirq.Circuit([cs_ops[-1]]).unitary() # CZ(c,a) = CZ(a,c) as CZ is symmetric # for the u1⊕u2 multiplexor operator: # as u1(b,c) is the operator in case a = \0>, # and u2(b,c) is the operator for (b,c) in case a = |1> # we can represent the merge by phasing u2 with I ⊗ Z u2 = u2 @ np.diag([1, -1, 1, -1]) cs_ops = cs_ops[:-1] d_ud, ud_ops = _two_qubit_multiplexor_to_ops(q0, q1, q2, u1, u2, shift_left=True, atol=atol) _, vdh_ops = _two_qubit_multiplexor_to_ops( q0, q1, q2, v1h, v2h, shift_left=False, diagonal=d_ud, atol=atol ) return list(cirq.Circuit(vdh_ops + cs_ops + ud_ops).all_operations())
def unitary2x2_to_gates(m): """Decomposes a two level unitary to Ry, Rz and R1 gates Args: m: Matrix to be converted into gates Returns: result: Returns result (list of gates to be applied from function su_to_gates) with additional R1 gates Raises: AssertionError: Matrix m is not a unitary matrix """ assert cirq.is_unitary(m) phi = np.angle(np.linalg.det(m)) if np.abs(phi) < 1e-9: return su_to_gates(m) elif np.allclose(m, np.array([[0, 1], [1, 0]], dtype=np.complex128)): return [('X', 'n/a')] else: m = np.diag([1.0, np.exp(-1j * phi)]) @ m return su_to_gates(m) + [('R1', phi)]
def test_is_unitary(): assert cirq.is_unitary(np.empty((0, 0))) assert not cirq.is_unitary(np.empty((1, 0))) assert not cirq.is_unitary(np.empty((0, 1))) assert cirq.is_unitary(np.array([[1]])) assert cirq.is_unitary(np.array([[-1]])) assert cirq.is_unitary(np.array([[1j]])) assert not cirq.is_unitary(np.array([[5]])) assert not cirq.is_unitary(np.array([[3j]])) assert not cirq.is_unitary(np.array([[1, 0]])) assert not cirq.is_unitary(np.array([[1], [0]])) assert not cirq.is_unitary(np.array([[1, 0], [0, -2]])) assert cirq.is_unitary(np.array([[1, 0], [0, -1]])) assert cirq.is_unitary(np.array([[1j, 0], [0, 1]])) assert not cirq.is_unitary(np.array([[1, 0], [1, 1]])) assert not cirq.is_unitary(np.array([[1, 1], [0, 1]])) assert not cirq.is_unitary(np.array([[1, 1], [1, 1]])) assert not cirq.is_unitary(np.array([[1, -1], [1, 1]])) assert cirq.is_unitary(np.array([[1, -1], [1, 1]]) * np.sqrt(0.5)) assert cirq.is_unitary(np.array([[1, 1j], [1j, 1]]) * np.sqrt(0.5)) assert not cirq.is_unitary(np.array([[1, -1j], [1j, 1]]) * np.sqrt(0.5)) assert cirq.is_unitary( np.array([[1, 1j + 1e-11], [1j, 1 + 1j * 1e-9]]) * np.sqrt(0.5))
def matrix_to_sycamore_operations( target_qubits: List[cirq.GridQubit], matrix: np.ndarray) -> Tuple[cirq.OP_TREE, List[cirq.GridQubit]]: """A method to convert a unitary matrix to a list of Sycamore operations. This method will return a list of `cirq.Operation`s using the qubits and (optionally) ancilla qubits to implement the unitary matrix `matrix` on the target qubits `qubits`. The operations are also supported by `cirq.google.gate_sets.SYC_GATESET`. Args: target_qubits: list of qubits the returned operations will act on. The qubit order defined by the list is assumed to be used by the operations to implement `matrix`. matrix: a matrix that is guaranteed to be unitary and of size (2**len(qs), 2**len(qs)). Returns: A tuple of operations and ancilla qubits allocated. Operations: In case the matrix is supported, a list of operations `ops` is returned. `ops` acts on `qs` qubits and for which `cirq.unitary(ops)` is equal to `matrix` up to certain tolerance. In case the matrix is not supported, it might return NotImplemented to reduce the noise in the judge output. Ancilla qubits: In case ancilla qubits are allocated a list of ancilla qubits. Otherwise an empty list. . """ #number of target qubits l = target_qubits if isinstance(target_qubits, list): qs = len(target_qubits) else: qs = 1 #check for the input matrix to be unitary unitary = cirq.is_unitary(matrix) if not (unitary): print("Error: Non unitary matrix") return NotImplemented, [] #class for the input unitary gate class unigate(cirq.Gate): def __init__(self, qs, matrix): super(unigate, self) def _num_qubits_(self): return qs def _unitary_(self): return matrix def _circuit_diagram_info_(self, args): return "Uni" #gate with the unitary input matrix ug = unigate(qs, matrix) #we make use of the converter for sycamore gates converter = cirq.google.ConvertToSycamoreGates() #we create the input qubits if qs == 1: converted = converter.convert(ug.on(l[0])) elif qs == 2: converted = converter.convert(ug.on(l[0], l[1])) elif qs == 3: converted = converter.convert(ug.on(l[0], l[1], l[2])) elif qs == 4: converted = converter.convert(ug.on(l[0], l[1], l[2], l[3])) elif qs == 5: converted = converter.convert(ug.on(l[0], l[1], l[2], l[3], l[4])) elif qs == 6: converted = converter.convert(ug.on(l[0], l[1], l[2], l[3], l[4], l[5])) elif qs == 7: converted = converter.convert( ug.on(l[0], l[1], l[2], l[3], l[4], l[5], l[6])) elif qs == 8: converted = converter.convert( ug.on(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7])) else: print("Error in conversion") return NotImplemented, [] return converted, []
def translate_cirq_to_qtrajectory( self, qubit_order: cirq.ops.QubitOrderOrList = cirq.ops.QubitOrder.DEFAULT ) -> qsim.NoisyCircuit: """ Translates this noisy Cirq circuit to the qsim representation. :qubit_order: Ordering of qubits :return: a C++ qsim NoisyCircuit object """ qsim_ncircuit = qsim.NoisyCircuit() ordered_qubits = cirq.ops.QubitOrder.as_qubit_order( qubit_order).order_for(self.all_qubits()) # qsim numbers qubits in reverse order from cirq ordered_qubits = list(reversed(ordered_qubits)) qsim_ncircuit.num_qubits = len(ordered_qubits) def has_qsim_kind(op: cirq.ops.GateOperation): return _cirq_gate_kind(op.gate) != None def to_matrix(op: cirq.ops.GateOperation): mat = cirq.unitary(op.gate, None) if mat is None: return NotImplemented return cirq.ops.MatrixGate(mat).on(*op.qubits) qubit_to_index_dict = {q: i for i, q in enumerate(ordered_qubits)} time_offset = 0 for moment in self: moment_length = 0 ops_by_gate = [] ops_by_mix = [] ops_by_channel = [] # Capture ops of each type in the appropriate list. for qsim_op in moment: if cirq.has_unitary(qsim_op) or cirq.is_measurement(qsim_op): oplist = cirq.decompose(qsim_op, fallback_decomposer=to_matrix, keep=has_qsim_kind) ops_by_gate.append(oplist) moment_length = max(moment_length, len(oplist)) pass elif cirq.has_mixture(qsim_op): ops_by_mix.append(qsim_op) moment_length = max(moment_length, 1) pass elif cirq.has_channel(qsim_op): ops_by_channel.append(qsim_op) moment_length = max(moment_length, 1) pass else: raise ValueError(f'Encountered unparseable op: {qsim_op}') # Gates must be added in time order. for gi in range(moment_length): # Handle gate output. for gate_ops in ops_by_gate: if gi >= len(gate_ops): continue qsim_op = gate_ops[gi] time = time_offset + gi gate_kind = _cirq_gate_kind(qsim_op.gate) add_op_to_circuit(qsim_op, time, qubit_to_index_dict, qsim_ncircuit) # Handle mixture output. for mixture in ops_by_mix: mixdata = [] for prob, mat in cirq.mixture(mixture): square_mat = np.reshape(mat, (int(np.sqrt(mat.size)), -1)) unitary = cirq.is_unitary(square_mat) # Package matrix into a qsim-friendly format. mat = np.reshape(mat, (-1, )).astype(np.complex64, copy=False) mixdata.append((prob, mat.view(np.float32), unitary)) qubits = [qubit_to_index_dict[q] for q in mixture.qubits] qsim.add_channel(time_offset, qubits, mixdata, qsim_ncircuit) # Handle channel output. for channel in ops_by_channel: chdata = [] for i, mat in enumerate(cirq.channel(channel)): square_mat = np.reshape(mat, (int(np.sqrt(mat.size)), -1)) unitary = cirq.is_unitary(square_mat) singular_vals = np.linalg.svd(square_mat)[1] lower_bound_prob = min(singular_vals)**2 # Package matrix into a qsim-friendly format. mat = np.reshape(mat, (-1, )).astype(np.complex64, copy=False) chdata.append( (lower_bound_prob, mat.view(np.float32), unitary)) qubits = [qubit_to_index_dict[q] for q in channel.qubits] qsim.add_channel(time_offset, qubits, chdata, qsim_ncircuit) time_offset += moment_length return qsim_ncircuit
def _unitary_power(U, p): """Raises unitary matrix U to power p.""" assert cirq.is_unitary(U) eig_vals, eig_vectors = np.linalg.eig(U) Q = np.array(eig_vectors) return Q @ np.diag(np.exp(p * 1j * np.angle(eig_vals))) @ Q.conj().T
def decompose(self, matrix, controls, target, free_qubits=[], power=1.0): """Implements action of multi-controlled unitary gate. Returns sequence of operations, which, when applied in sequence, gives result equivalent to applying single-qubit gate with matrix `matrix^power` on `target`, controlled by `controls`. Result is guaranteed to consist exclusively of 1-qubit, CNOT and CCNOT gates. If matrix is special unitary, result has length `O(len(controls))`. Otherwise result has length `O(len(controls)**2)`. Args: matrix - 2x2 numpy unitary matrix (of real or complex dtype). controls - control qubits. targets - target qubits. free_qubits - qubits which are neither controlled nor target. Can be modified by algorithm but will end up in inital state. power - power in which `unitary` should be raised. Deafults to 1.0. """ assert cirq.is_unitary(matrix) assert matrix.shape == (2, 2) u = _unitary_power(matrix, power) # Matrix parameters, see definitions in [1], chapter 4. delta = np.angle(np.linalg.det(u)) * 0.5 alpha = np.angle(u[0, 0]) + np.angle(u[0, 1]) - 2 * delta beta = np.angle(u[0, 0]) - np.angle(u[0, 1]) theta = 2 * np.arccos(np.minimum(1.0, np.abs(u[0, 0]))) # Decomposing matrix into three matrices - see [1], lemma 4.3. A = _Rz(alpha) @ _Ry(theta / 2) B = _Ry(-theta / 2) @ _Rz(-(alpha + beta) / 2) C = _Rz((beta - alpha) / 2) X = np.array([[0, 1], [1, 0]]) if not np.allclose(A @ B @ C, np.eye(2), atol=1e-4): print('alpha', alpha) print('beta', beta) print('theta', theta) print('delta', delta) print('A', A) print('B', B) print('C', C) print(np.abs(A @ B @ C)) assert np.allclose(A @ B @ C, np.eye(2), atol=1e-2) assert np.allclose(A @ X @ B @ X @ C, u / np.exp(1j * delta), atol=1e-2) m = len(controls) ctrl = controls assert m > 0, "No control qubits." if m == 1: # See [1], chapter 5.1. result = [ cirq.ZPowGate(exponent=delta / np.pi).on(ctrl[0]), cirq.rz(-0.5 * (beta - alpha)).on(target), cirq.CNOT.on(ctrl[0], target), cirq.rz(0.5 * (beta + alpha)).on(target), cirq.ry(0.5 * theta).on(target), cirq.CNOT.on(ctrl[0], target), cirq.ry(-0.5 * theta).on(target), cirq.rz(-alpha).on(target), ] # Remove no-ops. result = [g for g in result if not _is_identity(g._unitary_())] return result else: gate_is_special_unitary = np.allclose(delta, 0) if gate_is_special_unitary: # O(n) decomposition of SU matrix - [1], lemma 7.9. cnot_seq = self.multi_ctrl_x(ctrl[:-1], target, free_qubits=[ctrl[-1]]) result = [] result += self.decompose(C, [ctrl[-1]], target) result += cnot_seq result += self.decompose(B, [ctrl[-1]], target) result += cnot_seq result += self.decompose(A, [ctrl[-1]], target) return result else: # O(n^2) decomposition - [1], lemma 7.5. cnot_seq = self.multi_ctrl_x(ctrl[:-1], ctrl[-1], free_qubits=free_qubits + [target]) part1 = self.decompose(matrix, [ctrl[-1]], target, power=0.5 * power) part2 = self.decompose(matrix, [ctrl[-1]], target, power=-0.5 * power) part3 = self.decompose(matrix, ctrl[:-1], target, power=0.5 * power, free_qubits=free_qubits + [ctrl[-1]]) return part1 + cnot_seq + part2 + cnot_seq + part3 return circuit
def trotter_step( self, qubits: Sequence[cirq.QubitId], time: float, control_qubit: Optional[cirq.QubitId]=None ) -> cirq.OP_TREE: n_qubits = len(qubits) # Simulate the off-diagonal one-body terms. yield swap_network( qubits, lambda p, q, a, b: ControlledXXYYGate(duration= self.one_body_coefficients[p, q].real * time).on( control_qubit, a, b), fermionic=True) qubits = qubits[::-1] # Simulate the diagonal one-body terms. yield (cirq.Rot11Gate(rads= -self.one_body_coefficients[j, j].real * time).on( control_qubit, qubits[j]) for j in range(n_qubits)) # Simulate each singular vector of the two-body terms. prior_basis_matrix = numpy.identity(n_qubits, complex) for j in range(len(self.eigenvalues)): # Get the basis change matrix and its inverse. two_body_coefficients = self.scaled_density_density_matrices[j] basis_change_matrix = self.basis_change_matrices[j] # If the basis change matrix is unitary, merge rotations. # Otherwise, you must simulate two basis rotations. is_unitary = cirq.is_unitary(basis_change_matrix) if is_unitary: inverse_basis_matrix = numpy.conjugate( numpy.transpose(basis_change_matrix)) merged_basis_matrix = numpy.dot(prior_basis_matrix, inverse_basis_matrix) yield bogoliubov_transform(qubits, merged_basis_matrix) else: # TODO add LiH test to cover this. # coverage: ignore if j: yield bogoliubov_transform(qubits, prior_basis_matrix) yield cirq.inverse( bogoliubov_transform(qubits, basis_change_matrix)) # Simulate the off-diagonal two-body terms. yield swap_network( qubits, lambda p, q, a, b: Rot111Gate(rads= -2 * two_body_coefficients[p, q] * time).on( control_qubit, a, b)) qubits = qubits[::-1] # Simulate the diagonal two-body terms. yield (cirq.Rot11Gate(rads= -two_body_coefficients[k, k] * time).on( control_qubit, qubits[k]) for k in range(n_qubits)) # Undo basis transformation non-unitary case. # Else, set basis change matrix to prior matrix for merging. if is_unitary: prior_basis_matrix = basis_change_matrix else: # TODO add LiH test to cover this. # coverage: ignore yield bogoliubov_transform(qubits, basis_change_matrix) prior_basis_matrix = numpy.identity(n_qubits, complex) # Undo final basis transformation in unitary case. if is_unitary: yield bogoliubov_transform(qubits, prior_basis_matrix) # Apply phase from constant term yield cirq.RotZGate(rads=-self.constant * time).on(control_qubit)
def two_level_decompose(m): """Decomposes a unitary matrix into two level operations, if possible and returns list of decomposed unitary matrices with indexing. Args: m: Matrix to be decomposed Returns: result: Returns list of decomposed unitary matrices idx: Returns the matrix indexes Raises: AssertionError: Matrix m is not a unitary matrix """ assert cirq.is_unitary(m) n = m.shape[0] A = np.array(m, dtype=np.complex128) result = [] idxs = [] for i in range(n - 2): for j in range(n - 1, i, -1): a = A[i, j - 1] b = A[i, j] if abs(A[i, j]) < 1e-9: u_2x2 = np.eye(2, dtype=np.complex128) if j == i + 1: u_2x2 = np.array([[1 / a, 0], [0, a]], dtype=np.complex128) elif abs(A[i, j - 1]) < 1e-9: u_2x2 = np.array([[0, 1], [1, 0]], dtype=np.complex128) if j == i + 1: u_2x2 = np.array([[0, b], [1 / b, 0]], dtype=np.complex128) else: theta = np.arctan(np.abs(b / a)) lmbda = -np.angle(a) mu = np.pi + np.angle(b) - np.angle(a) - lmbda u_2x2 = np.array([[ np.cos(theta) * np.exp(1j * lmbda), np.sin(theta) * np.exp(1j * mu) ], [ -np.sin(theta) * np.exp(-1j * mu), np.cos(theta) * np.exp(-1j * lmbda) ]], dtype=np.complex128) A[:, (j - 1, j)] = A[:, (j - 1, j)] @ u_2x2 if not np.allclose(u_2x2, np.eye(2, dtype=np.complex128)): result.append(u_2x2.conj().T) idxs.append((j - 1, j)) last_matrix = A[n - 2:n, n - 2:n] if not np.allclose(last_matrix, np.eye(2, dtype=np.complex128)): result.append(last_matrix) idxs.append((n - 2, n - 1)) return result, idxs