Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
33 changes: 31 additions & 2 deletions cirq-core/cirq/sim/simulator_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
if TYPE_CHECKING:
import cirq

TStepResultBase = TypeVar('TStepResultBase', bound='StepResultBase')
TStepResultBase = TypeVar("TStepResultBase", bound="StepResultBase")
Comment thread
Acciaccatura marked this conversation as resolved.
Outdated


class SimulatorBase(
Expand Down Expand Up @@ -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:
Expand Down
81 changes: 65 additions & 16 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 @@ -229,7 +231,7 @@ def test_parameterized_copies_all_but_last() -> None:
sim = CountingSimulator()
n = 4
rs = sim.simulate_sweep(
cirq.Circuit(cirq.X(q0) ** sympy.Symbol('a')), [{'a': i} for i in range(n)]
cirq.Circuit(cirq.X(q0) ** sympy.Symbol("a")), [{"a": i} for i in range(n)]
)
for i in range(n):
r = rs[i]
Expand All @@ -252,19 +254,19 @@ def _act_on_(self, sim_state):
def test_run_one_gate_circuit() -> None:
sim = CountingSimulator()
r = sim.run(cirq.Circuit(cirq.X(q0), cirq.measure(q0)), repetitions=2)
assert np.allclose(r.measurements['q(0)'], [[1], [1]])
assert np.allclose(r.measurements["q(0)"], [[1], [1]])


def test_run_one_gate_circuit_noise() -> None:
sim = CountingSimulator(noise=cirq.X)
r = sim.run(cirq.Circuit(cirq.X(q0), cirq.measure(q0)), repetitions=2)
assert np.allclose(r.measurements['q(0)'], [[2], [2]])
assert np.allclose(r.measurements["q(0)"], [[2], [2]])


def test_run_non_unitary_circuit() -> None:
sim = CountingSimulator()
r = sim.run(cirq.Circuit(cirq.phase_damp(1).on(q0), cirq.measure(q0)), repetitions=2)
assert np.allclose(r.measurements['q(0)'], [[1], [1]])
assert np.allclose(r.measurements["q(0)"], [[1], [1]])


def test_run_non_unitary_circuit_non_unitary_state() -> None:
Expand All @@ -274,13 +276,13 @@ def _can_be_in_run_prefix(self, val):

sim = DensityCountingSimulator()
r = sim.run(cirq.Circuit(cirq.phase_damp(1).on(q0), cirq.measure(q0)), repetitions=2)
assert np.allclose(r.measurements['q(0)'], [[1], [1]])
assert np.allclose(r.measurements["q(0)"], [[1], [1]])


def test_run_non_terminal_measurement() -> None:
sim = CountingSimulator()
r = sim.run(cirq.Circuit(cirq.X(q0), cirq.measure(q0), cirq.X(q0)), repetitions=2)
assert np.allclose(r.measurements['q(0)'], [[1], [1]])
assert np.allclose(r.measurements["q(0)"], [[1], [1]])


def test_integer_initial_state_is_split() -> None:
Expand Down Expand Up @@ -370,7 +372,7 @@ def test_reorder_succeeds() -> None:
assert reordered.qubits == (q1, q0)


@pytest.mark.parametrize('split', [True, False])
@pytest.mark.parametrize("split", [True, False])
def test_sim_state_instance_unchanged_during_normal_sim(split: bool) -> None:
sim = SplittableCountingSimulator(split_untangled_states=split)
state = sim._create_simulation_state(0, (q0, q1))
Expand All @@ -383,12 +385,12 @@ def test_sim_state_instance_unchanged_during_normal_sim(split: bool) -> None:
def test_measurements_retained_in_step_results() -> None:
sim = SplittableCountingSimulator()
circuit = cirq.Circuit(
cirq.measure(q0, key='a'), cirq.measure(q0, key='b'), cirq.measure(q0, key='c')
cirq.measure(q0, key="a"), cirq.measure(q0, key="b"), cirq.measure(q0, key="c")
)
iterator = sim.simulate_moment_steps(circuit)
assert next(iterator).measurements.keys() == {'a'}
assert next(iterator).measurements.keys() == {'a', 'b'}
assert next(iterator).measurements.keys() == {'a', 'b', 'c'}
assert next(iterator).measurements.keys() == {"a"}
assert next(iterator).measurements.keys() == {"a", "b"}
assert next(iterator).measurements.keys() == {"a", "b", "c"}
assert not any(iterator)


Expand All @@ -415,11 +417,11 @@ def _has_unitary_(self):
return self.has_unitary

simulator = CountingSimulator()
params = [cirq.ParamResolver({'a': 0}), cirq.ParamResolver({'a': 1})]
params = [cirq.ParamResolver({"a": 0}), cirq.ParamResolver({"a": 1})]

op1 = TestOp(has_unitary=True)
op2 = TestOp(has_unitary=True)
circuit = cirq.Circuit(op1, cirq.XPowGate(exponent=sympy.Symbol('a'))(q), op2)
circuit = cirq.Circuit(op1, cirq.XPowGate(exponent=sympy.Symbol("a"))(q), op2)
rs = simulator.simulate_sweep(program=circuit, params=params)
assert isinstance(rs[0]._final_simulator_state, CountingSimulationState)
assert isinstance(rs[1]._final_simulator_state, CountingSimulationState)
Expand All @@ -430,7 +432,7 @@ def _has_unitary_(self):

op1 = TestOp(has_unitary=False)
op2 = TestOp(has_unitary=False)
circuit = cirq.Circuit(op1, cirq.XPowGate(exponent=sympy.Symbol('a'))(q), op2)
circuit = cirq.Circuit(op1, cirq.XPowGate(exponent=sympy.Symbol("a"))(q), op2)
rs = simulator.simulate_sweep(program=circuit, params=params)
assert isinstance(rs[0]._final_simulator_state, CountingSimulationState)
assert isinstance(rs[1]._final_simulator_state, CountingSimulationState)
Expand All @@ -442,7 +444,7 @@ def _has_unitary_(self):

def test_inhomogeneous_measurement_count_padding() -> None:
q = cirq.LineQubit(0)
key = cirq.MeasurementKey('m')
key = cirq.MeasurementKey("m")
sim = cirq.Simulator()
c = cirq.Circuit(
cirq.CircuitOperation(
Expand All @@ -453,4 +455,51 @@ 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
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):

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