Skip to content

Commit

Permalink
Add classical simulator (#6124)
Browse files Browse the repository at this point in the history
  • Loading branch information
naerabati authored Nov 13, 2023
1 parent 39bd926 commit 2f702c8
Show file tree
Hide file tree
Showing 5 changed files with 327 additions and 0 deletions.
1 change: 1 addition & 0 deletions cirq-core/cirq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@

from cirq.sim import (
CIRCUIT_LIKE,
ClassicalStateSimulator,
CliffordSimulator,
CliffordState,
CliffordSimulatorStepResult,
Expand Down
1 change: 1 addition & 0 deletions cirq-core/cirq/protocols/json_test_data/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
'ZerosSampler',
],
should_not_be_serialized=[
'ClassicalStateSimulator',
# Heatmaps
'Heatmap',
'TwoQubitInteractionHeatmap',
Expand Down
2 changes: 2 additions & 0 deletions cirq-core/cirq/sim/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@

from cirq.sim.state_vector_simulation_state import StateVectorSimulationState

from cirq.sim.classical_simulator import ClassicalStateSimulator

from cirq.sim.state_vector_simulator import (
SimulatesIntermediateStateVector,
StateVectorStepResult,
Expand Down
127 changes: 127 additions & 0 deletions cirq-core/cirq/sim/classical_simulator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Copyright 2023 The Cirq Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Dict
from collections import defaultdict
from cirq.sim.simulator import SimulatesSamples
from cirq import ops, protocols
from cirq.study.resolver import ParamResolver
from cirq.circuits.circuit import AbstractCircuit
from cirq.ops.raw_types import Qid
import numpy as np


class ClassicalStateSimulator(SimulatesSamples):
"""A simulator that only accepts only gates with classical counterparts.
This simulator evolves a single state, using only gates that output a single state for each
input state. The simulator runs in linear time, at the cost of not supporting superposition.
It can be used to estimate costs and simulate circuits for simple non-quantum algorithms using
many more qubits than fully capable quantum simulators.
The supported gates are:
- cirq.X
- cirq.CNOT
- cirq.SWAP
- cirq.TOFFOLI
- cirq.measure
Args:
circuit: The circuit to simulate.
param_resolver: Parameters to run with the program.
repetitions: Number of times to repeat the run. It is expected that
this is validated greater than zero before calling this method.
Returns:
A dictionary mapping measurement keys to measurement results.
Raises:
ValueError: If one of the gates is not an X, CNOT, SWAP, TOFFOLI or a measurement.
"""

def _run(
self, circuit: AbstractCircuit, param_resolver: ParamResolver, repetitions: int
) -> Dict[str, np.ndarray]:
results_dict: Dict[str, np.ndarray] = {}
values_dict: Dict[Qid, int] = defaultdict(int)
param_resolver = param_resolver or ParamResolver({})
resolved_circuit = protocols.resolve_parameters(circuit, param_resolver)

for moment in resolved_circuit:
for op in moment:
gate = op.gate
if gate == ops.X:
values_dict[op.qubits[0]] = 1 - values_dict[op.qubits[0]]

elif (
isinstance(gate, ops.CNotPowGate)
and gate.exponent == 1
and gate.global_shift == 0
):
if values_dict[op.qubits[0]] == 1:
values_dict[op.qubits[1]] = 1 - values_dict[op.qubits[1]]

elif (
isinstance(gate, ops.SwapPowGate)
and gate.exponent == 1
and gate.global_shift == 0
):
hold_qubit = values_dict[op.qubits[1]]
values_dict[op.qubits[1]] = values_dict[op.qubits[0]]
values_dict[op.qubits[0]] = hold_qubit

elif (
isinstance(gate, ops.CCXPowGate)
and gate.exponent == 1
and gate.global_shift == 0
):
if (values_dict[op.qubits[0]] == 1) and (values_dict[op.qubits[1]] == 1):
values_dict[op.qubits[2]] = 1 - values_dict[op.qubits[2]]

elif isinstance(gate, ops.MeasurementGate):
qubits_in_order = op.qubits
# add the new instance of a key to the numpy array in results dictionary
if gate.key in results_dict:
shape = len(qubits_in_order)
current_array = results_dict[gate.key]
new_instance = np.zeros(shape, dtype=np.uint8)
for bits in range(0, len(qubits_in_order)):
new_instance[bits] = values_dict[qubits_in_order[bits]]
results_dict[gate.key] = np.insert(
current_array, len(current_array[0]), new_instance, axis=1
)
else:
# create the array for the results dictionary
new_array_shape = (repetitions, 1, len(qubits_in_order))
new_array = np.zeros(new_array_shape, dtype=np.uint8)
for reps in range(0, repetitions):
for instances in range(1):
for bits in range(0, len(qubits_in_order)):
new_array[reps][instances][bits] = values_dict[
qubits_in_order[bits]
]
results_dict[gate.key] = new_array

elif not (
(isinstance(gate, ops.XPowGate) and gate.exponent == 0)
or (isinstance(gate, ops.CCXPowGate) and gate.exponent == 0)
or (isinstance(gate, ops.SwapPowGate) and gate.exponent == 0)
or (isinstance(gate, ops.CNotPowGate) and gate.exponent == 0)
):
raise ValueError(
"Can not simulate gates other than cirq.XGate, "
+ "cirq.CNOT, cirq.SWAP, and cirq.CCNOT"
)

return results_dict
196 changes: 196 additions & 0 deletions cirq-core/cirq/sim/classical_simulator_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
# Copyright 2023 The Cirq Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import numpy as np
import pytest
import cirq
import sympy


class TestSimulator:
def test_x_gate(self):
q0, q1 = cirq.LineQubit.range(2)
circuit = cirq.Circuit()
circuit.append(cirq.X(q0))
circuit.append(cirq.X(q1))
circuit.append(cirq.X(q1))
circuit.append(cirq.measure((q0, q1), key='key'))
expected_results = {'key': np.array([[[1, 0]]], dtype=np.uint8)}
sim = cirq.ClassicalStateSimulator()
results = sim.run(circuit, param_resolver=None, repetitions=1).records
np.testing.assert_equal(results, expected_results)

def test_CNOT(self):
q0, q1 = cirq.LineQubit.range(2)
circuit = cirq.Circuit()
circuit.append(cirq.X(q0))
circuit.append(cirq.CNOT(q0, q1))
circuit.append(cirq.measure(q1, key='key'))
expected_results = {'key': np.array([[[1]]], dtype=np.uint8)}
sim = cirq.ClassicalStateSimulator()
results = sim.run(circuit, param_resolver=None, repetitions=1).records
np.testing.assert_equal(results, expected_results)

def test_Swap(self):
q0, q1 = cirq.LineQubit.range(2)
circuit = cirq.Circuit()
circuit.append(cirq.X(q0))
circuit.append(cirq.SWAP(q0, q1))
circuit.append(cirq.measure((q0, q1), key='key'))
expected_results = {'key': np.array([[[0, 1]]], dtype=np.uint8)}
sim = cirq.ClassicalStateSimulator()
results = sim.run(circuit, param_resolver=None, repetitions=1).records
np.testing.assert_equal(results, expected_results)

def test_CCNOT(self):
q0, q1, q2 = cirq.LineQubit.range(3)
circuit = cirq.Circuit()
circuit.append(cirq.CCNOT(q0, q1, q2))
circuit.append(cirq.measure((q0, q1, q2), key='key'))
circuit.append(cirq.X(q0))
circuit.append(cirq.CCNOT(q0, q1, q2))
circuit.append(cirq.measure((q0, q1, q2), key='key'))
circuit.append(cirq.X(q1))
circuit.append(cirq.X(q0))
circuit.append(cirq.CCNOT(q0, q1, q2))
circuit.append(cirq.measure((q0, q1, q2), key='key'))
circuit.append(cirq.X(q0))
circuit.append(cirq.CCNOT(q0, q1, q2))
circuit.append(cirq.measure((q0, q1, q2), key='key'))
expected_results = {
'key': np.array([[[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 1]]], dtype=np.uint8)
}
sim = cirq.ClassicalStateSimulator()
results = sim.run(circuit, param_resolver=None, repetitions=1).records
np.testing.assert_equal(results, expected_results)

def test_measurement_gate(self):
q0, q1 = cirq.LineQubit.range(2)
circuit = cirq.Circuit()
circuit.append(cirq.measure((q0, q1), key='key'))
expected_results = {'key': np.array([[[0, 0]]], dtype=np.uint8)}
sim = cirq.ClassicalStateSimulator()
results = sim.run(circuit, param_resolver=None, repetitions=1).records
np.testing.assert_equal(results, expected_results)

def test_qubit_order(self):
q0, q1 = cirq.LineQubit.range(2)
circuit = cirq.Circuit()
circuit.append(cirq.CNOT(q0, q1))
circuit.append(cirq.X(q0))
circuit.append(cirq.measure((q0, q1), key='key'))
expected_results = {'key': np.array([[[1, 0]]], dtype=np.uint8)}
sim = cirq.ClassicalStateSimulator()
results = sim.run(circuit, param_resolver=None, repetitions=1).records
np.testing.assert_equal(results, expected_results)

def test_same_key_instances(self):
q0, q1 = cirq.LineQubit.range(2)
circuit = cirq.Circuit()
circuit.append(cirq.measure((q0, q1), key='key'))
circuit.append(cirq.X(q0))
circuit.append(cirq.measure((q0, q1), key='key'))
expected_results = {'key': np.array([[[0, 0], [1, 0]]], dtype=np.uint8)}
sim = cirq.ClassicalStateSimulator()
results = sim.run(circuit, param_resolver=None, repetitions=1).records
np.testing.assert_equal(results, expected_results)

def test_same_key_instances_order(self):
q0, q1 = cirq.LineQubit.range(2)
circuit = cirq.Circuit()
circuit.append(cirq.X(q0))
circuit.append(cirq.measure((q0, q1), key='key'))
circuit.append(cirq.X(q0))
circuit.append(cirq.measure((q1, q0), key='key'))
expected_results = {'key': np.array([[[1, 0], [0, 0]]], dtype=np.uint8)}
sim = cirq.ClassicalStateSimulator()
results = sim.run(circuit, param_resolver=None, repetitions=1).records
np.testing.assert_equal(results, expected_results)

def test_repetitions(self):
q0 = cirq.LineQubit.range(1)
circuit = cirq.Circuit()
circuit.append(cirq.measure(q0, key='key'))
expected_results = {
'key': np.array(
[[[0]], [[0]], [[0]], [[0]], [[0]], [[0]], [[0]], [[0]], [[0]], [[0]]],
dtype=np.uint8,
)
}
sim = cirq.ClassicalStateSimulator()
results = sim.run(circuit, param_resolver=None, repetitions=10).records
np.testing.assert_equal(results, expected_results)

def test_multiple_gates(self):
q0, q1 = cirq.LineQubit.range(2)
circuit = cirq.Circuit()
circuit.append(cirq.X(q0))
circuit.append(cirq.CNOT(q0, q1))
circuit.append(cirq.CNOT(q0, q1))
circuit.append(cirq.CNOT(q0, q1))
circuit.append(cirq.X(q1))
circuit.append(cirq.measure((q0, q1), key='key'))
expected_results = {'key': np.array([[[1, 0]]], dtype=np.uint8)}
sim = cirq.ClassicalStateSimulator()
results = sim.run(circuit, param_resolver=None, repetitions=1).records
np.testing.assert_equal(results, expected_results)

def test_multiple_gates_order(self):
q0, q1 = cirq.LineQubit.range(2)
circuit = cirq.Circuit()
circuit.append(cirq.X(q0))
circuit.append(cirq.CNOT(q0, q1))
circuit.append(cirq.CNOT(q1, q0))
circuit.append(cirq.measure((q0, q1), key='key'))
expected_results = {'key': np.array([[[0, 1]]], dtype=np.uint8)}
sim = cirq.ClassicalStateSimulator()
results = sim.run(circuit, param_resolver=None, repetitions=1).records
np.testing.assert_equal(results, expected_results)

def test_param_resolver(self):
gate = cirq.CNOT ** sympy.Symbol('t')
q0, q1 = cirq.LineQubit.range(2)
circuit = cirq.Circuit()
circuit.append(cirq.X(q0))
circuit.append(gate(q0, q1))
circuit.append(cirq.measure((q1), key='key'))
resolver = cirq.ParamResolver({'t': 0})
sim = cirq.ClassicalStateSimulator()
results_with_parameter_zero = sim.run(
circuit, param_resolver=resolver, repetitions=1
).records
resolver = cirq.ParamResolver({'t': 1})
results_with_parameter_one = sim.run(
circuit, param_resolver=resolver, repetitions=1
).records
np.testing.assert_equal(
results_with_parameter_zero, {'key': np.array([[[0]]], dtype=np.uint8)}
)
np.testing.assert_equal(
results_with_parameter_one, {'key': np.array([[[1]]], dtype=np.uint8)}
)

def test_unknown_gates(self):
gate = cirq.CNOT ** sympy.Symbol('t')
q0, q1 = cirq.LineQubit.range(2)
circuit = cirq.Circuit()
circuit.append(gate(q0, q1))
circuit.append(cirq.measure((q0), key='key'))
resolver = cirq.ParamResolver({'t': 0.5})
sim = cirq.ClassicalStateSimulator()
with pytest.raises(
ValueError,
match="Can not simulate gates other than "
+ "cirq.XGate, cirq.CNOT, cirq.SWAP, and cirq.CCNOT",
):
_ = sim.run(circuit, param_resolver=resolver, repetitions=1).records

0 comments on commit 2f702c8

Please sign in to comment.