Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add prod support to cirq device #183

Merged
merged 8 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 5 additions & 2 deletions pennylane_cirq/cirq_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import pennylane as qml
from pennylane import QubitDevice
from pennylane.operation import Tensor
from pennylane.ops import Prod
from pennylane.wires import Wires

from ._version import __version__
Expand Down Expand Up @@ -169,6 +170,7 @@ def __init__(self, wires, shots, qubits=None):
"Hadamard": CirqOperation(lambda: cirq.H),
"Hermitian": None,
# TODO: Consider using qml.utils.decompose_hamiltonian() to support this observable.
"Prod": None,
"Identity": CirqOperation(lambda: cirq.I),
"Projector": CirqOperation(lambda: cirq.ProductState.projector),
}
Expand All @@ -191,8 +193,9 @@ def supports_operation(self, operation):

def to_paulistring(self, observable):
"""Convert an observable to a cirq.PauliString"""
if isinstance(observable, Tensor):
obs = [self.to_paulistring(o) for o in observable.obs]
if isinstance(observable, (Tensor, Prod)):
obs = observable.obs if isinstance(observable, Tensor) else observable.operands
obs = [self.to_paulistring(o) for o in obs]
return functools.reduce(operator.mul, obs)
cirq_op = self._observable_map[observable.name]
if cirq_op is None:
Expand Down
1 change: 1 addition & 0 deletions pennylane_cirq/pasqal_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class PasqalDevice(SimulatorDevice):
``control_radius / 2``.
i.e., ``(0,0,0), (control_radius/2,0,0), (control_radius,0,0)``, etc.
"""

name = "Cirq Pasqal device for PennyLane"
short_name = "cirq.pasqal"

Expand Down
11 changes: 9 additions & 2 deletions pennylane_cirq/qsim_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class QSimDevice(SimulatorDevice):
usage documentation <https://github.com/quantumlib/qsim/blob/master/docs/usage.md>`__
for further details.
"""

name = "QSim device for PennyLane"
short_name = "cirq.qsim"

Expand Down Expand Up @@ -81,10 +82,15 @@ def observables(self):
return set(self._base_observable_map)

def expval(self, observable, shot_range=None, bin_size=None):
is_tensor = isinstance(observable, qml.operation.Tensor)
is_tensor = isinstance(observable, (qml.operation.Tensor, qml.ops.Prod))

ob_names = (
[obs.name for obs in observable.operands]
if isinstance(observable, qml.ops.Prod)
else observable.name
)
if (
is_tensor and all(obs == "Identity" for obs in observable.name)
is_tensor and all(obs == "Identity" for obs in ob_names)
) or observable.name == "Identity":
return 1

Expand All @@ -108,6 +114,7 @@ class QSimhDevice(SimulatorDevice):
as wires. The wire number corresponds to the index in the list.
By default, an array of ``cirq.LineQubit`` instances is created.
"""

name = "qsimh device for PennyLane"
short_name = "cirq.qsimh"

Expand Down
22 changes: 18 additions & 4 deletions pennylane_cirq/simulator_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class SimulatorDevice(CirqDevice):
simulator (Optional[cirq.Simulator]): Optional custom simulator object to use. If
None, the default ``cirq.Simulator()`` will be used instead.
"""

name = "Cirq Simulator device for PennyLane"
short_name = "cirq.simulator"

Expand Down Expand Up @@ -158,7 +159,7 @@ def expval(self, observable, shot_range=None, bin_size=None):
# pylint: disable=missing-function-docstring
# Analytic mode
if self.shots is None:
if not isinstance(observable, qml.operation.Tensor):
if not isinstance(observable, (qml.operation.Tensor, qml.ops.Prod)):
# Observable on a single wire
# Projector, Hermitian
if self._observable_map[observable.name] is None or observable.name == "Projector":
Expand All @@ -173,14 +174,26 @@ def expval(self, observable, shot_range=None, bin_size=None):

# Observables are in tensor form
else:

ob_names = (
[op.name for op in observable.operands]
if isinstance(observable, qml.ops.Prod)
else observable.name
)

# Projector, Hamiltonian, Hermitian
for name in observable.name:
for name in ob_names:
if self._observable_map[name] is None or name == "Projector":
return super().expval(observable, shot_range, bin_size)

if "Hadamard" in observable.name:
if "Hadamard" in ob_names:
list_obs = []
for obs in observable.obs:
observables = (
observable.operands
if isinstance(observable, qml.ops.Prod)
else observable.obs
)
for obs in observables:
list_obs.append(qml.PauliZ(wires=obs.wires))

T = qml.operation.Tensor(*list_obs)
Expand Down Expand Up @@ -216,6 +229,7 @@ class MixedStateSimulatorDevice(SimulatorDevice):
as wires. The wire number corresponds to the index in the list.
By default, an array of ``cirq.LineQubit`` instances is created.
"""

name = "Cirq Mixed-State Simulator device for PennyLane"
short_name = "cirq.mixedsimulator"

Expand Down
20 changes: 13 additions & 7 deletions tests/test_mixed_simulator_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ def test_apply_operation_single_wire_no_parameters(

simulator_device_1_wire.reset()
init_state = np.array(input, dtype=np.complex64)
simulator_device_1_wire._initial_state = simulator_device_1_wire._convert_to_density_matrix(init_state)
simulator_device_1_wire._initial_state = simulator_device_1_wire._convert_to_density_matrix(
init_state
)
simulator_device_1_wire.apply([op(wires=[0])])

state = np.array(expected_pure_state)
Expand Down Expand Up @@ -135,7 +137,9 @@ def test_apply_operation_two_wires_no_parameters(

simulator_device_2_wires.reset()
init_state = np.array(input, dtype=np.complex64)
simulator_device_2_wires._initial_state = simulator_device_2_wires._convert_to_density_matrix(init_state)
simulator_device_2_wires._initial_state = (
simulator_device_2_wires._convert_to_density_matrix(init_state)
)
simulator_device_2_wires.apply([op(wires=[0, 1])])

state = np.array(expected_pure_state)
Expand Down Expand Up @@ -287,7 +291,9 @@ def test_apply_operation_single_wire_with_parameters(

simulator_device_1_wire.reset()
init_state = np.array(input, dtype=np.complex64)
simulator_device_1_wire._initial_state = simulator_device_1_wire._convert_to_density_matrix(init_state)
simulator_device_1_wire._initial_state = simulator_device_1_wire._convert_to_density_matrix(
init_state
)
simulator_device_1_wire.apply([op(*par, wires=[0])])

state = np.array(expected_pure_state)
Expand Down Expand Up @@ -417,7 +423,9 @@ def test_apply_operation_two_wires_with_parameters(

simulator_device_2_wires.reset()
init_state = np.array(input, dtype=np.complex64)
simulator_device_2_wires._initial_state = simulator_device_2_wires._convert_to_density_matrix(init_state)
simulator_device_2_wires._initial_state = (
simulator_device_2_wires._convert_to_density_matrix(init_state)
)
simulator_device_2_wires.apply([op(*par, wires=[0, 1])])

state = np.array(expected_pure_state)
Expand Down Expand Up @@ -448,9 +456,7 @@ def test_qubit_state_vector_not_at_beginning_error(self, simulator_device_1_wire
qml.DeviceError,
match=f"The operation {stateprep.__name__} is only supported at the beginning of a circuit.",
):
simulator_device_1_wire.apply(
[qml.PauliX(0), stateprep(np.array([0, 1]), wires=[0])]
)
simulator_device_1_wire.apply([qml.PauliX(0), stateprep(np.array([0, 1]), wires=[0])])


@pytest.mark.parametrize("shots", [100])
Expand Down
20 changes: 15 additions & 5 deletions tests/test_native_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ def test_apply_depolarize_single_wire(

simulator_device_1_wire.reset()
init_state = np.array(input, dtype=np.complex64)
simulator_device_1_wire._initial_state = simulator_device_1_wire._convert_to_density_matrix(init_state)
simulator_device_1_wire._initial_state = simulator_device_1_wire._convert_to_density_matrix(
init_state
)
simulator_device_1_wire.apply([ops.Depolarize(*par, wires=[0])])

assert np.allclose(simulator_device_1_wire.state, expected_density_matrix, **tol)
Expand Down Expand Up @@ -84,7 +86,9 @@ def test_apply_bit_flip_single_wire(

simulator_device_1_wire.reset()
init_state = np.array(input, dtype=np.complex64)
simulator_device_1_wire._initial_state = simulator_device_1_wire._convert_to_density_matrix(init_state)
simulator_device_1_wire._initial_state = simulator_device_1_wire._convert_to_density_matrix(
init_state
)
simulator_device_1_wire.apply([ops.BitFlip(*par, wires=[0])])

assert np.allclose(simulator_device_1_wire.state, expected_density_matrix, **tol)
Expand Down Expand Up @@ -113,7 +117,9 @@ def test_apply_phase_flip_single_wire(

simulator_device_1_wire.reset()
init_state = np.array(input, dtype=np.complex64)
simulator_device_1_wire._initial_state = simulator_device_1_wire._convert_to_density_matrix(init_state)
simulator_device_1_wire._initial_state = simulator_device_1_wire._convert_to_density_matrix(
init_state
)
simulator_device_1_wire.apply([ops.PhaseFlip(*par, wires=[0])])

assert np.allclose(simulator_device_1_wire.state, expected_density_matrix, **tol)
Expand Down Expand Up @@ -150,7 +156,9 @@ def test_apply_phase_damp_single_wire(

simulator_device_1_wire.reset()
init_state = np.array(input, dtype=np.complex64)
simulator_device_1_wire._initial_state = simulator_device_1_wire._convert_to_density_matrix(init_state)
simulator_device_1_wire._initial_state = simulator_device_1_wire._convert_to_density_matrix(
init_state
)
simulator_device_1_wire.apply([ops.PhaseDamp(*par, wires=[0])])

assert np.allclose(simulator_device_1_wire.state, expected_density_matrix, **tol)
Expand Down Expand Up @@ -187,7 +195,9 @@ def test_apply_amplitude_damp_single_wire(

simulator_device_1_wire.reset()
init_state = np.array(input, dtype=np.complex64)
simulator_device_1_wire._initial_state = simulator_device_1_wire._convert_to_density_matrix(init_state)
simulator_device_1_wire._initial_state = simulator_device_1_wire._convert_to_density_matrix(
init_state
)
simulator_device_1_wire.apply([ops.AmplitudeDamp(*par, wires=[0])])

assert np.allclose(simulator_device_1_wire.state, expected_density_matrix, **tol)
11 changes: 4 additions & 7 deletions tests/test_pasqal_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,12 @@ def test_control_radius_negative_exception(self):

with pytest.raises(ValueError, match="must be a non-negative real number"):
dev = PasqalDevice(wires=2, shots=123, control_radius=-5.0)

def test_executing_batch(self):
"""Test that executing a batch of circuits works properly."""

qubits = [ThreeDQubit(x, y, z)
for x in range(2)
for y in range(2)
for z in range(2)]
dev = qml.device("cirq.pasqal", control_radius = 2., qubits=qubits, wires=len(qubits))
qubits = [ThreeDQubit(x, y, z) for x in range(2) for y in range(2) for z in range(2)]
dev = qml.device("cirq.pasqal", control_radius=2.0, qubits=qubits, wires=len(qubits))

@qml.qnode(dev)
def circuit(x):
Expand All @@ -114,4 +111,4 @@ def circuit(x):

res = circuit([0.3, 0.5])
expected = np.cos(np.array([0.3, 0.5]))
assert qml.math.allclose(res, expected)
assert qml.math.allclose(res, expected)
7 changes: 6 additions & 1 deletion tests/test_qsim_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,12 @@ def circuit(x, y, z):

@pytest.mark.parametrize("shots", [8192, None])
@pytest.mark.parametrize(
"op, params", [(qml.StatePrep, np.array([0, 1])), (qml.QubitStateVector, np.array([0, 1])), (qml.BasisState, np.array([1]))]
"op, params",
[
(qml.StatePrep, np.array([0, 1])),
(qml.QubitStateVector, np.array([0, 1])),
(qml.BasisState, np.array([1])),
],
)
def test_decomposition(self, shots, op, params, mocker):
"""Test that StatePrep and BasisState are decomposed"""
Expand Down
7 changes: 6 additions & 1 deletion tests/test_qsimh_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@ def circuit(x, y, z):

@pytest.mark.parametrize("shots", [8192])
@pytest.mark.parametrize(
"op, params", [(qml.StatePrep, np.array([0, 1])), (qml.QubitStateVector, np.array([0, 1])), (qml.BasisState, np.array([1]))]
"op, params",
[
(qml.StatePrep, np.array([0, 1])),
(qml.QubitStateVector, np.array([0, 1])),
(qml.BasisState, np.array([1])),
],
)
def test_decomposition(self, shots, op, params, mocker):
"""Test that StatePrep and BasisState are decomposed"""
Expand Down
7 changes: 1 addition & 6 deletions tests/test_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ def test_sample_values_hermitian(self, device, shots, tol):
rotations=qml.Hermitian(A, wires=[0]).diagonalizing_gates(),
)


s1 = dev.sample(qml.Hermitian(A, wires=[0]))

# s1 should only contain the eigenvalues of
Expand Down Expand Up @@ -257,11 +256,7 @@ def test_pauliz_hadamard(self, device, shots, tol):

dev = device(3)

obs = (
qml.PauliZ(wires=[0])
@ qml.Hadamard(wires=[1])
@ qml.PauliY(wires=[2])
)
obs = qml.PauliZ(wires=[0]) @ qml.Hadamard(wires=[1]) @ qml.PauliY(wires=[2])

with mimic_execution_for_sample(dev):
dev.apply(
Expand Down
13 changes: 6 additions & 7 deletions tests/test_simulator_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def test_native_power_support_single_wire(self, operation, expected_output):

@qml.qnode(dev)
def circuit():
operation(wires=[0])**1.1
operation(wires=[0]) ** 1.1
return qml.expval(qml.PauliZ(0))

assert np.isclose(circuit(), expected_output)
Expand All @@ -85,11 +85,12 @@ def test_native_power_support_two_wires(self, operation, expected_output):
@qml.qnode(dev)
def circuit():
qml.Hadamard(0)
operation(wires=[0,1])**1.1
operation(wires=[0, 1]) ** 1.1
return qml.expval(qml.PauliZ(1))

assert np.isclose(circuit(), expected_output)


@pytest.fixture(scope="function")
def simulator_device_1_wire(shots):
"""Return a single wire instance of the SimulatorDevice class."""
Expand Down Expand Up @@ -481,9 +482,7 @@ def test_qubit_state_vector_not_at_beginning_error(self, simulator_device_1_wire
qml.DeviceError,
match=f"The operation {stateprep.__name__} is only supported at the beginning of a circuit.",
):
simulator_device_1_wire.apply(
[qml.PauliX(0), stateprep(np.array([0, 1]), wires=[0])]
)
simulator_device_1_wire.apply([qml.PauliX(0), stateprep(np.array([0, 1]), wires=[0])])


@pytest.mark.parametrize("shots", [1000])
Expand Down Expand Up @@ -840,8 +839,8 @@ class TestSample:
[
(10, qml.PauliZ(0)),
(12, qml.PauliZ(1)),
(17, qml.Hermitian(np.diag([1, 1, 1, -1]), wires=[0, 1]))
]
(17, qml.Hermitian(np.diag([1, 1, 1, -1]), wires=[0, 1])),
],
)
def test_sample_dimensions(self, simulator_device_2_wires, new_shots, obs):
"""Tests if the samples returned by the sample function have
Expand Down
6 changes: 1 addition & 5 deletions tests/test_var.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,7 @@ def test_pauliz_hadamard(self, device, shots, tol):

dev = device(3)

obs = (
qml.PauliZ(wires=[0])
@ qml.Hadamard(wires=[1])
@ qml.PauliY(wires=[2])
)
obs = qml.PauliZ(wires=[0]) @ qml.Hadamard(wires=[1]) @ qml.PauliY(wires=[2])

with mimic_execution_for_var(dev):
dev.apply(
Expand Down
Loading