diff --git a/cirq-core/cirq/devices/noise_properties.py b/cirq-core/cirq/devices/noise_properties.py index 2a44ed90d80..2b2eadf8a45 100644 --- a/cirq-core/cirq/devices/noise_properties.py +++ b/cirq-core/cirq/devices/noise_properties.py @@ -56,6 +56,10 @@ def __init__(self, noise_properties: NoiseProperties) -> None: self._noise_properties = noise_properties self.noise_models = self._noise_properties.build_noise_models() + @property + def noise_properties(self): + return self._noise_properties + def _value_equality_values_(self): return self._noise_properties diff --git a/cirq-core/cirq/sim/simulator_base.py b/cirq-core/cirq/sim/simulator_base.py index 802fa5465c5..d58c5b591dc 100644 --- a/cirq-core/cirq/sim/simulator_base.py +++ b/cirq-core/cirq/sim/simulator_base.py @@ -186,13 +186,42 @@ def _core_iterator( StepResults from simulating a Moment of the Circuit. Raises: - TypeError: The simulator encounters an op it does not support. + TypeError: The simulator encounters an op it or its noise model + does not support. """ if len(circuit) == 0: yield self._create_step_result(sim_state) return + # For any noise model derived from noise properties, check the circuit to ensure it + # is compatible with the noise properties. + if isinstance(self._noise, devices.NoiseModelFromNoiseProperties): + noise_props = self._noise.noise_properties + # So far, only SuperconductingQubitsNoiseProperties implements such constraints on + # the circuit. + if isinstance(noise_props, devices.SuperconductingQubitsNoiseProperties): + circuit_gates = {op.gate for op in circuit.all_operations()} + if not all( + any( + isinstance(gate, expected_gate) + for expected_gate in noise_props.expected_gates() + ) + for gate in circuit_gates + ): + raise TypeError( + f"Circuit uses " + f"{circuit_gates.difference(noise_props.expected_gates())} " + f"which is not supported by noise properties " + f'"{noise_props.__class__.__name__}"' + ) + if not circuit.all_qubits().issubset(noise_props.qubits): + raise TypeError( + f"Circuit uses " + f"{circuit.all_qubits().difference(noise_props.qubits)} " + f"which is not supported by noise properties " + f'"{noise_props.__class__.__name__}"' + ) noisy_moments = self.noise.noisy_moments(circuit, sorted(circuit.all_qubits())) measured: dict[tuple[cirq.Qid, ...], bool] = collections.defaultdict(bool) for moment in noisy_moments: diff --git a/cirq-core/cirq/sim/simulator_base_test.py b/cirq-core/cirq/sim/simulator_base_test.py index bb14c229281..ff644e481b8 100644 --- a/cirq-core/cirq/sim/simulator_base_test.py +++ b/cirq-core/cirq/sim/simulator_base_test.py @@ -23,6 +23,8 @@ import sympy import cirq +from cirq import devices, ops +from cirq.devices import noise_utils class CountingState(cirq.qis.QuantumStateRepresentation): @@ -454,3 +456,50 @@ def test_inhomogeneous_measurement_count_padding() -> None: results = sim.run(c, repetitions=10) for i in range(10): assert np.sum(results.records['m'][i, :, :]) == 1 + + +def test_simulates_noise_only_on_valid_gates_and_qubits() -> None: + expected_single_qubit_gates = [cirq.XPowGate, cirq.ZPowGate] + expected_qubits = [cirq.GridQubit(1, 2)] + + unexpected_qubits = [cirq.GridQubit(2, 2)] + + class TestNoiseProperties(devices.SuperconductingQubitsNoiseProperties): + @classmethod + def single_qubit_gates(cls) -> set[type[ops.Gate]]: + return set(expected_single_qubit_gates) + + @classmethod + def symmetric_two_qubit_gates(cls) -> set[type[ops.Gate]]: + return set() + + @classmethod + def asymmetric_two_qubit_gates(cls) -> set[type[ops.Gate]]: + return set() + + noise_props = TestNoiseProperties( + gate_times_ns=dict.fromkeys(expected_single_qubit_gates, 1e9), + t1_ns=dict.fromkeys(expected_qubits, 1e9), + tphi_ns=dict.fromkeys(expected_qubits, 1e9), + readout_errors=dict.fromkeys(expected_qubits, [0.5, 0.5]), + gate_pauli_errors={ + noise_utils.OpIdentifier(gate, expected_qubits[0]): 0 + for gate in expected_single_qubit_gates + }, + ) + noise_model = devices.NoiseModelFromNoiseProperties(noise_props) + + simulator = cirq.Simulator(noise=noise_model) + + valid_circuit_invalid_qubits = cirq.Circuit(cirq.X(unexpected_qubits[0])) + valid_circuit_valid_qubits = cirq.Circuit(cirq.X(expected_qubits[0])) + invalid_circuit_invalid_qubits = cirq.Circuit(cirq.Y(unexpected_qubits[0])) + invalid_circuit_valid_qubits = cirq.Circuit(cirq.Y(expected_qubits[0])) + + with pytest.raises(TypeError): + simulator.simulate(invalid_circuit_invalid_qubits) + with pytest.raises(TypeError): + simulator.simulate(invalid_circuit_valid_qubits) + with pytest.raises(TypeError): + simulator.simulate(valid_circuit_invalid_qubits) + simulator.simulate(valid_circuit_valid_qubits)