# Copyright 2018-2023 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."""This module contains the Shots class to hold shot-related information."""fromcollections.abcimportSequence# pylint:disable=inconsistent-return-statementsfromtypingimportNamedTuple,Union
[docs]classShotCopies(NamedTuple):"""A namedtuple that represents a shot quantity being repeated some number of times. For example, ``ShotCopies(10 shots x 2)`` indicates two executions with 10 shots each for 20 shots total. """shots:intcopies:intdef__str__(self):"""The string representation of the class"""returnf"{self.shots} shots{' x '+str(self.copies)ifself.copies>1else''}"def__repr__(self):"""The representation of the class"""returnf"ShotCopies({self.shots} shots x {self.copies})"
defvalid_int(s):"""Returns True if s is a positive integer."""returnisinstance(s,int)ands>0defvalid_tuple(s):"""Returns True if s is a tuple of the form (shots, copies)."""returnisinstance(s,Sequence)andlen(s)==2andvalid_int(s[0])andvalid_int(s[1])
[docs]classShots:""" A data class that stores shot information. Args: shots (Union[None, int, Sequence[int, Tuple[int, int]]]): Raw shot information Defining shots enables users to specify circuit executions, and the Shots class standardizes the internal representation of shots. There are three ways to specify shot values: * The value ``None`` * A positive integer * A sequence consisting of either positive integers or a tuple-pair of positive integers of the form ``(shots, copies)`` The tuple-pair of the form ``(shots, copies)`` is represented internally by a NamedTuple called :class:`~ShotCopies`. The first value is the number of shots to execute, and the second value is the number of times to repeat a circuit with that number of shots. The ``Shots`` class exposes two properties: * ``total_shots``, the total number of shots to be executed * ``shot_vector``, the tuple of :class:`~ShotCopies` to be executed Instances of this class are static. If an instance is passed to the constructor, that same instance is returned. If an instance is constructed with a ``None`` value, ``total_shots`` will be ``None``. This indicates analytic execution. A ``Shots`` object created with a ``None`` value is Falsy, while any other value results in a Truthy object: >>> bool(Shots(None)), bool(Shots(1)) (False, True) **Examples** Example constructing a Shots instance with ``None``: >>> shots = Shots(None) >>> shots.total_shots, shots.shot_vector (None, ()) Example constructing a Shots instance with an int: >>> shots = Shots(100) >>> shots.total_shots, shots.shot_vector (100, (ShotCopies(100 shots),)) Example constructing a Shots instance with another instance: >>> shots = Shots(100) >>> Shots(shots) is shots True Example constructing a Shots instance with a sequence of ints: >>> shots = Shots([100, 200]) >>> shots.total_shots, shots.shot_vector (300, (ShotCopies(100 shots x 1), ShotCopies(200 shots x 1))) Example constructing a Shots instance with a sequence of tuple-pairs: >>> shots = Shots(((100, 3), (200, 4),)) >>> shots.total_shots, shots.shot_vector (1100, (ShotCopies(100 shots x 3), ShotCopies(200 shots x 4))) Example constructing a Shots instance with a sequence of both ints and tuple-pairs. Note that the first stand-alone ``100`` gets absorbed into the subsequent tuple because the shot value matches: >>> shots = Shots((10, 100, (100, 3), (200, 4),)) >>> shots.total_shots, shots.shot_vector (1210, (ShotCopies(10 shots x 1), ShotCopies(100 shots x 4), ShotCopies(200 shots x 4))) Example constructing a Shots instance by multiplying an existing one by an int or float: >>> Shots(100) * 2 Shots(total_shots=200, shot_vector=(ShotCopies(200 shots x 1),)) >>> Shots([7, (100, 2)]) * 1.5 Shots(total_shots=310, shot_vector=(ShotCopies(10 shots x 1), ShotCopies(150 shots x 2))) Example constructing a Shots instance by adding two existing instances together: >>> Shots(100) + Shots(((10,2),)) Shots(total_shots=120, shot_vector=(ShotCopies(100 shots x 1), ShotCopies(10 shots x 2))) One should also note that specifying a single tuple of length 2 is considered two different shot values, and *not* a tuple-pair representing shots and copies to avoid special behaviour depending on the iterable type: >>> shots = Shots((100, 2)) >>> shots.total_shots, shots.shot_vector (102, (ShotCopies(100 shots x 1), ShotCopies(2 shots x 1))) >>> shots = Shots(((100, 2),)) >>> shots.total_shots, shots.shot_vector (200, (ShotCopies(100 shots x 2),)) """total_shots:int=None"""The total number of shots to be executed."""shot_vector:tuple[ShotCopies]=None"""The tuple of :class:`~ShotCopies` to be executed. Each element is of the form ``(shots, copies)``."""_SHOT_ERROR=ValueError("Shots must be a single positive integer, a tuple pair of the form (shots, copies), or a sequence of these types.")_frozen=Falsedef__new__(cls,shots=None):returnshotsifisinstance(shots,cls)elseobject.__new__(cls)def__setattr__(self,name,value):ifself._frozen:raiseAttributeError("Shots is an immutable class. Consider creating a new instance if you need different shot values.")returnsuper().__setattr__(name,value)def__init__(self,shots=None):ifshotsisNone:self.total_shots=Noneself.shot_vector=()elifisinstance(shots,int):ifshots<1:raiseself._SHOT_ERRORself.total_shots=shotsself.shot_vector=(ShotCopies(shots,1),)elifisinstance(shots,Sequence):ifnotall(valid_int(s)orvalid_tuple(s)forsinshots):raiseself._SHOT_ERRORself.__all_tuple_init__([sifisinstance(s,Sequence)else(s,1)forsinshots])elifisinstance(shots,self.__class__):return# self already _is_ shots as defined by __new__else:raiseself._SHOT_ERRORself._frozen=Truedef__copy__(self):returnselfdef__deepcopy__(self,memo):memo[id(self)]=selfreturnselfdef__str__(self):"""The string representation of the class"""ifnotself.has_partitioned_shots:returnf"Shots(total={self.total_shots})"shot_copy_str=", ".join([str(sc)forscinself.shot_vector])orNonereturnf"Shots(total={self.total_shots}, vector=[{shot_copy_str}])"def__repr__(self):"""The representation of the class"""returnf"Shots(total_shots={self.total_shots}, shot_vector={self.shot_vector})"def__eq__(self,other):"""Equality between Shot instances."""return(isinstance(other,Shots)andself.total_shots==other.total_shotsandself.shot_vector==other.shot_vector)def__hash__(self):"""Hash for a given Shot instance."""returnhash(self.shot_vector)def__iter__(self):forshot_copyinself.shot_vector:for_inrange(shot_copy.copies):yieldshot_copy.shotsdef__all_tuple_init__(self,shots:Sequence[tuple]):res=[]total_shots=0current_shots,current_copies=shots[0]forsinshots[1:]:ifs[0]==current_shots:current_copies+=s[1]else:res.append(ShotCopies(current_shots,current_copies))total_shots+=current_shots*current_copiescurrent_shots,current_copies=sself.shot_vector=tuple(res+[ShotCopies(current_shots,current_copies)])self.total_shots=total_shots+current_shots*current_copiesdef__bool__(self):returnself.total_shotsisnotNonedef__add__(self,other):returnadd_shots(self,other)def__mul__(self,scalar):ifnotisinstance(scalar,(int,float)):raiseTypeError("Can't multiply Shots with non-integer or float type.")ifself.total_shotsisNone:returnselfscaled_shot_vector=tuple(ShotCopies(int(i.shots*scalar),i.copies)foriinself.shot_vector)returnself.__class__(scaled_shot_vector)def__rmul__(self,scalar):returnself.__mul__(scalar)@propertydefhas_partitioned_shots(self):""" Evaluates to True if this instance represents either multiple shot quantities, or the same shot quantity repeated multiple times. Returns: bool: whether shots are partitioned """ifnotself:returnFalsereturnlen(self.shot_vector)>1orself.shot_vector[0].copies>1@propertydefnum_copies(self):"""The total number of copies of any shot quantity."""returnsum(s.copiesforsinself.shot_vector)
[docs]defbins(self):""" Yields: tuple: A tuple containing the lower and upper bounds for each shot quantity in shot_vector. Example: >>> shots = Shots((1, 1, 2, 3)) >>> list(shots.bins()) [(0,1), (1,2), (2,4), (4,7)] """lower_bound=0forscinself.shot_vector:for_inrange(sc.copies):upper_bound=lower_bound+sc.shotsyieldlower_bound,upper_boundlower_bound=upper_bound
[docs]defadd_shots(s1:Shots,s2:Shots)->Shots:"""Add two :class:`~.Shots` objects by concatenating their shot vectors. Args: s1 (Shots): a Shots object to add s2 (Shots): a Shots object to add Returns: Shots: a :class:`~.Shots` object built by concatenating the shot vectors of ``s1`` and ``s2`` Example: >>> s1 = Shots((5, (10, 2))) >>> s2 = Shots((3, 2, (10, 3))) >>> print(qml.measurements.add_shots(s1, s2)) Shots(total=60, vector=[5 shots, 10 shots x 2, 3 shots, 2 shots, 10 shots x 3]) """ifs1.total_shotsisNone:returns2ifs2.total_shotsisNone:returns1shot_vector=[]forshotins1.shot_vector+s2.shot_vector:shot_vector.append((shot.shots,shot.copies))returnShots(shots=shot_vector)