# Copyright 2018-2021 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.r"""The default.mixed device is PennyLane's standard qubit simulator for mixed-state computations.It implements the necessary :class:`~pennylane.devices.LegacyDevice` methods as well as some built-inqubit :doc:`operations </introduction/operations>`, providing a simple mixed-state simulation ofqubit-based quantum circuits."""# isort: skip_file# pylint: disable=wrong-import-order, ungrouped-importsimportfunctoolsimportitertoolsimportloggingfromcollectionsimportdefaultdictfromstringimportascii_lettersasABCimportnumpyasnpimportpennylaneasqmlimportpennylane.mathasqnpfrompennylaneimportBasisState,QubitDensityMatrix,Snapshot,StatePrepfrompennylane.loggingimportdebug_logger,debug_logger_initfrompennylane.measurementsimport(CountsMP,DensityMatrixMP,ExpectationMP,MutualInfoMP,ProbabilityMP,PurityMP,SampleMP,StateMP,VarianceMP,VnEntropyMP,)frompennylane.operationimportChannelfrompennylane.ops.qubit.attributesimportdiagonal_in_z_basisfrompennylane.wiresimportWiresfrom.._versionimport__version__from._qubit_deviceimportQubitDevice# We deliberately separate the imports to avoid confusion with the legacy deviceimportwarningsfromcollections.abcimportCallable,SequencefromdataclassesimportreplacefromtypingimportOptional,Unionfrompennylane.devices.qubit_mixedimportsimulatefrompennylane.ops.channelimport__qubit_channels__aschannelsfrompennylane.transforms.coreimportTransformProgramfrompennylane.tapeimportQuantumScriptfrompennylane.typingimportResult,ResultBatchfrom.importDevicefrom.execution_configimportExecutionConfigfrom.preprocessimport(decompose,no_sampling,null_postprocessing,validate_device_wires,validate_measurements,validate_observables,)from.modifiersimportsimulator_tracking,single_tape_supportlogger=logging.getLogger(__name__)logger.addHandler(logging.NullHandler())observables={"Hadamard","Hermitian","Identity","PauliX","PauliY","PauliZ","Prod","Projector","SProd","Sum",}operations_mixed={"Identity","Snapshot","BasisState","StatePrep","QubitDensityMatrix","QubitUnitary","ControlledQubitUnitary","BlockEncode","MultiControlledX","DiagonalQubitUnitary","SpecialUnitary","PauliX","PauliY","PauliZ","MultiRZ","Hadamard","S","T","SX","CNOT","SWAP","ISWAP","CSWAP","Toffoli","CCZ","CY","CZ","CH","PhaseShift","PCPhase","ControlledPhaseShift","CPhaseShift00","CPhaseShift01","CPhaseShift10","RX","RY","RZ","Rot","CRX","CRY","CRZ","CRot","AmplitudeDamping","GeneralizedAmplitudeDamping","PhaseDamping","DepolarizingChannel","BitFlip","PhaseFlip","PauliError","ResetError","QubitChannel","SingleExcitation","SingleExcitationPlus","SingleExcitationMinus","DoubleExcitation","DoubleExcitationPlus","DoubleExcitationMinus","QubitCarry","QubitSum","OrbitalRotation","FermionicSWAP","QFT","ThermalRelaxationError","ECR","ParametrizedEvolution","GlobalPhase",}
[docs]defobservable_stopping_condition(obs:qml.operation.Operator)->bool:"""Specifies whether an observable is accepted by DefaultQubitMixed."""ifobs.namein{"Prod","Sum"}:returnall(observable_stopping_condition(observable)forobservableinobs.operands)ifobs.name=="LinearCombination":returnall(observable_stopping_condition(observable)forobservableinobs.terms()[1])ifobs.name=="SProd":returnobservable_stopping_condition(obs.base)returnobs.nameinobservables
[docs]defstopping_condition(op:qml.operation.Operator)->bool:"""Specify whether an Operator object is supported by the device."""expected_set=operations_mixed|{"Snapshot"}|channelsreturnop.nameinexpected_set
[docs]@qml.transformdefwarn_readout_error_state(tape:qml.tape.QuantumTape,)->tuple[Sequence[qml.tape.QuantumTape],Callable]:"""If a measurement in the QNode is an analytic state or density_matrix, warn that readout error will not be applied. Args: tape (QuantumTape, .QNode, Callable): a quantum circuit. Returns: qnode (pennylane.QNode) or quantum function (callable) or tuple[List[.QuantumTape], function]: The unaltered input circuit. """ifnottape.shots:formintape.measurements:ifisinstance(m,qml.measurements.StateMP):warnings.warn(f"Measurement {m} is not affected by readout error.")return(tape,),null_postprocessing
ABC_ARRAY=np.array(list(ABC))tolerance=1e-10# !TODO: when removing this class, rename operations_mixed back to operations
[docs]classDefaultMixed(QubitDevice):"""Default qubit device for performing mixed-state computations in PennyLane. .. warning:: The API of ``DefaultMixed`` will be updated soon to follow a new device interface described in :class:`pennylane.devices.Device`. This change will not alter device behaviour for most workflows, but may have implications for plugin developers and users who directly interact with device methods. Please consult :class:`pennylane.devices.Device` and the implementation in :class:`pennylane.devices.DefaultQubit` for more information on what the new interface will look like and be prepared to make updates in a coming release. If you have any feedback on these changes, please create an `issue <https://github.com/PennyLaneAI/pennylane/issues>`_ or post in our `discussion forum <https://discuss.pennylane.ai/>`_. Args: wires (int, 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 (None, int): Number of times the circuit should be evaluated (or sampled) to estimate the expectation values. Defaults to ``None`` if not specified, which means that outputs are computed exactly. readout_prob (None, int, float): Probability for adding readout error to the measurement outcomes of observables. Defaults to ``None`` if not specified, which means that the outcomes are without any readout error. """name="Default mixed-state qubit PennyLane plugin"short_name="default.mixed"pennylane_requires=__version__version=__version__author="Xanadu Inc."operations=operations_mixed_reshape=staticmethod(qnp.reshape)_flatten=staticmethod(qnp.flatten)_transpose=staticmethod(qnp.transpose)# Allow for the `axis` keyword argument for integration with broadcasting-enabling# code in QubitDevice. However, it is not used as DefaultMixed does not support broadcasting# pylint: disable=unnecessary-lambda_gather=staticmethod(lambda*args,axis=0,**kwargs:qnp.gather(*args,**kwargs))_dot=staticmethod(qnp.dot)measurement_map=defaultdict(lambda:"")measurement_map[PurityMP]="purity"@staticmethoddef_reduce_sum(array,axes):returnqnp.sum(array,tuple(axes))@staticmethoddef_asarray(array,dtype=None):# Support floatifnothasattr(array,"__len__"):returnnp.asarray(array,dtype=dtype)res=qnp.cast(array,dtype=dtype)returnres# pylint: disable=too-many-arguments@debug_logger_initdef__init__(self,wires,*,r_dtype=np.float64,c_dtype=np.complex128,shots=None,analytic=None,readout_prob=None,):ifisinstance(wires,int)andwires>23:raiseValueError("This device does not currently support computations on more than 23 wires")self.readout_err=readout_prob# Check that the readout error probability, if entered, is either integer or float in [0,1]ifself.readout_errisnotNone:ifnotisinstance(self.readout_err,float)andnotisinstance(self.readout_err,int):raiseTypeError("The readout error probability should be an integer or a floating-point number in [0,1].")ifself.readout_err<0orself.readout_err>1:raiseValueError("The readout error probability should be in the range [0,1].")# call QubitDevice initsuper().__init__(wires,shots,r_dtype=r_dtype,c_dtype=c_dtype,analytic=analytic)self._debugger=None# Create the initial state.self._state=self._create_basis_state(0)self._pre_rotated_state=self._stateself.measured_wires=[]"""List: during execution, stores the list of wires on which measurements are acted for applying the readout error to them when readout_prob is non-zero."""def_create_basis_state(self,index):"""Return the density matrix representing a computational basis state over all wires. Args: index (int): integer representing the computational basis state. Returns: array[complex]: complex array of shape ``[2] * (2 * num_wires)`` representing the density matrix of the basis state. """rho=qnp.zeros((2**self.num_wires,2**self.num_wires),dtype=self.C_DTYPE)rho[index,index]=1returnqnp.reshape(rho,[2]*(2*self.num_wires))
@propertydefstate(self):"""Returns the state density matrix of the circuit prior to measurement"""dim=2**self.num_wires# User obtains state as a matrixreturnqnp.reshape(self._pre_rotated_state,(dim,dim))
[docs]@debug_loggerdefdensity_matrix(self,wires):"""Returns the reduced density matrix over the given wires. Args: wires (Wires): wires of the reduced system Returns: array[complex]: complex array of shape ``(2 ** len(wires), 2 ** len(wires))`` representing the reduced density matrix of the state prior to measurement. """state=getattr(self,"state",None)wires=self.map_wires(wires)returnqml.math.reduce_dm(state,indices=wires,c_dtype=self.C_DTYPE)
[docs]@debug_loggerdefpurity(self,mp,**kwargs):# pylint: disable=unused-argument"""Returns the purity of the final state"""state=getattr(self,"state",None)wires=self.map_wires(mp.wires)returnqml.math.purity(state,indices=wires,c_dtype=self.C_DTYPE)
[docs]@debug_loggerdefreset(self):"""Resets the device"""super().reset()self._state=self._create_basis_state(0)self._pre_rotated_state=self._state
[docs]@debug_loggerdefanalytic_probability(self,wires=None):ifself._stateisNone:returnNone# convert rho from tensor to matrixrho=qnp.reshape(self._state,(2**self.num_wires,2**self.num_wires))# probs are diagonal elementsprobs=self.marginal_prob(qnp.diagonal(rho),wires)# take the real part so probabilities are not shown as complex numbersprobs=qnp.real(probs)returnqnp.where(probs<0,-probs,probs)
def_get_kraus(self,operation):# pylint: disable=no-self-use"""Return the Kraus operators representing the operation. Args: operation (.Operation): a PennyLane operation Returns: list[array[complex]]: Returns a list of 2D matrices representing the Kraus operators. If the operation is unitary, returns a single Kraus operator. In the case of a diagonal unitary, returns a 1D array representing the matrix diagonal. """ifoperationindiagonal_in_z_basis:returnoperation.eigvals()ifisinstance(operation,Channel):returnoperation.kraus_matrices()return[operation.matrix()]def_apply_channel(self,kraus,wires):r"""Apply a quantum channel specified by a list of Kraus operators to subsystems of the quantum state. For a unitary gate, there is a single Kraus operator. Args: kraus (list[array]): Kraus operators wires (Wires): target wires """channel_wires=self.map_wires(wires)rho_dim=2*self.num_wiresnum_ch_wires=len(channel_wires)# Computes K^\dagger, needed for the transformation K \rho K^\daggerkraus_dagger=[qnp.conj(qnp.transpose(k))forkinkraus]kraus=qnp.stack(kraus)kraus_dagger=qnp.stack(kraus_dagger)# Shape kraus operatorskraus_shape=[len(kraus)]+[2]*num_ch_wires*2kraus=qnp.cast(qnp.reshape(kraus,kraus_shape),dtype=self.C_DTYPE)kraus_dagger=qnp.cast(qnp.reshape(kraus_dagger,kraus_shape),dtype=self.C_DTYPE)# Tensor indices of the state. For each qubit, need an index for rows *and* columnsstate_indices=ABC[:rho_dim]# row indices of the quantum state affected by this operationrow_wires_list=channel_wires.tolist()row_indices="".join(ABC_ARRAY[row_wires_list].tolist())# column indices are shifted by the number of wirescol_wires_list=[w+self.num_wiresforwinrow_wires_list]col_indices="".join(ABC_ARRAY[col_wires_list].tolist())# indices in einsum must be replaced with new onesnew_row_indices=ABC[rho_dim:rho_dim+num_ch_wires]new_col_indices=ABC[rho_dim+num_ch_wires:rho_dim+2*num_ch_wires]# index for summation over Kraus operatorskraus_index=ABC[rho_dim+2*num_ch_wires:rho_dim+2*num_ch_wires+1]# new state indices replace row and column indices with new onesnew_state_indices=functools.reduce(lambdaold_string,idx_pair:old_string.replace(idx_pair[0],idx_pair[1]),zip(col_indices+row_indices,new_col_indices+new_row_indices),state_indices,)# index mapping for einsum, e.g., 'iga,abcdef,idh->gbchef'einsum_indices=(f"{kraus_index}{new_row_indices}{row_indices}, {state_indices},"f"{kraus_index}{col_indices}{new_col_indices}->{new_state_indices}")self._state=qnp.einsum(einsum_indices,kraus,self._state,kraus_dagger)def_apply_channel_tensordot(self,kraus,wires):r"""Apply a quantum channel specified by a list of Kraus operators to subsystems of the quantum state. For a unitary gate, there is a single Kraus operator. Args: kraus (list[array]): Kraus operators wires (Wires): target wires """channel_wires=self.map_wires(wires)num_ch_wires=len(channel_wires)# Shape kraus operators and cast them to complex data typekraus_shape=[2]*(num_ch_wires*2)kraus=[qnp.cast(qnp.reshape(k,kraus_shape),dtype=self.C_DTYPE)forkinkraus]# row indices of the quantum state affected by this operationrow_wires_list=channel_wires.tolist()# column indices are shifted by the number of wirescol_wires_list=[w+self.num_wiresforwinrow_wires_list]channel_col_ids=list(range(num_ch_wires,2*num_ch_wires))axes_left=[channel_col_ids,row_wires_list]# Use column indices instead or rows to incorporate transposition of K^\daggeraxes_right=[col_wires_list,channel_col_ids]# Apply the Kraus operators, and sum over all Kraus operators afterwardsdef_conjugate_state_with(k):"""Perform the double tensor product k @ self._state @ k.conj(). The `axes_left` and `axes_right` arguments are taken from the ambient variable space and `axes_right` is assumed to incorporate the tensor product and the transposition of k.conj() simultaneously."""returnqnp.tensordot(qnp.tensordot(k,self._state,axes_left),qnp.conj(k),axes_right)iflen(kraus)==1:_state=_conjugate_state_with(kraus[0])else:_state=qnp.sum(qnp.stack([_conjugate_state_with(k)forkinkraus]),axis=0)# Permute the affected axes to their destination places.# The row indices of the kraus operators are moved from the beginning to the original# target row locations, the column indices from the end to the target column locationssource_left=list(range(num_ch_wires))dest_left=row_wires_listsource_right=list(range(-num_ch_wires,0))dest_right=col_wires_listself._state=qnp.moveaxis(_state,source_left+source_right,dest_left+dest_right)def_apply_diagonal_unitary(self,eigvals,wires):r"""Apply a diagonal unitary gate specified by a list of eigenvalues. This method uses the fact that the unitary is diagonal for a more efficient implementation. Args: eigvals (array): eigenvalues (phases) of the diagonal unitary wires (Wires): target wires """channel_wires=self.map_wires(wires)eigvals=qnp.stack(eigvals)# reshape vectorseigvals=qnp.cast(qnp.reshape(eigvals,[2]*len(channel_wires)),dtype=self.C_DTYPE)# Tensor indices of the state. For each qubit, need an index for rows *and* columnsstate_indices=ABC[:2*self.num_wires]# row indices of the quantum state affected by this operationrow_wires_list=channel_wires.tolist()row_indices="".join(ABC_ARRAY[row_wires_list].tolist())# column indices are shifted by the number of wirescol_wires_list=[w+self.num_wiresforwinrow_wires_list]col_indices="".join(ABC_ARRAY[col_wires_list].tolist())einsum_indices=f"{row_indices},{state_indices},{col_indices}->{state_indices}"self._state=qnp.einsum(einsum_indices,eigvals,self._state,qnp.conj(eigvals))def_apply_basis_state(self,state,wires):"""Initialize the device in a specified computational basis state. Args: state (array[int]): computational basis state of shape ``(wires,)`` consisting of 0s and 1s. wires (Wires): wires that the provided computational state should be initialized on """# translate to wire labels used by devicedevice_wires=self.map_wires(wires)# length of basis state parametern_basis_state=len(state)ifnotset(state).issubset({0,1}):raiseValueError("BasisState parameter must consist of 0 or 1 integers.")ifn_basis_state!=len(device_wires):raiseValueError("BasisState parameter and wires must be of equal length.")# get computational basis state numberbasis_states=2**(self.num_wires-1-device_wires.toarray())num=int(qnp.dot(state,basis_states))self._state=self._create_basis_state(num)def_apply_state_vector(self,state,device_wires):"""Initialize the internal state in a specified pure state. Args: state (array[complex]): normalized input state of length ``2**len(wires)`` device_wires (Wires): wires that get initialized in the state """# translate to wire labels used by devicedevice_wires=self.map_wires(device_wires)state=qnp.asarray(state,dtype=self.C_DTYPE)n_state_vector=state.shape[0]ifstate.ndim!=1orn_state_vector!=2**len(device_wires):raiseValueError("State vector must be of length 2**wires.")ifnotqnp.allclose(qnp.linalg.norm(state,ord=2),1.0,atol=tolerance):raiseValueError("Sum of amplitudes-squared does not equal one.")iflen(device_wires)==self.num_wiresandsorted(device_wires.labels)==list(device_wires.labels):# Initialize the entire wires with the staterho=qnp.outer(state,qnp.conj(state))self._state=qnp.reshape(rho,[2]*2*self.num_wires)else:# generate basis states on subset of qubits via the cartesian productbasis_states=qnp.asarray(list(itertools.product([0,1],repeat=len(device_wires))),dtype=int)# get basis states to alter on full set of qubitsunravelled_indices=qnp.zeros((2**len(device_wires),self.num_wires),dtype=int)unravelled_indices[:,device_wires]=basis_states# get indices for which the state is changed to input state vector elementsravelled_indices=qnp.ravel_multi_index(unravelled_indices.T,[2]*self.num_wires)state=qnp.scatter(ravelled_indices,state,[2**self.num_wires])rho=qnp.outer(state,qnp.conj(state))rho=qnp.reshape(rho,[2]*2*self.num_wires)self._state=qnp.asarray(rho,dtype=self.C_DTYPE)def_apply_density_matrix(self,state,device_wires):r"""Initialize the internal state in a specified mixed state. If not all the wires are specified in the full state :math:`\rho`, remaining subsystem is filled by `\mathrm{tr}_in(\rho)`, which results in the full system state :math:`\mathrm{tr}_{in}(\rho) \otimes \rho_{in}`, where :math:`\rho_{in}` is the argument `state` of this function and :math:`\mathrm{tr}_{in}` is a partial trace over the subsystem to be replaced by this operation. Args: state (array[complex]): density matrix of length ``(2**len(wires), 2**len(wires))`` device_wires (Wires): wires that get initialized in the state """# translate to wire labels used by devicedevice_wires=self.map_wires(device_wires)state=qnp.asarray(state,dtype=self.C_DTYPE)state=qnp.reshape(state,(-1,))state_dim=2**len(device_wires)dm_dim=state_dim**2ifdm_dim!=state.shape[0]:raiseValueError("Density matrix must be of length (2**wires, 2**wires)")ifnotqml.math.is_abstract(state)andnotqnp.allclose(qnp.trace(qnp.reshape(state,(state_dim,state_dim))),1.0,atol=tolerance):raiseValueError("Trace of density matrix is not equal one.")iflen(device_wires)==self.num_wiresandsorted(device_wires.labels)==list(device_wires.labels):# Initialize the entire wires with the stateself._state=qnp.reshape(state,[2]*2*self.num_wires)self._pre_rotated_state=self._stateelse:# Initialize tr_in(ρ) ⊗ ρ_in with transposed wires where ρ is the density matrix before this operation.complement_wires=list(sorted(list(set(range(self.num_wires))-set(device_wires))))sigma=self.density_matrix(Wires(complement_wires))rho=qnp.kron(sigma,state.reshape(state_dim,state_dim))rho=rho.reshape([2]*2*self.num_wires)# Construct transposition axis to revert back to the original wire orderleft_axes=[]right_axes=[]complement_wires_count=len(complement_wires)foriinrange(self.num_wires):ifiindevice_wires:index=device_wires.index(i)left_axes.append(complement_wires_count+index)right_axes.append(complement_wires_count+index+self.num_wires)elifiincomplement_wires:index=complement_wires.index(i)left_axes.append(index)right_axes.append(index+self.num_wires)transpose_axes=left_axes+right_axesrho=qnp.transpose(rho,axes=transpose_axes)assertqml.math.is_abstract(rho)orqnp.allclose(qnp.trace(qnp.reshape(rho,(2**self.num_wires,2**self.num_wires))),1.0,atol=tolerance,)self._state=qnp.asarray(rho,dtype=self.C_DTYPE)self._pre_rotated_state=self._statedef_snapshot_measurements(self,density_matrix,measurement):"""Perform state-based snapshot measurement"""meas_wires=self.wiresifnotmeasurement.wireselsemeasurement.wirespre_rotated_state=self._stateifisinstance(measurement,(ProbabilityMP,ExpectationMP,VarianceMP)):fordiag_gateinmeasurement.diagonalizing_gates():self._apply_operation(diag_gate)ifisinstance(measurement,(StateMP,DensityMatrixMP)):map_wires=self.map_wires(meas_wires)snap_result=qml.math.reduce_dm(density_matrix,indices=map_wires,c_dtype=self.C_DTYPE)elifisinstance(measurement,PurityMP):map_wires=self.map_wires(meas_wires)snap_result=qml.math.purity(density_matrix,indices=map_wires,c_dtype=self.C_DTYPE)elifisinstance(measurement,ProbabilityMP):snap_result=self.analytic_probability(wires=meas_wires)elifisinstance(measurement,ExpectationMP):eigvals=self._asarray(measurement.obs.eigvals(),dtype=self.R_DTYPE)probs=self.analytic_probability(wires=meas_wires)snap_result=self._dot(probs,eigvals)elifisinstance(measurement,VarianceMP):eigvals=self._asarray(measurement.obs.eigvals(),dtype=self.R_DTYPE)probs=self.analytic_probability(wires=meas_wires)snap_result=self._dot(probs,(eigvals**2))-self._dot(probs,eigvals)**2elifisinstance(measurement,VnEntropyMP):base=measurement.log_basemap_wires=self.map_wires(meas_wires)snap_result=qml.math.vn_entropy(density_matrix,indices=map_wires,c_dtype=self.C_DTYPE,base=base)elifisinstance(measurement,MutualInfoMP):base=measurement.log_basewires0,wires1=list(map(self.map_wires,measurement.raw_wires))snap_result=qml.math.mutual_info(density_matrix,indices0=wires0,indices1=wires1,c_dtype=self.C_DTYPE,base=base,)else:raiseqml.DeviceError(f"Snapshots of {type(measurement)} are not yet supported on default.mixed")self._state=pre_rotated_stateself._pre_rotated_state=self._statereturnsnap_resultdef_apply_snapshot(self,operation):"""Applies the snapshot operation"""measurement=operation.hyperparameters["measurement"]ifself._debuggerandself._debugger.active:dim=2**self.num_wiresdensity_matrix=qnp.reshape(self._state,(dim,dim))snapshot_result=self._snapshot_measurements(density_matrix,measurement)ifoperation.tag:self._debugger.snapshots[operation.tag]=snapshot_resultelse:self._debugger.snapshots[len(self._debugger.snapshots)]=snapshot_resultdef_apply_operation(self,operation):"""Applies operations to the internal device state. Args: operation (.Operation): operation to apply on the device """wires=operation.wiresifoperation.name=="Identity":returnifisinstance(operation,StatePrep):self._apply_state_vector(operation.parameters[0],wires)returnifisinstance(operation,BasisState):self._apply_basis_state(operation.parameters[0],wires)returnifisinstance(operation,QubitDensityMatrix):self._apply_density_matrix(operation.parameters[0],wires)returnifisinstance(operation,Snapshot):self._apply_snapshot(operation)returnmatrices=self._get_kraus(operation)ifoperationindiagonal_in_z_basis:self._apply_diagonal_unitary(matrices,wires)else:num_op_wires=len(wires)interface=qml.math.get_interface(self._state,*matrices)# Use tensordot for Autograd and Numpy if there are more than 2 wires# Use tensordot in any case for more than 7 wires, as einsum does not support this caseif(num_op_wires>2andinterfacein{"autograd","numpy"})ornum_op_wires>7:self._apply_channel_tensordot(matrices,wires)else:self._apply_channel(matrices,wires)# pylint: disable=arguments-differ
[docs]@debug_loggerdefexecute(self,circuit,**kwargs):"""Execute a queue of quantum operations on the device and then measure the given observables. Applies a readout error to the measurement outcomes of any observable if readout_prob is non-zero. This is done by finding the list of measured wires on which BitFlip channels are applied in the :meth:`apply`. For plugin developers: instead of overwriting this, consider implementing a suitable subset of * :meth:`apply` * :meth:`~.generate_samples` * :meth:`~.probability` Additional keyword arguments may be passed to this method that can be utilised by :meth:`apply`. An example would be passing the ``QNode`` hash that can be used later for parametric compilation. Args: circuit (QuantumTape): circuit to execute on the device Raises: QuantumFunctionError: if the value of :attr:`~.Observable.return_type` is not supported Returns: array[float]: measured value(s) """ifself.readout_err:wires_list=[]formincircuit.measurements:ifisinstance(m,StateMP):# State: This returns pre-rotated state, so no readout error.# Assumed to only be allowed if it's the only measurement.self.measured_wires=[]returnsuper().execute(circuit,**kwargs)ifisinstance(m,(SampleMP,CountsMP))andm.wiresin(qml.wires.Wires([]),self.wires,):# Sample, Counts: Readout error applied to all device wires when wires# not specified or all wires specified.self.measured_wires=self.wiresreturnsuper().execute(circuit,**kwargs)ifisinstance(m,(VnEntropyMP,MutualInfoMP)):# VnEntropy, MutualInfo: Computed for the state# prior to measurement. So, readout error need not be applied on the# corresponding device wires.continuewires_list.append(m.wires)self.measured_wires=qml.wires.Wires.all_wires(wires_list)returnsuper().execute(circuit,**kwargs)
[docs]@debug_loggerdefapply(self,operations,rotations=None,**kwargs):rotations=rotationsor[]# apply the circuit operationsfori,operationinenumerate(operations):ifi>0andisinstance(operation,(StatePrep,BasisState)):raiseqml.DeviceError(f"Operation {operation.name} cannot be used after other Operations have already been applied "f"on a {self.short_name} device.")foroperationinoperations:self._apply_operation(operation)# store the pre-rotated stateself._pre_rotated_state=self._state# apply the circuit rotationsforoperationinrotations:self._apply_operation(operation)ifself.readout_err:forkinself.measured_wires:bit_flip=qml.BitFlip(self.readout_err,wires=k)self._apply_operation(bit_flip)
[docs]@simulator_tracking@single_tape_supportclassDefaultMixedNewAPI(Device):r"""A PennyLane Python-based device for mixed-state qubit simulation. Args: wires (int, Iterable[Number, str]): Number of wires present on the device, or iterable that contains unique labels for the wires as numbers (i.e., ``[-1, 0, 2]``) or strings (``['ancilla', 'q1', 'q2']``). shots (int, Sequence[int], Sequence[Union[int, Sequence[int]]]): The default number of shots to use in executions involving this device. seed (Union[str, None, int, array_like[int], SeedSequence, BitGenerator, Generator, jax.random.PRNGKey]): A seed-like parameter matching that of ``seed`` for ``numpy.random.default_rng``, or a request to seed from numpy's global random number generator. The default, ``seed="global"`` pulls a seed from NumPy's global generator. ``seed=None`` will pull a seed from the OS entropy. If a ``jax.random.PRNGKey`` is passed as the seed, a JAX-specific sampling function using ``jax.random.choice`` and the ``PRNGKey`` will be used for sampling rather than ``numpy.random.default_rng``. r_dtype (numpy.dtype): Real datatype to use for computations. Default is np.float64. c_dtype (numpy.dtype): Complex datatype to use for computations. Default is np.complex128. readout_prob (float): Probability of readout error for qubit measurements. Must be in :math:`[0,1]`. """_device_options=("rng","prng_key")# tuple of string names for all the device options.@propertydefname(self):"""The name of the device."""return"default.mixed"# pylint: disable=too-many-positional-arguments@debug_logger_initdef__init__(# pylint: disable=too-many-argumentsself,wires=None,shots=None,seed="global",# The following parameters are inherited from DefaultMixedreadout_prob=None,)->None:ifisinstance(wires,int)andwires>23:raiseValueError("This device does not currently support computations on more than 23 wires")self.readout_err=readout_prob# Check that the readout error probability, if entered, is either integer or float in [0,1]ifself.readout_errisnotNone:ifnotisinstance(self.readout_err,float)andnotisinstance(self.readout_err,int):raiseTypeError("The readout error probability should be an integer or a floating-point number in [0,1].")ifself.readout_err<0orself.readout_err>1:raiseValueError("The readout error probability should be in the range [0,1].")super().__init__(wires=wires,shots=shots)# Seed settingseed=np.random.randint(0,high=10000000)ifseed=="global"elseseedifqml.math.get_interface(seed)=="jax":self._prng_key=seedself._rng=np.random.default_rng(None)else:self._prng_key=Noneself._rng=np.random.default_rng(seed)self._debugger=None
[docs]@debug_loggerdefsupports_derivatives(self,execution_config:Optional[ExecutionConfig]=None,circuit:Optional[QuantumScript]=None,)->bool:"""Check whether or not derivatives are available for a given configuration and circuit. ``DefaultQubitMixed`` supports backpropagation derivatives with analytic results. Args: execution_config (ExecutionConfig): The configuration of the desired derivative calculation. circuit (QuantumTape): An optional circuit to check derivatives support for. Returns: bool: Whether or not a derivative can be calculated provided the given information. """ifexecution_configisNoneorexecution_config.gradient_methodin{"backprop","best"}:returncircuitisNoneornotcircuit.shotsreturnFalse
def_setup_execution_config(self,execution_config:ExecutionConfig)->ExecutionConfig:"""This is a private helper for ``preprocess`` that sets up the execution config. Args: execution_config (ExecutionConfig): an unprocessed execution config. Returns: ExecutionConfig: a preprocessed execution config. """updated_values={}# Add gradient relatedifexecution_config.gradient_method=="best":updated_values["gradient_method"]="backprop"updated_values["use_device_gradient"]=execution_config.gradient_methodin{"backprop","best",}updated_values["grad_on_execution"]=Falseifnotexecution_config.gradient_methodin{"best","backprop",None}:execution_config.interface=None# Add device optionsupdated_values["device_options"]=dict(execution_config.device_options)# copyforoptioninexecution_config.device_options:ifoptionnotinself._device_options:raiseqml.DeviceError(f"device option {option} not present on {self}")foroptioninself._device_options:ifoptionnotinupdated_values["device_options"]:updated_values["device_options"][option]=getattr(self,f"_{option}")returnreplace(execution_config,**updated_values)
[docs]@debug_loggerdefpreprocess(self,execution_config:ExecutionConfig=None,)->tuple[TransformProgram,ExecutionConfig]:"""This function defines the device transform program to be applied and an updated device configuration. Args: execution_config (Union[ExecutionConfig, Sequence[ExecutionConfig]]): A data structure describing the parameters needed to fully describe the execution. Returns: TransformProgram, ExecutionConfig: A transform program that when called returns ``QuantumTape`` objects that the device can natively execute, as well as a postprocessing function to be called after execution, and a configuration with unset specifications filled in. This device: * Supports any qubit operations that provide a matrix * Supports any qubit channel that provides Kraus matrices """execution_config=execution_configorExecutionConfig()config=self._setup_execution_config(execution_config)transform_program=TransformProgram()# Defer first since it addes wires to the devicetransform_program.add_transform(qml.defer_measurements,allow_postselect=False)transform_program.add_transform(decompose,stopping_condition=stopping_condition,name=self.name,)# TODO: If the setup_execution_config method becomes circuit-dependent in the future,# we should handle this case directly within setup_execution_config. This would# eliminate the need for the no_sampling transform in this section.ifconfig.gradient_method=="backprop":transform_program.add_transform(no_sampling,name="backprop + default.mixed")ifself.readout_errisnotNone:transform_program.add_transform(warn_readout_error_state)# Add the validate sectiontransform_program.add_transform(validate_device_wires,self.wires,name=self.name)transform_program.add_transform(validate_measurements,name=self.name)transform_program.add_transform(validate_observables,stopping_condition=observable_stopping_condition,name=self.name)returntransform_program,config