Skip to content

Commit 9ea5986

Browse files
austingmhuangddhawan11astralcaialbi3roandrijapau
authored
Add resource config class to Pennylane core (#8259)
**Context:** We are moving the resource config class from labs to pennylane. **Description of the Change:** Add resource_config.py and test_resource_config.py to pennylane.estimator. Since we do not have the templates, we do not add them and their related tests. **Benefits:** **Possible Drawbacks:** **Related GitHub Issues:** [sc-96876] --------- Co-authored-by: Diksha Dhawan <[email protected]> Co-authored-by: Diksha Dhawan <[email protected]> Co-authored-by: Astral Cai <[email protected]> Co-authored-by: Christina Lee <[email protected]> Co-authored-by: Andrija Paurevic <[email protected]> Co-authored-by: Soran Jahangiri <[email protected]> Co-authored-by: ANT0N <[email protected]> Co-authored-by: obliviateandsurrender <[email protected]>
1 parent 104b5e8 commit 9ea5986

File tree

4 files changed

+410
-0
lines changed

4 files changed

+410
-0
lines changed

doc/releases/changelog-dev.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
[(#8205)](https://github.com/PennyLaneAI/pennylane/pull/8205)
2222
* The test files were renamed to avoid the dual definition error with labs module.
2323
[(#8261)](https://github.com/PennyLaneAI/pennylane/pull/8261)
24+
* Added a new `ResourceConfig` class to store the configuration used for resource estimation, including operator precisions and custom decompositions.
25+
[(#8259)](https://github.com/PennyLaneAI/pennylane/pull/8259)
2426

2527
* Dynamic wire allocation with `qml.allocation.allocate` can now be executed on `default.qubit`.
2628
[(#7718)](https://github.com/PennyLaneAI/pennylane/pull/7718)

pennylane/estimator/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
:toctree: api
3434
3535
~Resources
36+
~ResourceConfig
3637
~ResourceOperator
3738
~CompressedResourceOp
3839
~GateCount
@@ -44,6 +45,8 @@
4445

4546
from .resources_base import Resources
4647

48+
from .resource_config import ResourceConfig
49+
4750
from .resource_operator import (
4851
ResourceOperator,
4952
CompressedResourceOp,
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
# Copyright 2025 Xanadu Quantum Technologies Inc.
2+
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
r"""This module contains the ResourceConfig class, which tracks the configuration for resource estimation"""
15+
16+
from __future__ import annotations
17+
18+
from collections.abc import Callable
19+
from enum import StrEnum
20+
from typing import TYPE_CHECKING
21+
22+
if TYPE_CHECKING:
23+
from pennylane.estimator.resource_operator import ResourceOperator
24+
25+
26+
class DecompositionType(StrEnum):
27+
"""Specifies the type of decomposition to override."""
28+
29+
ADJOINT = "adj"
30+
CONTROLLED = "ctrl"
31+
POW = "pow"
32+
BASE = "base"
33+
34+
35+
class ResourceConfig:
36+
"""A container to track the configuration for precisions and custom decompositions for the
37+
resource estimation pipeline.
38+
"""
39+
40+
def __init__(self) -> None:
41+
_DEFAULT_PRECISION = 1e-9
42+
_DEFAULT_BIT_PRECISION = 15
43+
self.resource_op_precisions = {}
44+
self._custom_decomps = {}
45+
self._adj_custom_decomps = {}
46+
self._ctrl_custom_decomps = {}
47+
self._pow_custom_decomps = {}
48+
49+
def __str__(self) -> str:
50+
decomps = [op.__name__ for op in self._custom_decomps]
51+
adj_decomps = [f"Adjoint({op.__name__})" for op in self._adj_custom_decomps]
52+
ctrl_decomps = [f"Controlled({op.__name__})" for op in self._ctrl_custom_decomps]
53+
pow_decomps = [f"Pow({op.__name__})" for op in self._pow_custom_decomps]
54+
55+
all_op_strings = decomps + adj_decomps + ctrl_decomps + pow_decomps
56+
op_names = ", ".join(all_op_strings)
57+
58+
dict_items_str = ",\n".join(
59+
f" {key.__name__}: {value!r}" for key, value in self.resource_op_precisions.items()
60+
)
61+
62+
formatted_dict = f"{{\n{dict_items_str}\n}}"
63+
64+
return (
65+
f"ResourceConfig(\n"
66+
f" precisions = {formatted_dict},\n"
67+
f" custom decomps = [{op_names}]\n)"
68+
)
69+
70+
def __repr__(self) -> str:
71+
return f"ResourceConfig(precisions = {self.resource_op_precisions}, custom_decomps = {self._custom_decomps}, adj_custom_decomps = {self._adj_custom_decomps}, ctrl_custom_decomps = {self._ctrl_custom_decomps}, pow_custom_decomps = {self._pow_custom_decomps})"
72+
73+
def set_precision(self, op_type: type[ResourceOperator], precision: float) -> None:
74+
r"""Sets the precision for a given resource operator.
75+
76+
This method updates the precision value for operators that use a single
77+
tolerance parameter (e.g., for synthesis error). It will raise an error
78+
if you attempt to set the precision for an operator that is not
79+
configurable or uses bit-precisions. A negative precision will also raise an error.
80+
81+
Args:
82+
op_type (type[ResourceOperator]): the operator class for which
83+
to set the precision
84+
precision (float): The desired precision tolerance. A smaller
85+
value corresponds to a higher precision compilation, which may
86+
increase the required gate counts. Must be greater than 0.
87+
88+
Raises:
89+
ValueError: If ``op_type`` is not a configurable operator or if setting
90+
the precision for it is not supported, or if ``precision`` is negative.
91+
92+
**Example**
93+
94+
.. code-block:: python
95+
96+
import pennylane.estimator as qre
97+
98+
config = qre.ResourceConfig()
99+
100+
# Check the default precision
101+
default = config.resource_op_precisions[qre.SelectPauliRot]['precision']
102+
print(f"Default precision for SelectPauliRot: {default}")
103+
104+
# Set a new precision
105+
config.set_precision(qre.SelectPauliRot, precision=1e-5)
106+
new = config.resource_op_precisions[qre.SelectPauliRot]['precision']
107+
print(f"New precision for SelectPauliRot: {new}")
108+
109+
.. code-block:: pycon
110+
111+
Default precision for SelectPauliRot: 1e-09
112+
New precision for SelectPauliRot: 1e-05
113+
"""
114+
if precision < 0:
115+
raise ValueError(f"Precision must be a non-negative value, but got {precision}.")
116+
117+
if op_type not in self.resource_op_precisions:
118+
configurable_ops = sorted(
119+
[
120+
op.__name__
121+
for op, params in self.resource_op_precisions.items()
122+
if "precision" in params
123+
]
124+
)
125+
raise ValueError(
126+
f"{op_type.__name__} is not a configurable operator. "
127+
f"Configurable operators are: {', '.join(configurable_ops)}"
128+
)
129+
130+
if "precision" not in self.resource_op_precisions[op_type]:
131+
raise ValueError(f"Setting precision for {op_type.__name__} is not supported.")
132+
133+
self.resource_op_precisions[op_type]["precision"] = precision
134+
135+
def set_single_qubit_rot_precision(self, precision: float) -> None:
136+
r"""Sets the synthesis precision for all single-qubit rotation gates.
137+
138+
This is a convenience method to update the synthesis precision tolerance
139+
for all standard single-qubit rotation gates (and their
140+
controlled versions) at once. The synthesis precision dictates the precision
141+
for compiling rotation gates into a discrete gate set, which in turn
142+
affects the number of gates required.
143+
144+
This method updates the ``precision`` value for the following operators:
145+
:class:`~.pennylane.estimator.RX`, :class:`~.pennylane.estimator.RY`,
146+
:class:`~.pennylane.estimator.RZ`, :class:`~.pennylane.estimator.CRX`,
147+
:class:`~.pennylane.estimator.CRY`, :class:`~.pennylane.estimator.CRZ`.
148+
149+
Args:
150+
precision (float): The desired synthesis precision tolerance. A smaller
151+
value corresponds to a higher precision compilation, which may
152+
increase the required gate counts. Must be greater than ``0``.
153+
154+
Raises:
155+
ValueError: If ``precision`` is a negative value.
156+
157+
**Example**
158+
159+
.. code-block:: python
160+
161+
import pennylane.estimator as qre
162+
163+
config = qre.ResourceConfig()
164+
print(f"Default RX precision: {config.resource_op_precisions[qre.RX]['precision']}")
165+
print(f"Default RY precision: {config.resource_op_precisions[qre.RY]['precision']}")
166+
print(f"Default RZ precision: {config.resource_op_precisions[qre.RZ]['precision']}")
167+
168+
config.set_single_qubit_rot_precision(1e-5)
169+
print(f"Updated RX precision: {config.resource_op_precisions[qre.RX]['precision']}")
170+
print(f"Updated RY precision: {config.resource_op_precisions[qre.RY]['precision']}")
171+
print(f"Updated RZ precision: {config.resource_op_precisions[qre.RZ]['precision']}")
172+
173+
.. code-block:: pycon
174+
175+
Default RX precision: 1e-09
176+
Default RY precision: 1e-09
177+
Default RZ precision: 1e-09
178+
Updated RX precision: 1e-05
179+
Updated RY precision: 1e-05
180+
Updated RZ precision: 1e-05
181+
"""
182+
if precision < 0:
183+
raise ValueError(f"Precision must be a non-negative value, but got {precision}.")
184+
185+
def set_decomp(
186+
self,
187+
op_type: type[ResourceOperator],
188+
decomp_func: Callable,
189+
decomp_type: DecompositionType | None = DecompositionType.BASE,
190+
) -> None:
191+
"""Sets a custom function to override the default resource decomposition.
192+
193+
Args:
194+
op_type (type[ResourceOperator]): the operator class whose decomposition is being overriden.
195+
decomp_func (Callable): the new resource decomposition function to be set as default.
196+
decomp_type (None | DecompositionType): the decomposition type to override. Options are
197+
``"adj"``, ``"pow"``, ``"ctrl"``,
198+
and ``"base"``. Default is ``"base"``.
199+
200+
Raises:
201+
ValueError: If ``decomp_type`` is not a valid decomposition type.
202+
203+
.. note::
204+
205+
The new decomposition function ``decomp_func`` should have the same signature as the one it replaces.
206+
Specifically, the signature should match the :code:`resource_keys` of the base resource
207+
operator class being overriden.
208+
209+
**Example**
210+
211+
.. code-block:: python
212+
213+
import pennylane.estimator as qre
214+
215+
def custom_res_decomp(**kwargs):
216+
h = qre.resource_rep(qre.Hadamard)
217+
s = qre.resource_rep(qre.S)
218+
return [qre.GateCount(h, 1), qre.GateCount(s, 2)]
219+
220+
.. code-block:: pycon
221+
222+
>>> print(qre.estimate_resources(qre.X(), gate_set={"Hadamard", "Z", "S"}))
223+
--- Resources: ---
224+
Total qubits: 1
225+
Total gates : 4
226+
Qubit breakdown:
227+
clean qubits: 0, dirty qubits: 0, algorithmic qubits: 1
228+
Gate breakdown:
229+
{'Hadamard': 2, 'S': 2}
230+
>>> config = qre.ResourceConfig()
231+
>>> config.set_decomp(qre.X, custom_res_decomp)
232+
>>> print(qre.estimate_resources(qre.X(), gate_set={"Hadamard", "Z", "S"}, config=config))
233+
--- Resources: ---
234+
Total qubits: 1
235+
Total gates : 3
236+
Qubit breakdown:
237+
clean qubits: 0, dirty qubits: 0, algorithmic qubits: 1
238+
Gate breakdown:
239+
{'S': 1, 'Hadamard': 2}
240+
"""
241+
if decomp_type is None:
242+
decomp_type = DecompositionType("base")
243+
else:
244+
decomp_type = DecompositionType(decomp_type)
245+
246+
if decomp_type == DecompositionType.ADJOINT:
247+
self._adj_custom_decomps[op_type] = decomp_func
248+
elif decomp_type == DecompositionType.CONTROLLED:
249+
self._ctrl_custom_decomps[op_type] = decomp_func
250+
elif decomp_type == DecompositionType.POW:
251+
self._pow_custom_decomps[op_type] = decomp_func
252+
elif decomp_type is None or decomp_type == DecompositionType.BASE:
253+
self._custom_decomps[op_type] = decomp_func

0 commit comments

Comments
 (0)