# 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."""Stores classes and logic to aggregate all the resource information from a quantum workflow."""from__future__importannotationsimportcopyfromabcimportabstractmethodfromcollectionsimportdefaultdictfromdataclassesimportdataclass,fieldfromtypingimportTuplefrompennylane.measurementsimportShots,add_shotsfrompennylane.operationimportOperation
[docs]@dataclass(frozen=True)classResources:r"""Contains attributes which store key resources such as number of gates, number of wires, shots, depth and gate types. Args: num_wires (int): number of qubits num_gates (int): number of gates gate_types (dict): dictionary storing operation names (str) as keys and the number of times they are used in the circuit (int) as values gate_sizes (dict): dictionary storing the number of :math:`n` qubit gates in the circuit as a key-value pair where :math:`n` is the key and the number of occurances is the value depth (int): the depth of the circuit defined as the maximum number of non-parallel operations shots (Shots): number of samples to generate .. details:: The resources being tracked can be accessed as class attributes. Additionally, the :code:`Resources` instance can be nicely displayed in the console. **Example** >>> from pennylane.resource import Resources >>> r = Resources(num_wires=2, num_gates=2, gate_types={'Hadamard': 1, 'CNOT':1}, gate_sizes={1: 1, 2: 1}, depth=2) >>> print(r) num_wires: 2 num_gates: 2 depth: 2 shots: Shots(total=None) gate_types: {'Hadamard': 1, 'CNOT': 1} gate_sizes: {1: 1, 2: 1} :class:`~.Resources` objects can be added together or multiplied by a scalar. >>> from pennylane.resource import Resources >>> r1 = Resources(num_wires=2, num_gates=2, gate_types={'Hadamard': 1, 'CNOT':1}, gate_sizes={1: 1, 2: 1}, depth=2) >>> r2 = Resources(num_wires=2, num_gates=2, gate_types={'RX': 1, 'CNOT':1}, gate_sizes={1: 1, 2: 1}, depth=2) >>> print(r1 + r2) wires: 2 gates: 4 depth: 4 shots: Shots(total=None) gate_types: {'Hadamard': 1, 'CNOT': 2, 'RX': 1} gate_sizes: {1: 2, 2: 2} >>> print(r1 * 2) wires: 2 gates: 4 depth: 4 shots: Shots(total=None) gate_types: {'Hadamard': 2, 'CNOT': 2} gate_sizes: {1: 2, 2: 2} """num_wires:int=0num_gates:int=0gate_types:dict=field(default_factory=dict)gate_sizes:dict=field(default_factory=dict)depth:int=0shots:Shots=field(default_factory=Shots)def__add__(self,other:Resources):r"""Adds two :class:`~resource.Resources` objects together as if the circuits were executed in series. Args: other (Resources): the resource object to add Returns: Resources: the combined resources .. details:: **Example** First we build two :class:`~.resource.Resources` objects. .. code-block:: python3 from pennylane.measurements import Shots from pennylane.resource import Resources r1 = Resources( num_wires = 2, num_gates = 2, gate_types = {"Hadamard": 1, "CNOT": 1}, gate_sizes = {1: 1, 2: 1}, depth = 2, shots = Shots(10) ) r2 = Resources( num_wires = 3, num_gates = 2, gate_types = {"RX": 1, "CNOT": 1}, gate_sizes = {1: 1, 2: 1}, depth = 1, shots = Shots((5, (2, 10))) ) Now we print their sum. >>> print(r1 + r2) wires: 3 gates: 4 depth: 3 shots: Shots(total=35, vector=[10 shots, 5 shots, 2 shots x 10]) gate_types: {'Hadamard': 1, 'CNOT': 2, 'RX': 1} gate_sizes: {1: 2, 2: 2} """returnadd_in_series(self,other)def__mul__(self,scalar:int):r"""Multiply the :class:`~resource.Resources` object by a scalar as if that many copies of the circuit were executed in series Args: scalar (int): the scalar to multiply the resource object by Returns: Resources: the combined resources .. details:: **Example** First we build a :class:`~.resource.Resources` object. .. code-block:: python3 from pennylane.measurements import Shots from pennylane.resource import Resources resources = Resources( num_wires = 2, num_gates = 2, gate_types = {"Hadamard": 1, "CNOT": 1}, gate_sizes = {1: 1, 2: 1}, depth = 2, shots = Shots(10) ) Now we print the product. >>> print(resources * 2) wires: 2 gates: 4 depth: 4 shots: Shots(total=20) gate_types: {'Hadamard': 2, 'CNOT': 2} gate_sizes: {1: 2, 2: 2} """returnmul_in_series(self,scalar)__rmul__=__mul__def__str__(self):keys=["num_wires","num_gates","depth"]vals=[self.num_wires,self.num_gates,self.depth]items="\n".join([str(i)foriinzip(keys,vals)])items=items.replace("('","")items=items.replace("',",":")items=items.replace(")","")items+=f"\nshots: {str(self.shots)}"gate_type_str=", ".join([f"'{gate_name}': {count}"forgate_name,countinself.gate_types.items()])items+="\ngate_types:\n{"+gate_type_str+"}"gate_size_str=", ".join([f"{n_gate}: {count}"forn_gate,countinself.gate_sizes.items()])items+="\ngate_sizes:\n{"+gate_size_str+"}"returnitemsdef_ipython_display_(self):"""Displays __str__ in ipython instead of __repr__"""print(str(self))
[docs]classResourcesOperation(Operation):r"""Base class that represents quantum gates or channels applied to quantum states and stores the resource requirements of the quantum gate. .. note:: Child classes must implement the :func:`~.ResourcesOperation.resources` method which computes the resource requirements of the operation. """
[docs]@abstractmethoddefresources(self)->Resources:r"""Compute the resources required for this operation. Returns: Resources: The resources required by this operation. **Examples** >>> class CustomOp(ResourcesOperation): ... num_wires = 2 ... def resources(self): ... return Resources(num_wires=self.num_wires, num_gates=3, depth=2) ... >>> op = CustomOp(wires=[0, 1]) >>> print(op.resources()) num_wires: 2 num_gates: 3 depth: 2 shots: Shots(total=None) gate_types: {} gate_sizes: {} """
[docs]defadd_in_series(r1:Resources,r2:Resources)->Resources:r""" Add two :class:`~.resource.Resources` objects assuming the circuits are executed in series. The gates in ``r1`` and ``r2`` are assumed to act on the same qubits. The resulting circuit depth is the sum of the depths of ``r1`` and ``r2``. To add resources as if they were executed in parallel see :func:`~.resource.add_in_parallel`. Args: r1 (Resources): a :class:`~resource.Resources` to add r2 (Resources): a :class:`~resource.Resources` to add Returns: Resources: the combined resources .. details:: **Example** First we build two :class:`~.resource.Resources` objects. .. code-block:: python3 from pennylane.measurements import Shots from pennylane.resource import Resources r1 = Resources( num_wires = 2, num_gates = 2, gate_types = {"Hadamard": 1, "CNOT": 1}, gate_sizes = {1: 1, 2: 1}, depth = 2, shots = Shots(10) ) r2 = Resources( num_wires = 3, num_gates = 2, gate_types = {"RX": 1, "CNOT": 1}, gate_sizes = {1: 1, 2: 1}, depth = 1, shots = Shots((5, (2, 10))) ) Now we print their sum. >>> print(qml.resource.add_in_series(r1, r2)) wires: 3 gates: 4 depth: 3 shots: Shots(total=35, vector=[10 shots, 5 shots, 2 shots x 10]) gate_types: {'Hadamard': 1, 'CNOT': 2, 'RX': 1} gate_sizes: {1: 2, 2: 2} """new_wires=max(r1.num_wires,r2.num_wires)new_gates=r1.num_gates+r2.num_gatesnew_gate_types=_combine_dict(r1.gate_types,r2.gate_types)new_gate_sizes=_combine_dict(r1.gate_sizes,r2.gate_sizes)new_shots=add_shots(r1.shots,r2.shots)new_depth=r1.depth+r2.depthreturnResources(new_wires,new_gates,new_gate_types,new_gate_sizes,new_depth,new_shots)
[docs]defadd_in_parallel(r1:Resources,r2:Resources)->Resources:r""" Add two :class:`~.resource.Resources` objects assuming the circuits are executed in parallel. The gates in ``r2`` and ``r2`` are assumed to act on disjoint sets of qubits. The resulting circuit depth is the max depth of ``r1`` and ``r2``. To add resources as if they were executed in series see :func:`~.resource.add_in_series`. Args: r1 (Resources): a :class:`~.resource.Resources` object to add r2 (Resources): a :class:`~.resource.Resources` object to add Returns: Resources: the combined resources .. details:: **Example** First we build two :class:`~.resource.Resources` objects. .. code-block:: python3 from pennylane.measurements import Shots from pennylane.resource import Resources r1 = Resources( num_wires = 2, num_gates = 2, gate_types = {"Hadamard": 1, "CNOT": 1}, gate_sizes = {1: 1, 2: 1}, depth = 2, shots = Shots(10) ) r2 = Resources( num_wires = 3, num_gates = 2, gate_types = {"RX": 1, "CNOT": 1}, gate_sizes = {1: 1, 2: 1}, depth = 1, shots = Shots((5, (2, 10))) ) Now we print their sum. >>> print(qml.resource.add_in_parallel(r1, r2)) wires: 5 gates: 4 depth: 2 shots: Shots(total=35, vector=[10 shots, 5 shots, 2 shots x 10]) gate_types: {'Hadamard': 1, 'CNOT': 2, 'RX': 1} gate_sizes: {1: 2, 2: 2} """new_wires=r1.num_wires+r2.num_wiresnew_gates=r1.num_gates+r2.num_gatesnew_gate_types=_combine_dict(r1.gate_types,r2.gate_types)new_gate_sizes=_combine_dict(r1.gate_sizes,r2.gate_sizes)new_shots=add_shots(r1.shots,r2.shots)new_depth=max(r1.depth,r2.depth)returnResources(new_wires,new_gates,new_gate_types,new_gate_sizes,new_depth,new_shots)
[docs]defmul_in_series(resources:Resources,scalar:int)->Resources:""" Multiply the :class:`~resource.Resources` object by a scalar as if the circuit was repeated that many times in series. The repeated copies of ``resources`` are assumed to act on the same wires as ``resources``. The resulting circuit depth is the depth of ``resources`` multiplied by ``scalar``. To multiply as if the circuit was repeated in parallel see :func:`~.resource.mul_in_parallel`. Args: resources (Resources): a :class:`~resource.Resources` to be scaled scalar (int): the scalar to multiply the :class:`~resource.Resources` by Returns: Resources: the combined resources .. details:: **Example** First we build a :class:`~.resource.Resources` object. .. code-block:: python3 from pennylane.measurements import Shots from pennylane.resource import Resources resources = Resources( num_wires = 2, num_gates = 2, gate_types = {"Hadamard": 1, "CNOT": 1}, gate_sizes = {1: 1, 2: 1}, depth = 2, shots = Shots(10) ) Now we print the product. >>> print(qml.resource.mul_in_series(resources, 2)) wires: 2 gates: 4 depth: 4 shots: Shots(total=20) gate_types: {'Hadamard': 2, 'CNOT': 2} gate_sizes: {1: 2, 2: 2} """new_wires=resources.num_wiresnew_gates=scalar*resources.num_gatesnew_gate_types=_scale_dict(resources.gate_types,scalar)new_gate_sizes=_scale_dict(resources.gate_sizes,scalar)new_shots=scalar*resources.shotsnew_depth=scalar*resources.depthreturnResources(new_wires,new_gates,new_gate_types,new_gate_sizes,new_depth,new_shots)
[docs]defmul_in_parallel(resources:Resources,scalar:int)->Resources:""" Multiply the :class:`~resource.Resources` object by a scalar as if the circuit was repeated that many times in parallel. The repeated copies of ``resources`` are assumed to act on disjoint qubits. The resulting circuit depth is equal to the depth of ``resources``. To multiply as if the repeated copies were executed in series see :func:`~.resource.mul_in_series`. Args: resources (Resources): a :class:`~resource.Resources` to be scaled scalar (int): the scalar to multiply the :class:`~resource.Resources` by Returns: Resources: The combined resources .. details:: **Example** First we build a :class:`~.resource.Resources` object. .. code-block:: python3 from pennylane.measurements import Shots from pennylane.resource import Resources resources = Resources( num_wires = 2, num_gates = 2, gate_types = {"Hadamard": 1, "CNOT": 1}, gate_sizes = {1: 1, 2: 1}, depth = 2, shots = Shots(10) ) Now we print the product. >>> print(qml.resource.mul_in_parallel(resources, 2)) wires: 4 gates: 4 depth: 2 shots: Shots(total=20) gate_types: {'Hadamard': 2, 'CNOT': 2} gate_sizes: {1: 2, 2: 2} """new_wires=scalar*resources.num_wiresnew_gates=scalar*resources.num_gatesnew_gate_types=_scale_dict(resources.gate_types,scalar)new_gate_sizes=_scale_dict(resources.gate_sizes,scalar)new_shots=scalar*resources.shotsreturnResources(new_wires,new_gates,new_gate_types,new_gate_sizes,resources.depth,new_shots)
[docs]defsubstitute(initial_resources:Resources,gate_info:Tuple[str,int],replacement:Resources):"""Replaces a specified gate in a :class:`~.resource.Resources` object with the contents of another :class:`~.resource.Resources` object. Args: initial_resources (Resources): the :class:`~resource.Resources` object to be modified gate_info (Iterable(str, int)): sequence containing the name of the gate to be replaced and the number of wires it acts on replacement (Resources): the :class:`~resource.Resources` containing the resources that will replace the gate Returns: Resources: the updated :class:`~resource.Resources` after substitution .. details:: **Example** First we build the :class:`~.resource.Resources`. .. code-block:: python3 from pennylane.measurements import Shots from pennylane.resource import Resources initial_resources = Resources( num_wires = 2, num_gates = 3, gate_types = {"RX": 2, "CNOT": 1}, gate_sizes = {1: 2, 2: 1}, depth = 2, shots = Shots(10) ) # the RX gates will be replaced by the substitution gate_info = ("RX", 1) replacement = Resources( num_wires = 1, num_gates = 7, gate_types = {"Hadamard": 3, "S": 4}, gate_sizes = {1: 7}, depth = 7 ) Now we print the result of the substitution. >>> res = qml.resource.substitute(initial_resources, gate_info, replacement) >>> print(res) wires: 2 gates: 15 depth: 9 shots: Shots(total=10) gate_types: {'CNOT': 1, 'H': 6, 'S': 8} gate_sizes: {1: 14, 2: 1} """gate_name,num_wires=gate_infoifnotnum_wiresininitial_resources.gate_sizes:raiseValueError(f"initial_resources does not contain a gate acting on {num_wires} wires.")gate_count=initial_resources.gate_types.get(gate_name,0)ifgate_count>initial_resources.gate_sizes[num_wires]:raiseValueError(f"Found {gate_count} gates of type {gate_name}, but only {initial_resources.gate_sizes[num_wires]} gates act on {num_wires} wires in initial_resources.")ifgate_count>0:new_wires=initial_resources.num_wiresnew_gates=initial_resources.num_gates-gate_count+(gate_count*replacement.num_gates)replacement_gate_types=_scale_dict(replacement.gate_types,gate_count)replacement_gate_sizes=_scale_dict(replacement.gate_sizes,gate_count)new_gate_types=_combine_dict(initial_resources.gate_types,replacement_gate_types)new_gate_types.pop(gate_name)new_gate_sizes=copy.copy(initial_resources.gate_sizes)new_gate_sizes[num_wires]-=gate_countnew_gate_sizes=_combine_dict(new_gate_sizes,replacement_gate_sizes)new_depth=initial_resources.depth+replacement.depthwire_diff=num_wires-replacement.num_wiresifwire_diff<0:new_wires=initial_resources.num_wires+abs(wire_diff)else:new_wires=initial_resources.num_wiresreturnResources(new_wires,new_gates,new_gate_types,new_gate_sizes,new_depth,initial_resources.shots)returninitial_resources
def_combine_dict(dict1:dict,dict2:dict):r"""Combines two dictionaries and adds values of common keys."""combined_dict=copy.copy(dict1)fork,vindict2.items():try:combined_dict[k]+=vexceptKeyError:combined_dict[k]=vreturncombined_dictdef_scale_dict(dict1:dict,scalar:int):r"""Scales the values in a dictionary with a scalar."""combined_dict=copy.copy(dict1)forkincombined_dict:combined_dict[k]*=scalarreturncombined_dictdef_count_resources(tape)->Resources:"""Given a quantum circuit (tape), this function counts the resources used by standard PennyLane operations. Args: tape (.QuantumTape): The quantum circuit for which we count resources Returns: (.Resources): The total resources used in the workflow """num_wires=len(tape.wires)shots=tape.shotsdepth=tape.graph.get_depth()num_gates=0gate_types=defaultdict(int)gate_sizes=defaultdict(int)foropintape.operations:ifisinstance(op,ResourcesOperation):op_resource=op.resources()fordinop_resource.gate_types:gate_types[d]+=op_resource.gate_types[d]forninop_resource.gate_sizes:gate_sizes[n]+=op_resource.gate_sizes[n]num_gates+=sum(op_resource.gate_types.values())else:gate_types[op.name]+=1gate_sizes[len(op.wires)]+=1num_gates+=1returnResources(num_wires,num_gates,gate_types,gate_sizes,depth,shots)