Skip to content
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

[WIP] Add resource methods to some templates (TrotterProduct, Exp, StatePrep, QPE) #6677

Open
wants to merge 7 commits into
base: resource_symbolic_decomps
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions pennylane/labs/resource_estimation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,15 @@
.. autosummary::
:toctree: api

~ResourceBasisRotation
~ResourcePrepSelPrep
~ResourceQFT
~ResourceQPE
~ResourceQubitization
~ResourceReflection
~ResourceSelect
~ResourceStatePrep
~ResourceTrotterProduct

Tracking Resources
~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -161,6 +169,7 @@
ResourceDoubleExcitation,
ResourceDoubleExcitationMinus,
ResourceDoubleExcitationPlus,
ResourceExp,
ResourceFermionicSWAP,
ResourceGlobalPhase,
ResourceHadamard,
Expand Down Expand Up @@ -193,5 +202,15 @@
)

from .templates import (
ResourceBasisRotation,
ResourcePrepSelPrep,
ResourceQFT,
ResourceQPE,
ResourceQuantumPhaseEstimation,
ResourceQubitization,
ResourceReflection,
ResourceSelect,
ResourceStatePrep,
ResourceTrotterProduct,
resource_trotterize,
)
1 change: 1 addition & 0 deletions pennylane/labs/resource_estimation/ops/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
ResourceCRX,
ResourceCRY,
ResourceCRZ,
ResourceExp,
ResourceToffoli,
ResourceMultiControlledX,
ResourceCNOT,
Expand Down
144 changes: 144 additions & 0 deletions pennylane/labs/resource_estimation/ops/op_math/symbolic.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@
from collections import defaultdict
from typing import Dict

import pennylane as qml
import pennylane.labs.resource_estimation as re
from pennylane import math
from pennylane.labs.resource_estimation.resource_container import _combine_dict, _scale_dict
from pennylane.ops.op_math.adjoint import AdjointOperation
from pennylane.ops.op_math.controlled import ControlledOp
from pennylane.ops.op_math.exp import Exp
from pennylane.ops.op_math.pow import PowOperation
from pennylane.ops.op_math.sprod import SProd

# pylint: disable=too-many-ancestors,arguments-differ,protected-access,too-many-arguments,too-many-positional-arguments

Expand Down Expand Up @@ -189,3 +194,142 @@ def pow_resource_decomp(
def tracking_name(base_class, z, base_params) -> str:
base_name = base_class.tracking_name(**base_params)
return f"Pow({base_name}, {z})"


class ResourceExp(Exp, re.ResourceOperator):
"""Resource class for Exp"""

@staticmethod
def _resource_decomp(base_class, coeff, num_steps, **kwargs):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like base_class can be of multiple types. A type annotation like base_class: Union[....] would be helpful.


while isinstance(base_class, SProd):
coeff *= base_class.scalar
base_class = base_class.base
Comment on lines +205 to +207
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be handled in ResourceSProd.exp_resource_decomp. Since that isn't implemented yet, it's fine to leave this for now.

Also isinstance should be changed to issubclass. See my comments below about base_class.


# Custom exponential operator resources:
if isinstance(base_class, re.ResourceOperator):
try:
return base_class.exp_resource_decomp(coeff, num_steps, **kwargs)
except re.ResourcesNotDefined:
pass
Comment on lines +209 to +214
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the other symbolic classes we assume base_class is a type that inherits from ResourceOperator, so this check shouldn't be necessary unless there is some kind of edge case I am missing. Also, base_class should be a type, so this conditional should be checking issubclass rather than isinstance.


# PauliRot resource decomp:
if (pauli_sentence := base_class.pauli_rep) and math.real(coeff) == 0:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

base_class should be a type, so either there needs to be a pauli_rep attribute in resource_params or pauli_rep needs to be provided in base_params (see my earlier comment about resource_rep).

if qml.pauli.is_pauli_word(base_class):
num_wires = len(base_class.wires)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, num_wires should be provided in the minimal set of information needed to compute a resource estimate.

pauli_word = tuple(pauli_sentence.keys())[0] # only one term in the sum
return _resources_from_pauli_word(pauli_word, num_wires)

scalar = num_steps or 1 # 1st-order Trotter-Suzuki with 'num_steps' trotter steps:
return _scale_dict(
_resources_from_pauli_sentence(pauli_sentence), scalar=scalar, in_place=True
)

raise re.ResourcesNotDefined

def resource_params(self):
return {
"base_class": self.base,
"coeff": self.scalar,
"num_steps": self.num_steps,
}
Comment on lines +230 to +235
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two comments here.

  1. The base class should be "base_class": type(self.base). We don't want to pass around the full operator.
  2. The dictionary should contain "base_params": self.base.resource_params() so we have the minimal set of information needed for resources estimation.


@classmethod
def resource_rep(cls, base_class, coeff, num_steps, **kwargs):
name = f"Exp({base_class.__class__.__name__}, {coeff}, num_steps={num_steps})".replace(
Comment on lines +238 to +239
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets use the tracking name here. This change will require base_params like I commented earlier. It should look something like this.

base_name = base_class.tracking_name(**base_params)
name = f"Exp({base_name}, ..."

"Resource", ""
)
return re.CompressedResourceOp(
cls, {"base_class": base_class, "coeff": coeff, "num_steps": num_steps}, name=name
)

@classmethod
def pow_resource_decomp(
cls, z0, base_class, coeff, num_steps
) -> Dict[re.CompressedResourceOp, int]:
return {cls.resource_rep(base_class, z0 * coeff, num_steps): 1}

@classmethod
def controlled_resource_decomp(
cls,
num_ctrl_wires,
num_ctrl_values,
num_work_wires,
base_class,
coeff,
num_steps,
) -> Dict[re.CompressedResourceOp, int]:
"""The controlled exponential decomposition of a Pauli hamiltonian is symmetric, thus we only need to control
on the RZ gate in the middle."""
if (p_rep := base_class.pauli_rep) and math.real(coeff) == 0:

if qml.pauli.is_pauli_word(base_class) and len(p_rep) > 1:
base_gate_types = cls.resources(base_class, coeff, num_steps)

rz_counts = base_gate_types.pop(re.ResourceRZ.resource_rep())
ctrl_rz = re.ResourceControlled.resource_rep(
re.ResourceRZ, {}, num_ctrl_wires, num_ctrl_values, num_work_wires
)

base_gate_types[ctrl_rz] = rz_counts
return base_gate_types

raise re.ResourcesNotDefined


def _resources_from_pauli_word(pauli_word, num_wires):
pauli_string = "".join((str(v) for v in pauli_word.values()))
len_str = len(pauli_string)

if len_str == 0:
return {} # Identity operation has no resources.
Comment on lines +284 to +285
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if len_str == 0:
return {} # Identity operation has no resources.
if len_str == 0:
return {re.ResourceIdentity.resource_rep(): 1}

For more complete tracking.


if len_str == 1:
if pauli_string == "X":
return {re.CompressedResourceOp(re.ResourceRX, {}): 1}
if pauli_string == "Y":
return {re.CompressedResourceOp(re.ResourceRY, {}): 1}
if pauli_string == "Z":
return {re.CompressedResourceOp(re.ResourceRZ, {}): 1}

counter = {"X": 0, "Y": 0, "Z": 0}
for c in pauli_string:
counter[c] += 1

num_x = counter["X"]
num_y = counter["Y"]

s = re.CompressedResourceOp(re.ResourceS, {})
h = re.CompressedResourceOp(re.ResourceHadamard, {})
rz = re.CompressedResourceOp(re.ResourceRZ, {})
cnot = re.CompressedResourceOp(re.ResourceCNOT, {})
Comment on lines +287 to +305
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change the calls to re.CompressedResourceOp to re.ResourceWhatever.resource_rep().


gate_types = {}
gate_types[rz] = 1
gate_types[s] = 2 * num_y
gate_types[h] = 2 * (num_x + num_y)
gate_types[cnot] = 2 * (num_wires - 1)

return gate_types


def _resources_from_pauli_sentence(pauli_sentence):
gate_types = defaultdict(int)
rx = re.CompressedResourceOp(re.ResourceRX, {})
ry = re.CompressedResourceOp(re.ResourceRY, {})
rz = re.CompressedResourceOp(re.ResourceRZ, {})
Comment on lines +316 to +320
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. Let's use resource_rep to get the compressed representation.


for pauli_word in iter(pauli_sentence.keys()):
num_wires = len(pauli_word.wires)

if num_wires == 1:
pauli_string = "".join((str(v) for v in pauli_word.values()))
op_type = {"Z": rz, "X": rx, "Y": ry}[pauli_string]
gate_types[op_type] += 1

continue

pw_gates = _resources_from_pauli_word(pauli_word, num_wires)
_ = _combine_dict(gate_types, pw_gates, in_place=True)

return gate_types
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,12 @@ def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]:


class ResourceRX(qml.RX, re.ResourceOperator):
"""Resource class for the RX gate."""
"""Resource class for the RX gate.

Resources:
The resources are estimated by approximating the gate with a series of T gates.
The estimate is taken from https://arxiv.org/abs/1404.5320.
"""

@staticmethod
def _resource_decomp(config, **kwargs) -> Dict[re.CompressedResourceOp, int]:
Expand Down Expand Up @@ -159,7 +164,12 @@ def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]:


class ResourceRY(qml.RY, re.ResourceOperator):
"""Resource class for the RY gate."""
"""Resource class for the RY gate.

Resources:
The resources are estimated by approximating the gate with a series of T gates.
The estimate is taken from https://arxiv.org/abs/1404.5320.
"""

@staticmethod
def _resource_decomp(config, **kwargs) -> Dict[re.CompressedResourceOp, int]:
Expand Down
7 changes: 7 additions & 0 deletions pennylane/labs/resource_estimation/resource_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,13 @@ def pow_resource_decomp(cls, z, *args, **kwargs) -> Dict[CompressedResourceOp, i
"""Returns a compressed representation of the operator raised to a power"""
raise ResourcesNotDefined

@classmethod
def exp_resource_decomp(
cls, scalar, num_steps, *args, **kwargs
) -> Dict[CompressedResourceOp, int]:
"""Returns a compressed representation for the resources of the exponentiated operator"""
raise ResourcesNotDefined
Comment on lines +126 to +131
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this be defined?


@classmethod
def tracking_name(cls, *args, **kwargs) -> str:
"""Returns a name used to track the operator during resource estimation."""
Expand Down
6 changes: 3 additions & 3 deletions pennylane/labs/resource_estimation/resource_tracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@

# user-friendly gateset for visual checks and initial compilation
_StandardGateSet = {
"PauliX",
"PauliY",
"PauliZ",
"X",
"Y",
"Z",
"Hadamard",
"SWAP",
"CNOT",
Expand Down
13 changes: 12 additions & 1 deletion pennylane/labs/resource_estimation/templates/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
r"""This module contains resource operators for PennyLane templates. """
from .subroutines import ResourceQFT
from .subroutines import (
ResourceQFT,
ResourceQuantumPhaseEstimation,
ResourceStatePrep,
ResourceQPE,
ResourceBasisRotation,
ResourcePrepSelPrep,
ResourceQubitization,
ResourceReflection,
ResourceSelect,
)
from .trotter import ResourceTrotterProduct, resource_trotterize
Loading
Loading