Source code for pennylane_cirq.simulator_device
# Copyright 2018 Xanadu Quantum Technologies Inc.
# 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
# http://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.
"""
Cirq Simulator Devices
======================
**Module name:** :mod:`pennylane_cirq.simulator_device`
.. currentmodule:: pennylane_cirq.simulator_device
This Device implements all the :class:`~pennylane.device.Device` methods,
for using Cirq simulators as PennyLane device.
Classes
-------
.. autosummary::
SimulatorDevice
MixedStateSimulatorDevice
----
"""
import numpy as np
import cirq
import pennylane as qml
from .cirq_device import CirqDevice
from .cirq_operation import CirqOperation
[docs]class SimulatorDevice(CirqDevice):
r"""Cirq simulator device for PennyLane.
Args:
wires (int or Iterable[Number, str]]): Number of subsystems represented by the device,
or iterable that contains unique labels for the subsystems as numbers (i.e., ``[-1, 0, 2]``)
or strings (``['ancilla', 'q1', 'q2']``).
shots (int): Number of circuit evaluations/random samples used
to estimate expectation values of observables. Shots need
to >= 1. If ``None``, expectation values are calculated analytically.
qubits (List[cirq.Qubit]): A list of Cirq qubits that are used
as wires. The wire number corresponds to the index in the list.
By default, an array of ``cirq.LineQubit`` instances is created.
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"
# pylint: disable=too-many-arguments
def __init__(self, wires, shots=None, qubits=None, simulator=None):
super().__init__(wires, shots, qubits)
self._simulator = simulator or cirq.Simulator()
self._initial_state = None
self._result = None
self._state = None
[docs] def reset(self):
# pylint: disable=missing-function-docstring
super().reset()
self._initial_state = None
self._result = None
self._state = None
[docs] def capabilities(self): # pylint: disable=missing-function-docstring
capabilities = super().capabilities().copy()
capabilities.update(
returns_state=self.shots is None # State information is only set if obtaining shots
)
return capabilities
def _apply_basis_state(self, basis_state_operation):
# pylint: disable=missing-function-docstring
if self.shots is not None:
raise qml.DeviceError("The operation BasisState is only supported in analytic mode.")
self._initial_state = basis_state_operation.state_vector(wire_order=self.wires).flatten()
def _apply_qubit_state_vector(self, qubit_state_vector_operation):
# pylint: disable=missing-function-docstring
if self.shots is not None:
raise qml.DeviceError(
"The operations StatePrep and QubitStateVector are only supported in analytic mode."
)
self._initial_state = qubit_state_vector_operation.state_vector(
wire_order=self.wires
).flatten()
[docs] def apply(self, operations, **kwargs):
# pylint: disable=missing-function-docstring
super().apply(operations, **kwargs)
if self.shots is None:
self._result = self._simulator.simulate(
self.circuit, qubit_order=self.qubits, initial_state=self._initial_state
)
self._state = self._get_state_from_cirq(self._result)
[docs] def analytic_probability(self, wires=None):
# pylint: disable=missing-function-docstring
if self._state is None:
return None
probs = self._get_computational_basis_probs()
return self.marginal_prob(probs, wires)
@staticmethod
def _get_state_from_cirq(result):
"""Extract the state array from a Cirq TrialResult ``result``"""
return np.array(result.state_vector())
def _get_computational_basis_probs(self):
"""Extract the probabilities of all computational basis measurements."""
return np.abs(self._state) ** 2
@property
def state(self):
"""Returns the state vector of the circuit prior to measurement.
.. note::
The state includes possible basis rotations for non-diagonal
observables. Note that this behaviour differs from PennyLane's
default.qubit plugin.
"""
return self._state
[docs] def generate_samples(self):
# pylint: disable=missing-function-docstring
if self.shots is None:
return super().generate_samples()
for wire in range(self.num_wires):
self.circuit.append(cirq.measure(self.qubits[wire], key=str(wire)))
self._result = self._simulator.run(self.circuit, repetitions=self.shots)
return np.array(
[self._result.measurements[str(wire)].flatten() for wire in range(self.num_wires)]
).T.astype(int)
[docs] 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, qml.ops.Prod)):
# Observable on a single wire
# Projector, Hermitian
if self._observable_map[observable.name] is None or observable.name == "Projector":
return super().expval(observable, shot_range, bin_size)
if observable.name == "Hadamard":
circuit = self.circuit
obs = cirq.PauliSum() + self.to_paulistring(qml.PauliZ(wires=observable.wires))
else:
circuit = self.pre_rotated_circuit
obs = cirq.PauliSum() + self.to_paulistring(observable)
# 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 ob_names:
if self._observable_map[name] is None or name == "Projector":
return super().expval(observable, shot_range, bin_size)
if "Hadamard" in ob_names:
list_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.prod(*list_obs)
circuit = self.circuit
obs = cirq.PauliSum() + self.to_paulistring(T)
else:
circuit = self.pre_rotated_circuit
obs = cirq.PauliSum() + self.to_paulistring(observable)
return self._simulator.simulate_expectation_values(
program=circuit,
qubit_order=self.qubits,
observables=obs,
initial_state=self._initial_state,
)[0].real
# Shots mode
samples = self.sample(observable, shot_range=shot_range, bin_size=bin_size)
return np.squeeze(np.mean(samples, axis=0))
[docs]class MixedStateSimulatorDevice(SimulatorDevice):
r"""Cirq mixed-state simulator device for PennyLane.
Args:
wires (int or Iterable[Number, str]]): Number of subsystems represented by the device,
or iterable that contains unique labels for the subsystems as numbers (i.e., ``[-1, 0, 2]``)
or strings (``['ancilla', 'q1', 'q2']``).
shots (int): Number of circuit evaluations/random samples used
to estimate expectation values of observables. Shots need
to >= 1. If ``None``, expectation values are calculated analytically.
qubits (List[cirq.Qubit]): A list of Cirq qubits that are used
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"
_mixed_sim_operation_map = {
"BitFlip": CirqOperation(cirq.bit_flip),
"PhaseFlip": CirqOperation(cirq.phase_flip),
"PhaseDamp": CirqOperation(cirq.phase_damp),
"AmplitudeDamp": CirqOperation(cirq.amplitude_damp),
"Depolarize": CirqOperation(cirq.depolarize),
}
def __init__(self, wires, shots=None, qubits=None):
self._operation_map = dict(self._operation_map, **self._mixed_sim_operation_map)
super().__init__(wires, shots, qubits)
self._simulator = cirq.DensityMatrixSimulator()
self._initial_state = None
self._result = None
self._state = None
[docs] def capabilities(self): # pylint: disable=missing-function-docstring
capabilities = super().capabilities().copy()
capabilities.update(
returns_state=self.shots is None # State information is only set if obtaining shots
)
return capabilities
[docs] def expval(self, observable, shot_range=None, bin_size=None):
# The simulate_expectation_values from Cirq for mixed states involves
# a density matrix check, which does not always pass because the tolerance
# is too low. If the error is raised we use the PennyLane function for
# expectation value.
try:
return super().expval(observable, shot_range, bin_size)
except ValueError:
return qml.devices.QubitDevice.expval(self, observable, shot_range, bin_size)
def _apply_basis_state(self, basis_state_operation):
super()._apply_basis_state(basis_state_operation)
self._initial_state = self._convert_to_density_matrix(self._initial_state)
def _apply_qubit_state_vector(self, qubit_state_vector_operation):
super()._apply_qubit_state_vector(qubit_state_vector_operation)
self._initial_state = self._convert_to_density_matrix(self._initial_state)
def _convert_to_density_matrix(self, state_vec):
"""Convert ``state_vec`` into a density matrix."""
dim = 2**self.num_wires
return np.kron(state_vec, state_vec.conj()).reshape((dim, dim))
@staticmethod
def _get_state_from_cirq(result):
"""Extract the state array from a Cirq TrialResult"""
return np.array(result.final_density_matrix)
def _get_computational_basis_probs(self):
"""Extract the probabilities of all computational basis measurements."""
return np.diag(self._state).real
@property
def state(self):
"""Returns the density matrix of the circuit prior to measurement.
.. note::
The state includes possible basis rotations for non-diagonal
observables. Note that this behaviour differs from PennyLane's
default.qubit plugin.
"""
return self._state
_modules/pennylane_cirq/simulator_device
Download Python script
Download Notebook
View on GitHub