# Copyright 2018-2024 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."""Contains the ReferenceQubit device, a minimal device that can be used for testingand plugin development purposes."""importnumpyasnpimportpennylaneasqmlfrom.device_apiimportDevicefrom.execution_configimportDefaultExecutionConfigfrom.modifiersimportsimulator_tracking,single_tape_supportfrom.preprocessimportdecompose,validate_device_wires,validate_measurements
[docs]defsample_state(state:np.ndarray,shots:int,seed=None):"""Generate samples from the provided state and number of shots."""probs=np.imag(state)**2+np.real(state)**2basis_states=np.arange(len(probs))num_wires=int(np.log2(len(probs)))rng=np.random.default_rng(seed)probs/=np.sum(probs)# Fix: Normalize to prevent sum ≠ 1 errors in NumPy 2.0+basis_samples=rng.choice(basis_states,shots,p=probs)# convert basis state integers to array of booleansbin_strings=(format(s,f"0{num_wires}b")forsinbasis_samples)returnnp.array([[int(val)forvalins]forsinbin_strings])
[docs]defsimulate(tape:qml.tape.QuantumTape,seed=None)->qml.typing.Result:"""Simulate a tape and turn it into results. Args: tape (.QuantumTape): a representation of a circuit seed (Any): A seed to use to control the generation of samples. """# 1) create the initial statestate=np.zeros(2**len(tape.wires))state[0]=1.0# 2) apply all the operationsforopintape.operations:op_mat=op.matrix(wire_order=tape.wires)ifqml.math.get_interface(op_mat)!="numpy":raiseValueError("Reference qubit can only work with numpy data.")state=qml.math.matmul(op_mat,state)# 3) perform measurements# note that shots are pulled from the tape, not from the deviceiftape.shots:samples=sample_state(state,shots=tape.shots.total_shots,seed=seed)# Shot vector supportresults=[]forlower,upperintape.shots.bins():sub_samples=samples[lower:upper]results.append(tuple(mp.process_samples(sub_samples,tape.wires)formpintape.measurements))iflen(tape.measurements)==1:results=[res[0]forresinresults]ifnottape.shots.has_partitioned_shots:results=results[0]else:results=tuple(results)else:results=tuple(mp.process_state(state,tape.wires)formpintape.measurements)iflen(tape.measurements)==1:results=results[0]returnresults
[docs]defsupports_operation(op:qml.operation.Operator)->bool:"""This function used by preprocessing determines what operations are natively supported by the device. While in theory ``simulate`` can support any operation with a matrix, we limit the target gate set for improved testing and reference purposes. """returngetattr(op,"name",None)inoperations
[docs]@simulator_tracking# update device.tracker with some relevant information@single_tape_support# add support for device.execute(tape) in addition to device.execute((tape,))classReferenceQubit(Device):"""A slimmed down numpy-based simulator for reference and testing purposes. 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 (``['aux', 'q1', 'q2']``). Default ``None`` if not specified. While this device allows for ``wires`` to be unspecified at construction time, other devices may make this argument mandatory. Devices can also implement additional restrictions on the possible wires. shots (int, Sequence[int], Sequence[Union[int, Sequence[int]]]): The default number of shots to use in executions involving this device. Note that during execution, shots are pulled from the circuit, not from the 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``. This is an optional keyword argument added to follow recommend NumPy best practices. Other devices do not need this parameter if it does not make sense for them. """name="reference.qubit"def__init__(self,wires=None,shots=None,seed=None):super().__init__(wires=wires,shots=shots)# seed and rng not necessary for a device, but part of recommended# numpy practices to use a local random number generatorself._rng=np.random.default_rng(seed)
[docs]defpreprocess(self,execution_config=DefaultExecutionConfig):# Here we convert an arbitrary tape into one natively supported by the deviceprogram=qml.transforms.core.TransformProgram()program.add_transform(validate_device_wires,wires=self.wires,name="reference.qubit")program.add_transform(qml.defer_measurements,allow_postselect=False)program.add_transform(qml.transforms.split_non_commuting)program.add_transform(qml.transforms.diagonalize_measurements)program.add_transform(decompose,stopping_condition=supports_operation,skip_initial_state_prep=False,name="reference.qubit",)program.add_transform(validate_measurements,name="reference.qubit")program.add_transform(qml.transforms.broadcast_expand)# no need to preprocess the config as the device does not support derivativesreturnprogram,execution_config