diff --git a/cirq-core/cirq/circuits/circuit.py b/cirq-core/cirq/circuits/circuit.py index e411336cdd1..499199dc3cb 100644 --- a/cirq-core/cirq/circuits/circuit.py +++ b/cirq-core/cirq/circuits/circuit.py @@ -999,6 +999,10 @@ def unitary( result = _apply_unitary_circuit(self, state, qs, dtype) return result.reshape((side_len, side_len)) + def _has_superoperator_(self) -> bool: + """Returns True if self has superoperator representation.""" + return all(m._has_superoperator_() for m in self) + def _superoperator_(self) -> np.ndarray: """Compute superoperator matrix for quantum channel specified by this circuit.""" all_qubits = self.all_qubits() diff --git a/cirq-core/cirq/circuits/circuit_test.py b/cirq-core/cirq/circuits/circuit_test.py index 02f1dfb65b4..63cda64ee94 100644 --- a/cirq-core/cirq/circuits/circuit_test.py +++ b/cirq-core/cirq/circuits/circuit_test.py @@ -2856,6 +2856,7 @@ def _decompose_(self, qubits): def test_circuit_superoperator_too_many_qubits(): circuit = cirq.Circuit(cirq.IdentityGate(num_qubits=11).on(*cirq.LineQubit.range(11))) + assert not circuit._has_superoperator_() with pytest.raises(ValueError, match="too many"): _ = circuit._superoperator_() @@ -2896,6 +2897,7 @@ def test_circuit_superoperator_too_many_qubits(): ) def test_circuit_superoperator_fixed_values(circuit, expected_superoperator): """Tests Circuit._superoperator_() on a few simple circuits.""" + assert circuit._has_superoperator_() assert np.allclose(circuit._superoperator_(), expected_superoperator) @@ -2932,6 +2934,9 @@ def depolarize(r: float, n_qubits: int) -> cirq.DepolarizingChannel: circuit1 = cirq.Circuit(depolarize(r, n_qubits).on(*qubits) for r in rs) circuit2 = cirq.Circuit(depolarize(np.prod(rs), n_qubits).on(*qubits)) + assert circuit1._has_superoperator_() + assert circuit2._has_superoperator_() + cm1 = circuit1._superoperator_() cm2 = circuit2._superoperator_() assert np.allclose(cm1, cm2) @@ -2996,6 +3001,7 @@ def density_operator_basis(n_qubits: int) -> Iterator[np.ndarray]: ) def test_compare_circuits_superoperator_to_simulation(circuit, initial_state): """Compares action of circuit superoperator and circuit simulation.""" + assert circuit._has_superoperator_() superoperator = circuit._superoperator_() vectorized_initial_state = np.reshape(initial_state, np.prod(initial_state.shape)) vectorized_final_state = superoperator @ vectorized_initial_state diff --git a/cirq-core/cirq/ops/moment.py b/cirq-core/cirq/ops/moment.py index 34fa925dc34..7c1f132ca2a 100644 --- a/cirq-core/cirq/ops/moment.py +++ b/cirq-core/cirq/ops/moment.py @@ -342,6 +342,10 @@ def expand_to(self, qubits: Iterable['cirq.Qid']) -> 'cirq.Moment': operations.append(ops.I(q)) return Moment(*operations) + def _has_kraus_(self) -> bool: + """Returns True if self has a Kraus representation.""" + return all(protocols.has_kraus(op) for op in self.operations) and len(self.qubits) <= 10 + def _kraus_(self) -> Sequence[np.ndarray]: r"""Returns Kraus representation of self. @@ -402,6 +406,10 @@ def kraus_tensors(op: 'cirq.Operation') -> Sequence[np.ndarray]: r.append(np.reshape(k, (d, d))) return r + def _has_superoperator_(self) -> bool: + """Returns True if self has superoperator representation.""" + return self._has_kraus_() + def _superoperator_(self) -> np.ndarray: """Returns superoperator representation of self.""" return qis.kraus_to_superoperator(self._kraus_()) diff --git a/cirq-core/cirq/ops/moment_test.py b/cirq-core/cirq/ops/moment_test.py index 224e363b2be..dcec40204d8 100644 --- a/cirq-core/cirq/ops/moment_test.py +++ b/cirq-core/cirq/ops/moment_test.py @@ -645,16 +645,19 @@ def test_kraus(): a, b = cirq.LineQubit.range(2) m = cirq.Moment() + assert cirq.has_kraus(m) k = cirq.kraus(m) assert len(k) == 1 assert np.allclose(k[0], np.array([[1.0]])) m = cirq.Moment(cirq.S(a)) + assert cirq.has_kraus(m) k = cirq.kraus(m) assert len(k) == 1 assert np.allclose(k[0], np.diag([1, 1j])) m = cirq.Moment(cirq.CNOT(a, b)) + assert cirq.has_kraus(m) k = cirq.kraus(m) print(k[0]) assert len(k) == 1 @@ -662,6 +665,7 @@ def test_kraus(): p = 0.1 m = cirq.Moment(cirq.depolarize(p).on(a)) + assert cirq.has_kraus(m) k = cirq.kraus(m) assert len(k) == 4 assert np.allclose(k[0], np.sqrt(1 - p) * I) @@ -672,6 +676,7 @@ def test_kraus(): p = 0.2 q = 0.3 m = cirq.Moment(cirq.bit_flip(p).on(a), cirq.phase_flip(q).on(b)) + assert cirq.has_kraus(m) k = cirq.kraus(m) assert len(k) == 4 assert np.allclose(k[0], np.sqrt((1 - p) * (1 - q)) * np.kron(I, I)) @@ -681,8 +686,11 @@ def test_kraus(): def test_kraus_too_big(): + m = cirq.Moment(cirq.IdentityGate(11).on(*cirq.LineQubit.range(11))) + assert not cirq.has_kraus(m) + assert not m._has_superoperator_() with pytest.raises(ValueError, match='11 > 10 qubits'): - _ = cirq.kraus(cirq.Moment(cirq.IdentityGate(11).on(*cirq.LineQubit.range(11)))) + _ = cirq.kraus(m) def test_superoperator(): @@ -691,25 +699,31 @@ def test_superoperator(): a, b = cirq.LineQubit.range(2) m = cirq.Moment() + assert m._has_superoperator_() s = m._superoperator_() assert np.allclose(s, np.array([[1.0]])) m = cirq.Moment(cirq.I(a)) + assert m._has_superoperator_() s = m._superoperator_() assert np.allclose(s, np.eye(4)) m = cirq.Moment(cirq.IdentityGate(2).on(a, b)) + assert m._has_superoperator_() s = m._superoperator_() assert np.allclose(s, np.eye(16)) m = cirq.Moment(cirq.S(a)) + assert m._has_superoperator_() s = m._superoperator_() assert np.allclose(s, np.diag([1, -1j, 1j, 1])) m = cirq.Moment(cirq.CNOT(a, b)) + assert m._has_superoperator_() s = m._superoperator_() assert np.allclose(s, np.kron(cnot, cnot)) m = cirq.Moment(cirq.depolarize(0.75).on(a)) + assert m._has_superoperator_() s = m._superoperator_() assert np.allclose(s, np.array([[1, 0, 0, 1], [0, 0, 0, 0], [0, 0, 0, 0], [1, 0, 0, 1]]) / 2)