Skip to content
Open
4 changes: 4 additions & 0 deletions cirq-core/cirq/devices/noise_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
32 changes: 31 additions & 1 deletion cirq-core/cirq/sim/simulator_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,13 +191,43 @@ 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)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

noise models define their expected gateset explicitly - for example:

@classmethod
def single_qubit_gates(cls) -> set[type]:
return {cirq.ZPowGate, cirq.PhasedXZGate, cirq.MeasurementGate, cirq.ResetChannel}

there are some gates that are mathematically equivalent to other gates (e.g. I think PhasedXZGate(axis_phase_exponent=0, x_exponent=1, z_exponent=0) and X), but I believe this check will not allow X. is this ok, and if not is there a better alternative for testing gate equality?

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__}"'
)

system_qubits = sorted(sim_state.qubits)
measured: dict[tuple[cirq.Qid, ...], bool] = collections.defaultdict(bool)
for moment in circuit:
Expand Down
49 changes: 49 additions & 0 deletions cirq-core/cirq/sim/simulator_base_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import sympy

import cirq
from cirq import devices, ops
from cirq.devices import noise_utils


class CountingState(cirq.qis.QuantumStateRepresentation):
Expand Down Expand Up @@ -464,3 +466,50 @@ def test_simulate_unresolved_parameter_raises() -> None:
match=r'Circuit contains ops whose symbols were not specified in the parameter sweep\.',
):
sim.simulate(circuit)


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):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the TestNoiseProperties created for this test might not make sense from a QC point of view just because of my limited domain knowledge - feel free to re-implement to something more sensible if this is a concern!

@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)
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@
... cal, gate_times_ns="legacy")
>>> noise_model = cirq_google.NoiseModelFromGoogleNoiseProperties(noise_props)
>>> simulator = cirq.Simulator(noise=noise_model)
>>> circuit = cirq.Circuit(cirq.X(cirq.GridQubit(5, 2)))
>>> circuit = cirq.Circuit(cirq.PhasedXZGate(
... x_exponent=1.0,
... z_exponent=0.0,
... axis_phase_exponent=0.0,
... )(cirq.GridQubit(5, 2))) # X gate
>>> result = simulator.simulate(circuit)
"""

Expand Down
Loading