def test_can_optimize_single_qubit_group_debatable(self, *gates): # compare the TODO at the beginning of class XmonArchitecture gates = ( circuit.PhasedXGate(0.4711, 0.42), circuit.PhasedXGate(0.815, 0.137) ) # check type and value for xmon_arch.can_optimize_single_qubit_group(...) _check_boolean( self, self.xmon_arch.can_optimize_single_qubit_group(gates), True ) # construct the pauli_transform for the full gate sequence (note that the # order for the factors needs to be reversed here) pauli_transform = np.eye(3) for gate in gates: pauli_transform = np.dot(gate.get_pauli_transform(), pauli_transform) # check that xmon_arch.decompose_single_qubit_gate(...) gives a gate # sequence of equal length (the result of that function will not match the # input sequence because it decomposes it into a PhasedX and a RotZ gate) self.assertLen( self.xmon_arch.decompose_single_qubit_gate(pauli_transform), 2 )
def test_circuit(self): # construct the original circuit circ_orig = circuit.Circuit(2, [ circuit.Operation(circuit.PhasedXGate(0.47, 0.11), [0]), circuit.Operation(circuit.ControlledZGate(), [0, 1]), circuit.Operation(circuit.RotZGate(0.42), [1]), ]) # export the circuit to Cirq circ_exported = cirq_converter.export_to_cirq(circ_orig) # check the type of circ_exported self.assertIsInstance(circ_exported, cirq.Circuit) # TODO(tfoesel): # a direct comparison between circ_orig and circ_exported would be better # reimport the circuit from Cirq circ_reimported = cirq_converter.import_from_cirq(circ_exported) # check that the number of operations and the gate types are conserved self.assertEqual(len(circ_orig), len(circ_reimported)) self.assertEqual( [type(operation.get_gate()) for operation in circ_orig], [type(operation.get_gate()) for operation in circ_orig] )
def import_from_cirq(obj): """Imports a gate, operation or circuit from Cirq. Args: obj: the Cirq object to be imported. Returns: the imported object (an instance of circuit.Circuit, circuit.Operation, or a subclass of circuit.Gate). Raises: TypeError: if import is not supported for the given type. ValueError: if the object cannot be imported successfully. """ if isinstance(obj, cirq.PhasedXPowGate): return circuit.PhasedXGate(obj.exponent * np.pi, obj.phase_exponent * np.pi) elif isinstance(obj, cirq.ZPowGate): return circuit.RotZGate(obj.exponent * np.pi) elif isinstance(obj, cirq.CZPowGate): if not np.isclose(np.mod(obj.exponent, 2.0), 1.0): raise ValueError('partial ControlledZ gates are not supported') return circuit.ControlledZGate() elif isinstance(obj, (cirq.SingleQubitMatrixGate, cirq.TwoQubitMatrixGate)): return circuit.MatrixGate(cirq.unitary(obj)) elif isinstance(obj, cirq.GateOperation): return circuit.Operation(import_from_cirq(obj.gate), [qubit.x for qubit in obj.qubits]) elif isinstance(obj, cirq.Circuit): qubits = obj.all_qubits() if not all(isinstance(qubit, cirq.LineQubit) for qubit in qubits): qubit_types = set(type(qubit) for qubit in qubits) qubit_types = sorted(qubit_type.__name__ for qubit_type in qubit_types) raise ValueError( 'import is supported for circuits on LineQubits only' ' [found qubit type(s): %s]' % ', '.join(qubit_types)) return circuit.Circuit( max(qubit.x for qubit in qubits) + 1, [ import_from_cirq(operation) for operation in itertools.chain.from_iterable(obj) ]) else: raise TypeError('unknown type: %s' % type(obj).__name__)
def main(argv): if len(argv) > 1: raise app.UsageError('Too many command-line arguments.') rule = rules.ExchangeCommutingOperations() circ = circuit.Circuit(7, [ circuit.Operation(circuit.ControlledZGate(), [0, 1]), circuit.Operation(circuit.RotZGate(0.42), [0]), circuit.Operation(circuit.ControlledZGate(), [1, 2]), circuit.Operation(circuit.PhasedXGate(0.815, 0.4711), [1]), circuit.Operation(circuit.ControlledZGate(), [0, 1]), circuit.Operation(circuit.ControlledZGate(), [1, 2]) ]) transformations = tuple(rule.scan(circ)) # Show 4 transformations. print(transformations)
class XmonArchitectureTest(parameterized.TestCase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.xmon_arch = architecture.XmonArchitecture() @parameterized.parameters( [[[]], [[circuit.RotZGate(0.42)]], [[circuit.PhasedXGate(0.4711, 0.137)]], [[circuit.RotZGate(0.42), circuit.PhasedXGate(0.4711, 0.137)]], [[circuit.PhasedXGate(0.4711, 0.137), circuit.RotZGate(0.42)]]]) def test_can_optimize_single_qubit_group_negative(self, gates): # check type and value for xmon_arch.can_optimize_single_qubit_group(...) _check_boolean(self, self.xmon_arch.can_optimize_single_qubit_group(gates), False) # construct the pauli_transform for the full gate sequence (note that the # order for the factors needs to be reversed here) pauli_transform = np.eye(3) for gate in gates: pauli_transform = np.dot(gate.get_pauli_transform(), pauli_transform) # check that xmon_arch.decompose_single_qubit_gate(...) gives a gate # sequence of equal length (the result of that function does not need to # match the input sequence because the order of PhasedX and RotZ is # arbitrary, so this check would be too strict) self.assertEqual( len(self.xmon_arch.decompose_single_qubit_gate(pauli_transform)), len(gates)) def test_can_optimize_single_qubit_group_debatable(self, *gates): # compare the TODO at the beginning of class XmonArchitecture gates = (circuit.PhasedXGate(0.4711, 0.42), circuit.PhasedXGate(0.815, 0.137)) # check type and value for xmon_arch.can_optimize_single_qubit_group(...) _check_boolean(self, self.xmon_arch.can_optimize_single_qubit_group(gates), True) # construct the pauli_transform for the full gate sequence (note that the # order for the factors needs to be reversed here) pauli_transform = np.eye(3) for gate in gates: pauli_transform = np.dot(gate.get_pauli_transform(), pauli_transform) # check that xmon_arch.decompose_single_qubit_gate(...) gives a gate # sequence of equal length (the result of that function will not match the # input sequence because it decomposes it into a PhasedX and a RotZ gate) self.assertLen( self.xmon_arch.decompose_single_qubit_gate(pauli_transform), 2) @parameterized.parameters( itertools.chain( [[circuit.RotZGate(0.0)]], [[circuit.PhasedXGate(0.0, 0.0)]], [[circuit.PhasedXGate(0.0, 0.42)]], [[circuit.PhasedXGate(0.0, 0.5 * np.pi)]], itertools.product([circuit.RotZGate(0.0)], [ circuit.PhasedXGate(0.0, 0.0), circuit.PhasedXGate(0.0, 0.42), circuit.PhasedXGate(0.0, 0.5 * np.pi) ]), itertools.product([ circuit.PhasedXGate(0.0, 0.0), circuit.PhasedXGate(0.0, 0.42), circuit.PhasedXGate(0.0, 0.5 * np.pi) ], [circuit.RotZGate(0.0)]), itertools.product([circuit.RotZGate(0.137)], [ circuit.PhasedXGate(0.0, 0.0), circuit.PhasedXGate(0.0, 0.42), circuit.PhasedXGate(0.0, 0.5 * np.pi) ]), itertools.product([ circuit.PhasedXGate(0.0, 0.0), circuit.PhasedXGate(0.0, 0.42), circuit.PhasedXGate(0.0, 0.5 * np.pi) ], [circuit.RotZGate(0.137)]), itertools.product([circuit.RotZGate(0.0)], [ circuit.PhasedXGate(0.4711, 0.0), circuit.PhasedXGate(0.4711, 0.42), circuit.PhasedXGate(0.4711, 0.5 * np.pi) ]), itertools.product([ circuit.PhasedXGate(0.4711, 0.0), circuit.PhasedXGate(0.4711, 0.42), circuit.PhasedXGate(0.4711, 0.5 * np.pi) ], [circuit.RotZGate(0.0)]), [[circuit.RotZGate(0.137), circuit.RotZGate(0.137)]], [[circuit.RotZGate(0.137), circuit.RotZGate(0.42)]], [ [ # pylint: disable=g-complex-comprehension circuit.PhasedXGate(0.4711, phase_angle), circuit.PhasedXGate(0.42, phase_angle) ] for phase_angle in (0.0, 0.42, 0.5 * np.pi) ], [[circuit.RotZGate(0.137), circuit.PhasedXGate(np.pi, 0.42)]], [[circuit.PhasedXGate(np.pi, 0.42), circuit.RotZGate(0.137)]], [[ circuit.RotZGate(0.815), circuit.PhasedXGate(np.pi, 0.42), circuit.RotZGate(0.137) ]])) def test_can_optimize_single_qubit_group_positive(self, *gates): gates = tuple(gates) # check type and value for xmon_arch.can_optimize_single_qubit_group(...) _check_boolean(self, self.xmon_arch.can_optimize_single_qubit_group(gates), True) # construct the pauli_transform for the full gate sequence (note that the # order for the factors needs to be reversed here) pauli_transform = np.eye(3) for gate in gates: pauli_transform = np.dot(gate.get_pauli_transform(), pauli_transform) # check that xmon_arch.decompose_single_qubit_gate(...) can construct a # sequence with *less* gates self.assertLess( len(self.xmon_arch.decompose_single_qubit_gate(pauli_transform)), len(gates)) def test_can_optimize_single_qubit_group_error_not_iterable(self): with self.assertRaisesRegex(TypeError, r'\'int\' object is not iterable'): self.xmon_arch.can_optimize_single_qubit_group(42) def test_can_optimize_single_qubit_group_error_not_gate(self): with self.assertRaisesRegex( TypeError, r'illegal types found in gates: range \(should be subtypes of Gate\)' ): self.xmon_arch.can_optimize_single_qubit_group([range(42)]) @parameterized.parameters([ (np.eye(3), []), (transform.Rotation.from_euler('z', 0.42).as_dcm(), [circuit.RotZGate]), (transform.Rotation.from_euler('zxz', [0.42, 0.4711, -0.42]).as_dcm(), [circuit.PhasedXGate]), (transform.Rotation.from_euler('zxz', [0.42, np.pi, -0.42]).as_dcm(), [circuit.PhasedXGate]), (transform.Rotation.from_euler('zxz', [0.42, 0.4711, 0.137]).as_dcm(), [circuit.PhasedXGate, circuit.RotZGate]) ]) def test_decompose_single_qubit_gate(self, pauli_transform, gate_types): # call the method to be tested gates = self.xmon_arch.decompose_single_qubit_gate( pauli_transform.copy()) # check that gates is a list of Gate instances self.assertIsInstance(gates, list) self.assertTrue(all(isinstance(gate, circuit.Gate) for gate in gates)) # check that the gates reconstruct the correct target operation pauli_reconstr = np.eye(3) for gate in gates: pauli_reconstr = np.dot(gate.get_pauli_transform(), pauli_reconstr) np.testing.assert_allclose(pauli_transform, pauli_reconstr, rtol=1e-5, atol=1e-8) # check that gates has the expected length self.assertEqual(len(gates), len(gate_types)) # check that the gates match the expected types self.assertTrue( all( isinstance(gate, gate_type) for gate, gate_type in zip(gates, gate_types))) def test_decompose_single_qubit_gate_error_no_array(self): with self.assertRaisesRegex(TypeError, r'slice cannot be converted to np.array'): self.xmon_arch.decompose_single_qubit_gate(slice(42)) def test_decompose_single_qubit_gate_error_illegal_dtype(self): with self.assertRaisesRegex( TypeError, r'illegal dtype for pauli_transform: complex128 \(must be safely' r' castable to float\)'): self.xmon_arch.decompose_single_qubit_gate(np.eye(3, dtype=complex)) def test_decompose_single_qubit_gate_error_illegal_shape(self): with self.assertRaisesRegex( ValueError, r'illegal shape for pauli_transform: \(3, 5\) \[expected: \(3, 3\)\]' ): self.xmon_arch.decompose_single_qubit_gate(np.random.randn(3, 5)) def test_decompose_single_qubit_gate_error_not_orthogonal(self): with self.assertRaisesRegex( ValueError, r'pauli_transform is not an orthogonal matrix'): self.xmon_arch.decompose_single_qubit_gate(np.ones([3, 3]))
class TestExportAndImport(parameterized.TestCase): @parameterized.parameters([ circuit.PhasedXGate(0.47, 0.11), circuit.RotZGate(0.42), circuit.ControlledZGate(), circuit.MatrixGate(stats.unitary_group.rvs(2)), # random 1-qubit unitary circuit.MatrixGate(stats.unitary_group.rvs(4)), # random 2-qubit unitary circuit.MatrixGate(stats.unitary_group.rvs(8)), # random 3-qubit unitary circuit.MatrixGate(stats.unitary_group.rvs(16)) # random 4-qubit unitary ]) def test_gates(self, gate_orig): # export the gate to Cirq gate_exported = cirq_converter.export_to_cirq(gate_orig) # check that the gates are equivalent self.assertIsInstance(gate_exported, cirq.Gate) np.testing.assert_allclose( gate_orig.get_pauli_transform(), circuit.compute_pauli_transform(cirq.unitary(gate_exported)), rtol=1e-5, atol=1e-8 ) # reimport the gate from Cirq gate_reimported = cirq_converter.import_from_cirq(gate_exported) # check that the original and the reimported gate are equivalent self.assertIs(type(gate_reimported), type(gate_orig)) self.assertEqual( gate_reimported.get_num_qubits(), gate_orig.get_num_qubits() ) np.testing.assert_allclose( gate_orig.get_operator(), gate_reimported.get_operator(), rtol=1e-5, atol=1e-8 ) @parameterized.parameters([1, 2]) def test_operations(self, num_qubits): # construct the original operation op_orig = circuit.Operation( circuit.MatrixGate(stats.unitary_group.rvs(2 ** num_qubits)), np.random.permutation(10)[:num_qubits] ) # export the operation to Cirq op_exported = cirq_converter.export_to_cirq(op_orig) # check that the operations are equivalent self.assertIsInstance(op_exported, cirq.GateOperation) np.testing.assert_allclose( op_orig.get_gate().get_operator(), cirq.unitary(op_exported.gate), rtol=1e-5, atol=1e-8 ) self.assertTupleEqual( op_orig.get_qubits(), tuple(qubit.x for qubit in op_exported.qubits) ) # reimport the operation from Cirq op_reimported = cirq_converter.import_from_cirq(op_exported) # check that the original and the reimported operation are equivalent self.assertIs(type(op_reimported), circuit.Operation) self.assertEqual(op_reimported.get_num_qubits(), op_orig.get_num_qubits()) np.testing.assert_allclose( op_orig.get_gate().get_operator(), op_reimported.get_gate().get_operator() ) self.assertTupleEqual(op_orig.get_qubits(), op_reimported.get_qubits()) def test_circuit(self): # construct the original circuit circ_orig = circuit.Circuit(2, [ circuit.Operation(circuit.PhasedXGate(0.47, 0.11), [0]), circuit.Operation(circuit.ControlledZGate(), [0, 1]), circuit.Operation(circuit.RotZGate(0.42), [1]), ]) # export the circuit to Cirq circ_exported = cirq_converter.export_to_cirq(circ_orig) # check the type of circ_exported self.assertIsInstance(circ_exported, cirq.Circuit) # TODO(tfoesel): # a direct comparison between circ_orig and circ_exported would be better # reimport the circuit from Cirq circ_reimported = cirq_converter.import_from_cirq(circ_exported) # check that the number of operations and the gate types are conserved self.assertEqual(len(circ_orig), len(circ_reimported)) self.assertEqual( [type(operation.get_gate()) for operation in circ_orig], [type(operation.get_gate()) for operation in circ_orig] ) def test_export_unknown_type_error(self): with self.assertRaisesRegex(TypeError, r'unknown type: range'): cirq_converter.export_to_cirq(range(42)) def test_import_unknown_type_error(self): with self.assertRaisesRegex(TypeError, r'unknown type: range'): cirq_converter.import_from_cirq(range(42)) def test_import_partial_cz_error(self): partial_cz = cirq.CZPowGate(exponent=0.37) with self.assertRaisesRegex( ValueError, r'partial ControlledZ gates are not supported'): cirq_converter.import_from_cirq(partial_cz) def test_import_non_line_qubits_error(self): qubit_a = cirq.GridQubit(0, 1) qubit_b = cirq.GridQubit(0, 2) circ = cirq.Circuit( cirq.PhasedXPowGate(exponent=0.47, phase_exponent=0.11).on(qubit_a), cirq.CZPowGate(exponent=1.0).on(qubit_a, qubit_b), cirq.ZPowGate(exponent=0.42).on(qubit_b) ) with self.assertRaisesRegex( ValueError, r'import is supported for circuits on LineQubits only \[found qubit' r' type\(s\): GridQubit\]'): cirq_converter.import_from_cirq(circ)
def decompose_single_qubit_gate(self, pauli_transform ): """Decomposes an arbitrary single-qubit gate into native Xmon gates. Args: pauli_transform: np.ndarray with shape (3, 3) and dtype float. The pauli_transform representing the (single-qubit) gate to be decomposed. Returns: a sequence of native Xmon gates that are equivalent to the input, consisting of a PhasedX and/or a RotZ gate Raises: TypeError: if pauli_transform is not a np.array with real dtype, and cannot be casted to one. ValueError: if pauli_transform does not have the correct shape or if pauli_transform is not an orthogonal matrix """ if not hasattr(pauli_transform, '__getitem__'): raise TypeError('%s cannot be converted to np.array' %type(pauli_transform).__name__) pauli_transform = np.array(pauli_transform) try: pauli_transform = pauli_transform.astype(float, casting='safe') except TypeError: raise TypeError('illegal dtype for pauli_transform: %s (must be safely' ' castable to float)'%pauli_transform.dtype) if not np.array_equal(pauli_transform.shape, [3, 3]): raise ValueError( 'illegal shape for pauli_transform: %s [expected: (3, 3)]' %str(pauli_transform.shape) ) if not np.allclose(np.dot(pauli_transform, pauli_transform.T), np.eye(3)): raise ValueError('pauli_transform is not an orthogonal matrix') if np.allclose(pauli_transform, np.eye(3)): return [] # We want to decompose a given single-qubit gate U into PhasedX and RotZ # gates. For this, we make the following ansatz: # # ───U─── ≡ ───PhasedX(beta, -alpha)───RotZ(phi)─── # # To obtain the parameters of the PhasedX and RotZ gate, we perform an Euler # angle decomposition for the pauli_transform of U. # # This can be motivated as follows. We have # # PhasedX(beta, -alpha).pauli_transform == # == dot(Rz(-alpha), Rx(beta), Rz(alpha)) # RotZ(phi).pauli_transform == Rz(phi) # # where Rx and Rz are 3D rotations around the x and z axis, respectively. # From this, we get: # # dot(RotZ(phi), PhasedX(alpha, beta)).pauli_transform == # == dot(Rz(phi-alpha), Rx(beta), Rz(alpha)) # # The right-hand side is exactly the Euler angle decomposition with # signature 'zxz' and angles [alpha, beta, phi-alpha], which gives us a # direct way to extract the parameters for the two gates. Below, the angle # gamma will substitute phi-alpha. # # Note 1: The fact that this Euler angle decomposition is possible for every # SO(3) rotation, i.e. every possible Pauli transform, is one way to prove # universality for the combination of the PhasedX and RotZ gate. # # Note 2: In the calculation above, we have reversed two times the order of # the operations. This is because in circuit diagrams and for the Euler # angle decomposition (following the convention used in the corresponding # Scipy function), the first operation is left, whereas when multiplying the # associated operators, the first operation is right such that this is the # one applied first to the ket state (a vector multiplied from the right). # The two if-branches are special cases where the Euler decomposition into # 'zxz' is not anymore unique; here, we fix a choice that corresponds to # either just a PhasedX gate or just a RotZ gate, and thus minimizes the # total number of gates. # # In addition, the algorithm for Euler decomposition used in the else # branch seems to run into numerical precision issues for these cases; as # a positive side effect, these issues are circumvented. if np.isclose(pauli_transform[2, 2], 1.0): gamma = 0.5 * np.arctan2(pauli_transform[1, 0], pauli_transform[0, 0]) beta = 0.0 alpha = gamma elif np.isclose(pauli_transform[2, 2], -1.0): gamma = 0.5 * np.arctan2(pauli_transform[1, 0], pauli_transform[0, 0]) beta = np.pi alpha = -gamma else: rot = scipy.spatial.transform.Rotation.from_dcm(pauli_transform) alpha, beta, gamma = rot.as_euler('zxz') gates = [] if not np.isclose(np.cos(beta), 1.0): gates.append(circuit.PhasedXGate(beta, -alpha)) if not np.isclose(np.cos(alpha + gamma), 1.0): gates.append(circuit.RotZGate(alpha + gamma)) return gates
def parse_gates(gates, *gate_types): """Parses gates into expected gate types.""" if len(gates) != len(gate_types): raise ValueError( 'inconsistent length of gates and gate_types (%d vs %d)' % (len(gates), len(gate_types))) parsed_gates = [] for gate_in, gate_type in zip(gates, gate_types): if not isinstance(gate_in, circuit.Gate): raise TypeError('%s is not a Gate' % type(gate_in).__name__) if not isinstance(gate_type, type): raise TypeError('%s instance is not a type' % type(gate_type).__name__) if not issubclass(gate_type, circuit.Gate): raise TypeError('%s is not a Gate type' % gate_type.__name__) if gate_type == circuit.PhasedXGate: if gate_in.get_num_qubits() != 1: return None elif isinstance(gate_in, circuit.PhasedXGate): gate_out = gate_in elif isinstance(gate_in, circuit.RotZGate): if gate_in.is_identity(phase_invariant=True): gate_out = circuit.PhasedXGate(0.0, 0.0) else: return None else: pauli_transform = gate_in.get_pauli_transform() if np.isclose(pauli_transform[2, 2], -1.0): gate_out = circuit.PhasedXGate( np.pi, 0.5 * np.arctan2(pauli_transform[0, 1], pauli_transform[0, 0])) else: rotation = scipy.spatial.transform.Rotation.from_dcm( pauli_transform) alpha, beta, gamma = rotation.as_euler('zxz') if np.isclose(alpha, -gamma): gate_out = circuit.PhasedXGate(beta, -alpha) else: return None elif gate_type == circuit.RotZGate: if gate_in.get_num_qubits() != 1: return None elif isinstance(gate_in, circuit.RotZGate): gate_out = gate_in elif isinstance(gate_in, circuit.PhasedXGate): if gate_in.is_identity(phase_invariant=True): gate_out = circuit.RotZGate(0.0) else: return None else: pauli_transform = gate_in.get_pauli_transform() if np.isclose(pauli_transform[2, 2], 1.0): gate_out = circuit.RotZGate( np.arctan2(pauli_transform[1, 0], pauli_transform[0, 0])) else: return None elif gate_type == circuit.ControlledZGate: if gate_in.get_num_qubits() != 2: return None elif isinstance(gate_in, circuit.ControlledZGate): gate_out = gate_in elif gate_in == circuit.ControlledZGate(): gate_out = circuit.ControlledZGate() else: return None else: raise ValueError('unknown Gate type: %s' % gate_type.__name__) assert isinstance(gate_out, gate_type) parsed_gates.append(gate_out) assert len(parsed_gates) == len(gates) return parsed_gates