-
Notifications
You must be signed in to change notification settings - Fork 604
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add and Multiply operations for Resource objects #6567
base: master
Are you sure you want to change the base?
Changes from 19 commits
3eef294
e81ec0c
745c82b
3f709c6
24e284c
9707c8a
7198ca2
6b67f20
076d533
e418ded
fd07117
5e7c54a
b8f3a95
0018dd1
82403d4
cba2f91
f2a9a09
429f665
fbc037e
ea02350
d9c6bd2
c144557
3d644c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,8 +14,12 @@ | |
""" | ||
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 collections.abc import Sequence | ||
from dataclasses import dataclass, field | ||
|
||
from pennylane.measurements import Shots | ||
|
@@ -63,6 +67,60 @@ | |
depth: int = 0 | ||
shots: Shots = field(default_factory=Shots) | ||
|
||
def __add__(self, other: Resources): | ||
r"""Adds two 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** | ||
|
||
>>> r1 = Resources(num_wires=2, num_gates=2, gate_types={'Hadamard': 1, 'CNOT':1}, gate_sizes={1: 1, 2: 1}) | ||
>>> r2 = Resources(num_wires=2, num_gates=2, gate_types={'RX': 1, 'CNOT':1}, gate_sizes={1: 1, 2: 1}) | ||
>>> print(r1 + r2) | ||
wires: 2 | ||
gates: 4 | ||
depth: 0 | ||
shots: Shots(total=None) | ||
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 Resource 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** | ||
>>> r1 = Resources(num_wires=2, num_gates=2, gate_types={'Hadamard': 1, 'CNOT':1}, gate_sizes={1: 1, 2: 1}) | ||
>>> print(r1 * 2) | ||
wires: 2 | ||
gates: 4 | ||
depth: 0 | ||
shots: Shots(total=None) | ||
gate_types: | ||
{'Hadamard': 2, 'CNOT': 2} | ||
gate_sizes: | ||
{1: 2, 2: 2} | ||
""" | ||
return mul_in_series(self, scalar) | ||
|
||
__rmul__ = __mul__ | ||
|
||
def __str__(self): | ||
keys = ["wires", "gates", "depth"] | ||
vals = [self.num_wires, self.num_gates, self.depth] | ||
|
@@ -125,6 +183,177 @@ | |
""" | ||
|
||
|
||
def add_in_series(r1: Resources, r2: Resources) -> Resources: | ||
r"""Add two resources assuming the circuits are executed in series. | ||
|
||
Args: | ||
r1 (Resources): A Resources object to add. | ||
r2 (Resources): A Resources object to add. | ||
|
||
Returns: | ||
Resources: The combined resources of r1 and r2. | ||
|
||
.. details:: | ||
|
||
**Example** | ||
|
||
>>> r1 = Resources(num_wires=2, num_gates=2, gate_types={'Hadamard': 1, 'CNOT':1}, gate_sizes={1: 1, 2: 1}) | ||
>>> r2 = Resources(num_wires=2, num_gates=2, gate_types={'RX': 1, 'CNOT':1}, gate_sizes={1: 1, 2: 1}) | ||
>>> print(r1 + r2) | ||
wires: 2 | ||
gates: 4 | ||
depth: 0 | ||
shots: Shots(total=None) | ||
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) | ||
|
||
|
||
def add_in_parallel(r1: Resources, r2: Resources) -> Resources: | ||
r"""Add two resources assuming the circuits are executed in parallel. | ||
|
||
Args: | ||
r1 (Resources): A Resources object to add. | ||
r2 (Resources): A Resources object to add. | ||
|
||
Returns: | ||
Resources: The combined resources of r1 and r2. | ||
|
||
.. details:: | ||
|
||
**Example** | ||
|
||
>>> r1 = Resources(num_wires=2, num_gates=2, gate_types={'Hadamard': 1, 'CNOT':1}, gate_sizes={1: 1, 2: 1}) | ||
>>> r2 = Resources(num_wires=2, num_gates=2, gate_types={'RX': 1, 'CNOT':1}, gate_sizes={1: 1, 2: 1}) | ||
>>> print(add_in_parallel(r1, r2)) | ||
wires: 4 | ||
gates: 4 | ||
depth: 0 | ||
shots: Shots(total=None) | ||
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) | ||
|
||
|
||
def mul_in_series(r1: Resources, scalar: int) -> Resources: | ||
"""Multiply the Resources object by a scalar as if the circuit was repeated | ||
that many times in series. | ||
|
||
Args: | ||
r1 (Resources): A Resources object to be scaled. | ||
scalar (int): The scalar to multiply the Resources object by. | ||
|
||
Returns: | ||
Resources: The combined resources | ||
|
||
.. details:: | ||
|
||
**Example** | ||
|
||
>>> r1 = Resources(num_wires=2, num_gates=2, gate_types={'Hadamard': 1, 'CNOT':1}, gate_sizes={1: 1, 2: 1}) | ||
>>> print(r1 * 2) | ||
wires: 2 | ||
gates: 4 | ||
depth: 0 | ||
shots: Shots(total=None) | ||
gate_types: | ||
{'Hadamard': 2, 'CNOT': 2} | ||
gate_sizes: | ||
{1: 2, 2: 2} | ||
""" | ||
|
||
new_wires = r1.num_wires | ||
new_gates = scalar * r1.num_gates | ||
new_gate_types = _scale_dict(r1.gate_types, scalar) | ||
new_gate_sizes = _scale_dict(r1.gate_sizes, scalar) | ||
new_shots = scalar * r1.shots | ||
new_depth = scalar * r1.depth | ||
|
||
return Resources(new_wires, new_gates, new_gate_types, new_gate_sizes, new_depth, new_shots) | ||
|
||
|
||
def mul_in_parallel(r1: Resources, scalar: int) -> Resources: | ||
"""Multiply the Resources object by a scalar as if the circuit was repeated | ||
that many times in parallel. | ||
|
||
Args: | ||
r1 (Resources): A Resources object to be scaled. | ||
scalar (int): The scalar to multiply the Resources object by. | ||
|
||
Returns: | ||
Resources: The combined resources | ||
|
||
.. details:: | ||
|
||
**Example** | ||
|
||
>>> r1 = Resources(num_wires=2, num_gates=2, gate_types={'Hadamard': 1, 'CNOT':1}, gate_sizes={1: 1, 2: 1}) | ||
>>> print(mul_in_parallel(r1, 2)) | ||
wires: 4 | ||
gates: 4 | ||
depth: 0 | ||
shots: Shots(total=None) | ||
gate_types: | ||
{'Hadamard': 2, 'CNOT': 2} | ||
gate_sizes: | ||
{1: 2, 2: 2} | ||
""" | ||
|
||
new_wires = scalar * r1.num_wires | ||
new_gates = scalar * r1.num_gates | ||
new_gate_types = _scale_dict(r1.gate_types, scalar) | ||
new_gate_sizes = _scale_dict(r1.gate_sizes, scalar) | ||
new_shots = scalar * r1.shots | ||
|
||
return Resources(new_wires, new_gates, new_gate_types, new_gate_sizes, r1.depth, new_shots) | ||
|
||
|
||
def _combine_dict(dict1: dict, dict2: dict): | ||
r"""Private function which combines two dictionaries together.""" | ||
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"""Private function which scales the values in a dictionary.""" | ||
|
||
combined_dict = copy.copy(dict1) | ||
|
||
for k in combined_dict: | ||
combined_dict[k] *= scalar | ||
|
||
return combined_dict | ||
|
||
|
||
def _count_resources(tape) -> Resources: | ||
"""Given a quantum circuit (tape), this function | ||
counts the resources used by standard PennyLane operations. | ||
|
@@ -159,3 +388,17 @@ | |
num_gates += 1 | ||
|
||
return Resources(num_wires, num_gates, gate_types, gate_sizes, depth, shots) | ||
|
||
|
||
def _add_shots(s1: Shots, s2: Shots) -> Shots: | ||
if s1.total_shots is None: | ||
return s2 | ||
|
||
if s2.total_shots is None: | ||
return s1 | ||
|
||
shot_vector = [] | ||
for shot in s1.shot_vector + s2.shot_vector: | ||
shot_vector.append((shot.shots, shot.copies)) | ||
|
||
return Shots(shots=shot_vector) | ||
Comment on lines
+392
to
+403
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks good, I just wonder if it's ideal to have this defined here, or better in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking the same thing, but I didn't want to add an unrelated file to the PR. I'll ask Soran. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current location looks good to me. @Jaybsoni please also check. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is needed for the type hints to work. Is there a problem with this import?