Source code for pennylane.resource.resource
# Copyright 2018-2025 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__ import annotations
import copy
from abc import abstractmethod
from collections import defaultdict
from dataclasses import asdict, dataclass, field, fields
from typing import Any
from pennylane.measurements import MeasurementProcess, Shots, add_shots
from pennylane.operation import Operation
from pennylane.ops.op_math import Controlled, ControlledOp
from pennylane.tape import QuantumScript
from .error.error import _compute_algo_error
[docs]
@dataclass(frozen=True)
class Resources:
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)
num_wires: 2
num_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)
num_wires: 2
num_gates: 4
depth: 4
shots: Shots(total=None)
gate_types:
{'Hadamard': 2, 'CNOT': 2}
gate_sizes:
{1: 2, 2: 2}
"""
num_wires: int = 0
num_gates: int = 0
gate_types: dict[str, int] = field(default_factory=dict)
gate_sizes: dict[int, int] = field(default_factory=dict)
depth: int | None = 0
shots: 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:: python
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)
num_wires: 3
num_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}
"""
return add_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:: python
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)
num_wires: 2
num_gates: 4
depth: 4
shots: Shots(total=20)
gate_types:
{'Hadamard': 2, 'CNOT': 2}
gate_sizes:
{1: 2, 2: 2}
"""
return mul_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) for i in zip(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}" for gate_name, count in self.gate_types.items()]
)
items += "\ngate_types:\n{" + gate_type_str + "}"
gate_size_str = ", ".join(
[f"{n_gate}: {count}" for n_gate, count in self.gate_sizes.items()]
)
items += "\ngate_sizes:\n{" + gate_size_str + "}"
return items
def _ipython_display_(self):
"""Displays __str__ in ipython instead of __repr__"""
# See https://ipython.readthedocs.io/en/stable/config/integrating.html#custom-methods
print(str(self))
# TODO: Would be better to have SpecsResources inherit from Resources directly, but there are too
# many extra fields that are unwanted. Would be worth refactoring in the future.
[docs]
@dataclass(frozen=True)
class SpecsResources:
"""
Class for storing resource information for a quantum circuit. Contains attributes which store
key resources such as gate counts, number of wire allocations, measurements, and circuit depth.
Args:
gate_types (dict[str, int]): A dictionary mapping gate names to their counts.
gate_sizes (dict[int, int]): A dictionary mapping gate sizes to their counts.
measurements (dict[str, int]): A dictionary mapping measurements to their counts.
num_allocs (int): The number of unique wire allocations. For circuits that do not use
dynamic wires, this should be equal to the number of device wires.
depth (int | None): The depth of the circuit, or None if not computed.
Properties:
num_gates (int): The total number of gates in the circuit (computed from `gate_types`).
.. details::
Methods have been provided to allow pretty-printing, as well as
indexing into it as a dictionary. See examples below.
**Example**
>>> from pennylane.resource import SpecsResources
>>> res = SpecsResources(
... gate_types={'Hadamard': 1, 'CNOT': 1},
... gate_sizes={1: 1, 2: 1},
... measurements={'expval(PauliZ)': 1},
... num_allocs=2,
... depth=2
... )
>>> print(res.num_gates)
2
>>> print(res["num_gates"])
2
>>> print(res)
Total wire allocations: 2
Total gates: 2
Circuit depth: 2
<BLANKLINE>
Gate types:
Hadamard: 1
CNOT: 1
<BLANKLINE>
Measurements:
expval(PauliZ): 1
"""
gate_types: dict[str, int]
gate_sizes: dict[int, int]
measurements: dict[str, int]
num_allocs: int
depth: int | None = None
def __post_init__(self):
if sum(self.gate_types.values()) != sum(self.gate_sizes.values()):
raise ValueError(
"Inconsistent gate counts: `gate_types` and `gate_sizes` describe different amounts of gates."
)
[docs]
def to_dict(self) -> dict[str, Any]:
"""Convert the SpecsResources to a dictionary."""
# Need to explicitly include properties
d = asdict(self)
d["num_gates"] = self.num_gates
return d
def __getitem__(self, key):
if key in (field.name for field in fields(self)):
return getattr(self, key)
match key:
# Fields that used to be included in specs output prior to PL version 0.44
case "shots":
raise KeyError(
"shots is no longer included within specs's resources, check the top-level object instead."
)
case "num_wires":
raise KeyError(
"num_wires has been renamed to num_allocs to more accurate describe what it measures."
)
case "num_gates":
# As a property, this needs to be handled differently to the true fields
return self.num_gates
raise KeyError(
f"key '{key}' not available. Options are {[field.name for field in fields(self)]}"
)
@property
def num_gates(self) -> int:
"""Total number of gates in the circuit."""
return sum(self.gate_types.values())
[docs]
def to_pretty_str(self, preindent: int = 0) -> str:
"""
Pretty string representation of the SpecsResources object.
Args:
preindent (int): Number of spaces to prepend to each line.
Returns:
str: A pretty representation of this object.
"""
prefix = " " * preindent
lines = []
lines.append(f"{prefix}Total wire allocations: {self.num_allocs}")
lines.append(f"{prefix}Total gates: {self.num_gates}")
lines.append(
f"{prefix}Circuit depth: {self.depth if self.depth is not None else 'Not computed'}"
)
lines.append("") # Blank line
lines.append(f"{prefix}Gate types:")
if not self.gate_types:
lines.append(prefix + " No gates.")
else:
for gate, count in self.gate_types.items():
lines.append(f"{prefix} {gate}: {count}")
lines.append("") # Blank line
lines.append(f"{prefix}Measurements:")
if not self.measurements:
lines.append(prefix + " No measurements.")
else:
for meas, count in self.measurements.items():
lines.append(f"{prefix} {meas}: {count}")
return "\n".join(lines)
# Leave repr and str methods separate for simple and pretty printing
def __str__(self) -> str:
return self.to_pretty_str()
def _ipython_display_(self): # pragma: no cover
"""Displays __str__ in ipython instead of __repr__"""
# See https://ipython.readthedocs.io/en/stable/config/integrating.html#custom-methods
print(str(self))
[docs]
@dataclass(frozen=True)
class CircuitSpecs:
"""
Class for storing specifications of a qnode. Contains resource information as well as additional
data such as the device, number of shots, and level of the requested specs.
Args:
device_name (str): The name of the device used.
num_device_wires (int): The number of wires on the device.
shots (Shots): The shots configuration used.
level (Any): The level of the specs (see :func:`~pennylane.specs` for more details).
resources (SpecsResources | list[SpecsResources] |
dict[int | str, SpecsResources | list[SpecsResources]]): The resource specifications.
Depending on the ``level`` chosen, this may be a single :class:`.SpecsResources` object,
a list of :class:`.SpecsResources` objects, or a dictionary mapping levels to their
corresponding outputs.
.. details::
Some helpful methods have been added to this data class to allow pretty-printing, as well as
indexing into it as a dictionary. See examples below.
**Example**
>>> from pennylane.resource import SpecsResources, CircuitSpecs
>>> specs = CircuitSpecs(
... device_name="default.qubit",
... num_device_wires=2,
... shots=Shots(1000),
... level="device",
... resources=SpecsResources(
... gate_types={"RX": 2, "CNOT": 1},
... gate_sizes={1: 2, 2: 1},
... measurements={"expval(PauliZ)": 1},
... num_allocs=2,
... depth=3,
... ),
... )
>>> print(specs.num_device_wires)
2
>>> print(specs["num_device_wires"])
2
>>> print(specs)
Device: default.qubit
Device wires: 2
Shots: Shots(total=1000)
Level: device
<BLANKLINE>
Resource specifications:
Total wire allocations: 2
Total gates: 3
Circuit depth: 3
<BLANKLINE>
Gate types:
RX: 2
CNOT: 1
<BLANKLINE>
Measurements:
expval(PauliZ): 1
"""
device_name: str | None = None
num_device_wires: int | None = None
shots: Shots | None = None
level: Any = None
resources: (
SpecsResources
| list[SpecsResources]
| dict[int | str, SpecsResources | list[SpecsResources]]
| None
) = None
[docs]
def to_dict(self) -> dict[str, Any]:
"""Convert the CircuitSpecs to a dictionary."""
d = asdict(self)
# Replace Resources objects with their dict representations
if isinstance(self.resources, SpecsResources):
d["resources"] = self.resources.to_dict()
elif isinstance(self.resources, list):
d["resources"] = [r.to_dict() for r in self.resources]
elif isinstance(self.resources, dict):
d["resources"] = {
k: (v.to_dict() if isinstance(v, SpecsResources) else [r.to_dict() for r in v])
for k, v in self.resources.items()
}
return d
def __getitem__(self, key):
if key in (field.name for field in fields(self)):
return getattr(self, key)
match key:
# Fields that used to be included in specs output prior to PL version 0.44
case "num_observables":
raise KeyError(
"num_observables is no longer in top-level specs and has instead been absorbed into the 'measurements' attribute of the specs's resources."
)
case "interface" | "diff_method" | "errors" | "num_tape_wires":
raise KeyError(f"key '{key}' is no longer included in specs.")
case (
"gradient_fn"
| "gradient_options"
| "num_gradient_executions"
| "num_trainable_params"
):
raise KeyError(
f"key '{key}' is no longer included in specs, as specs no longer gathers gradient information."
)
raise KeyError(
f"key '{key}' not available. Options are {[field.name for field in fields(self)]}"
)
def _resources_to_str(self, res) -> str:
"""Helper for printing resources, prints list or single SpecsResources."""
lines = []
if isinstance(res, SpecsResources):
lines.append(res.to_pretty_str(preindent=2))
elif isinstance(res, list):
for i, r in enumerate(res):
lines.append(f" Batched tape {i}:")
lines.append(r.to_pretty_str(preindent=4))
lines.append("") # Blank line
else:
raise ValueError(
"Resources must be either a SpecsResources object or a list of SpecsResources objects."
) # pragma: no cover
return "\n".join(lines)
# Separate str and repr methods for simple and pretty printing
def __str__(self):
lines = []
lines.append(f"Device: {self.device_name}")
lines.append(f"Device wires: {self.num_device_wires}")
lines.append(f"Shots: {self.shots}")
lines.append(f"Level: {self.level}")
lines.append("") # Blank line
lines.append("Resource specifications:")
if isinstance(self.resources, dict):
for level, res in self.resources.items():
lines.append(f"Level = {level}:")
lines.append(self._resources_to_str(res))
lines.append("\n" + "-" * 60 + "\n") # Separator between levels
else:
lines.append(self._resources_to_str(self.resources))
return "\n".join(lines).rstrip("\n-")
def _ipython_display_(self): # pragma: no cover
"""Displays __str__ in ipython instead of __repr__"""
# See https://ipython.readthedocs.io/en/stable/config/integrating.html#custom-methods
print(str(self))
[docs]
class ResourcesOperation(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]
@abstractmethod
def resources(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]
def add_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:: python
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))
num_wires: 3
num_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_gates
new_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.depth
return Resources(new_wires, new_gates, new_gate_types, new_gate_sizes, new_depth, new_shots)
[docs]
def add_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:: python
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))
num_wires: 5
num_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_wires
new_gates = r1.num_gates + r2.num_gates
new_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)
return Resources(new_wires, new_gates, new_gate_types, new_gate_sizes, new_depth, new_shots)
[docs]
def mul_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:: python
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))
num_wires: 2
num_gates: 4
depth: 4
shots: Shots(total=20)
gate_types:
{'Hadamard': 2, 'CNOT': 2}
gate_sizes:
{1: 2, 2: 2}
"""
new_wires = resources.num_wires
new_gates = scalar * resources.num_gates
new_gate_types = _scale_dict(resources.gate_types, scalar)
new_gate_sizes = _scale_dict(resources.gate_sizes, scalar)
new_shots = scalar * resources.shots
new_depth = scalar * resources.depth
return Resources(new_wires, new_gates, new_gate_types, new_gate_sizes, new_depth, new_shots)
[docs]
def mul_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:: python
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))
num_wires: 4
num_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_wires
new_gates = scalar * resources.num_gates
new_gate_types = _scale_dict(resources.gate_types, scalar)
new_gate_sizes = _scale_dict(resources.gate_sizes, scalar)
new_shots = scalar * resources.shots
return Resources(
new_wires, new_gates, new_gate_types, new_gate_sizes, resources.depth, new_shots
)
[docs]
def substitute(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:: python
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)
num_wires: 2
num_gates: 15
depth: 9
shots: Shots(total=10)
gate_types:
{'CNOT': 1, 'Hadamard': 6, 'S': 8}
gate_sizes:
{1: 14, 2: 1}
"""
gate_name, num_wires = gate_info
if not num_wires in initial_resources.gate_sizes:
raise ValueError(f"initial_resources does not contain a gate acting on {num_wires} wires.")
gate_count = initial_resources.gate_types.get(gate_name, 0)
if gate_count > initial_resources.gate_sizes[num_wires]:
raise ValueError(
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."
)
if gate_count > 0:
new_wires = initial_resources.num_wires
new_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_count
new_gate_sizes = _combine_dict(new_gate_sizes, replacement_gate_sizes)
new_depth = initial_resources.depth + replacement.depth
wire_diff = num_wires - replacement.num_wires
if wire_diff < 0:
new_wires = initial_resources.num_wires + abs(wire_diff)
else:
new_wires = initial_resources.num_wires
return Resources(
new_wires, new_gates, new_gate_types, new_gate_sizes, new_depth, initial_resources.shots
)
return initial_resources
# The reason why this function is not a method of the QuantumScript class is
# because we don't want a core module (QuantumScript) to depend on an auxiliary module (Resource).
# The `QuantumScript.specs` property will eventually be deprecated in favor of this function.
[docs]
def resources_from_tape(
tape: QuantumScript, compute_depth: bool = True, compute_errors: bool = False
) -> SpecsResources | tuple[SpecsResources, dict[str, Any]]:
"""
Extracts the resource information from a quantum circuit (tape).
The depth of the circuit is computed by default, but can be set to None
by setting the `compute_depth` argument to False.
This is useful when the depth is not needed, for example, in some
resource counting scenarios or heavy circuits where computing depth is expensive.
Args:
tape (.QuantumScript): The quantum circuit for which we extract resources
compute_depth (bool): If True, the depth of the circuit is computed and included in the resources.
If False, the depth is set to None.
compute_errors (bool): If True, algorithmic errors are computed and returned alongside the resources.
Defaults to False.
Returns:
(SpecsResources | tuple[SpecsResources, dict[str, Any]]): The resources associated with this tape, optionally
with algorithmic errors if `compute_errors` is set to True.
"""
resources = _count_resources(tape, compute_depth=compute_depth)
if compute_errors:
algo_errors = _compute_algo_error(tape)
return resources, algo_errors
return resources
def _combine_dict(dict1: dict, dict2: dict):
r"""Combines two dictionaries and adds values of common keys."""
combined_dict = copy.copy(dict1)
for k, v in dict2.items():
try:
combined_dict[k] += v
except KeyError:
combined_dict[k] = v
return combined_dict
def _scale_dict(dict1: dict, scalar: int):
r"""Scales the values in a dictionary with a scalar."""
combined_dict = copy.copy(dict1)
for k in combined_dict:
combined_dict[k] *= scalar
return combined_dict
def _obs_to_str(obs) -> str:
"""Convert an Observable to a string representation for resource counting."""
name = obs.name
match name:
case "Hamiltonian" | "LinearCombination" | "Sum" | "Prod":
if name == "LinearCombination":
name = "Hamiltonian"
return f"{name}(num_wires={obs.num_wires}, num_terms={len(obs.operands)})"
case "SProd":
return _obs_to_str(obs.base)
case "Exp":
return f"Exp({_obs_to_str(obs.base)})"
case _:
return name
def _mp_to_str(mp: MeasurementProcess, num_wires: int) -> str:
"""Convert a MeasurementProcess to a string representation for resource counting."""
meas_name = mp._shortname # pylint: disable=protected-access
if mp.mv is not None:
meas_name += "(mcm)"
elif mp.obs is None:
meas_wires = len(mp.wires)
if meas_wires in (None, 0, num_wires):
meas_name += "(all wires)"
else:
meas_name += f"({meas_wires} wires)"
else:
meas_name += f"({_obs_to_str(mp.obs)})"
return meas_name
def _count_resources(tape: QuantumScript, compute_depth: bool = True) -> SpecsResources:
"""Given a quantum tape, this function counts the resources used by standard PennyLane operations.
Args:
tape (.QuantumScript): The quantum circuit for which we count resources
compute_depth (bool): If True, the depth of the circuit is computed and included in the resources.
If False, the depth is set to None.
Returns:
(.SpecsResources): The total resources used in the workflow
"""
num_wires = len(tape.wires)
depth = tape.graph.get_depth() if compute_depth else None
gate_types = defaultdict(int)
measurements = defaultdict(int)
gate_sizes = defaultdict(int)
for op in tape.operations:
if isinstance(op, ResourcesOperation):
op_resource = op.resources()
for d in op_resource.gate_types:
gate_types[d] += op_resource.gate_types[d]
for n in op_resource.gate_sizes:
gate_sizes[n] += op_resource.gate_sizes[n]
else:
gate_name = op.name
# pylint: disable=unidiomatic-typecheck
if type(op) in (Controlled, ControlledOp):
n_ctrls = len(op.control_wires)
if n_ctrls > 1:
gate_name = f"{n_ctrls}{gate_name}"
gate_types[gate_name] += 1
gate_sizes[len(op.wires)] += 1
for meas in tape.measurements:
measurements[_mp_to_str(meas, num_wires)] += 1
return SpecsResources(
gate_types=dict(gate_types),
gate_sizes=dict(gate_sizes),
measurements=dict(measurements),
num_allocs=num_wires,
depth=depth,
)
_modules/pennylane/resource/resource
Download Python script
Download Notebook
View on GitHub