Source code for pennylane.labs.resource_estimation.resource_config
# Copyright 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.
r"""This module contains the ResourceConfig class, which tracks the configuration for resource estimation"""
from __future__ import annotations
from collections.abc import Callable
from enum import StrEnum
from .ops.op_math.controlled_ops import (
ResourceCRX,
ResourceCRY,
ResourceCRZ,
)
from .ops.qubit.parametric_ops_single_qubit import (
ResourceRX,
ResourceRY,
ResourceRZ,
)
from .resource_operator import ResourceOperator
from .templates import (
ResourceAliasSampling,
ResourceMPSPrep,
ResourcePrepTHC,
ResourceQROMStatePreparation,
ResourceQubitizeTHC,
ResourceQubitUnitary,
ResourceSelectPauliRot,
ResourceSelectTHC,
)
class DecompositionType(StrEnum):
"""Specifies the type of decomposition to override."""
ADJOINT = "adj"
CONTROLLED = "ctrl"
POW = "pow"
BASE = "base"
[docs]
class ResourceConfig:
"""A container to track the configuration for precisions and custom decompositions for the
resource estimation pipeline.
"""
def __init__(self) -> None:
_DEFAULT_PRECISION = 1e-9
_DEFAULT_BIT_PRECISION = 15
self.resource_op_precisions = {
ResourceRX: {"precision": _DEFAULT_PRECISION},
ResourceRY: {"precision": _DEFAULT_PRECISION},
ResourceRZ: {"precision": _DEFAULT_PRECISION},
ResourceCRX: {"precision": _DEFAULT_PRECISION},
ResourceCRY: {"precision": _DEFAULT_PRECISION},
ResourceCRZ: {"precision": _DEFAULT_PRECISION},
ResourceSelectPauliRot: {"precision": _DEFAULT_PRECISION},
ResourceQubitUnitary: {"precision": _DEFAULT_PRECISION},
ResourceQROMStatePreparation: {"precision": _DEFAULT_PRECISION},
ResourceMPSPrep: {"precision": _DEFAULT_PRECISION},
ResourceAliasSampling: {"precision": _DEFAULT_PRECISION},
ResourceQubitizeTHC: {
"rotation_precision": _DEFAULT_BIT_PRECISION,
"coeff_precision": _DEFAULT_BIT_PRECISION,
},
ResourceSelectTHC: {
"rotation_precision": _DEFAULT_BIT_PRECISION,
"coeff_precision": _DEFAULT_BIT_PRECISION,
},
ResourcePrepTHC: {
"rotation_precision": _DEFAULT_BIT_PRECISION,
"coeff_precision": _DEFAULT_BIT_PRECISION,
},
}
self._custom_decomps = {}
self._adj_custom_decomps = {}
self._ctrl_custom_decomps = {}
self._pow_custom_decomps = {}
def __str__(self) -> str:
decomps = [op.__name__ for op in self._custom_decomps]
adj_decomps = [f"Adjoint({op.__name__})" for op in self._adj_custom_decomps]
ctrl_decomps = [f"Controlled({op.__name__})" for op in self._ctrl_custom_decomps]
pow_decomps = [f"Pow({op.__name__})" for op in self._pow_custom_decomps]
all_op_strings = decomps + adj_decomps + ctrl_decomps + pow_decomps
op_names = ", ".join(all_op_strings)
dict_items_str = ",\n".join(
f" {key.__name__}: {value!r}" for key, value in self.resource_op_precisions.items()
)
formatted_dict = f"{{\n{dict_items_str}\n}}"
return (
f"ResourceConfig(\n"
f" precisions = {formatted_dict},\n"
f" custom decomps = [{op_names}]\n)"
)
def __repr__(self) -> str:
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})"
[docs]
def set_precision(self, op_type: type[ResourceOperator], precision: float) -> None:
r"""Sets the precision for a given resource operator.
This method updates the precision value for operators that use a single
tolerance parameter (e.g., for synthesis error). It will raise an error
if you attempt to set the precision for an operator that is not
configurable or uses bit-precisions. A negative precision will also raise an error.
Args:
op_type (type[ResourceOperator]): the operator class for which
to set the precision
precision (float): The desired synthesis precision tolerance. A smaller
value corresponds to a higher precision compilation, which may
increase the required gate counts. Must be greater than 0.
Raises:
ValueError: If ``op_type`` is not a configurable operator or if setting
the precision for it is not supported, or if ``precision`` is negative.
**Example**
.. code-block:: python
from pennylane.labs.resource_estimation import ResourceConfig
from pennylane.labs.resource_estimation.templates import ResourceSelectPauliRot
config = ResourceConfig()
# Check the default precision
default = config.resource_op_precisions[ResourceSelectPauliRot]['precision']
print(f"Default precision for SelectPauliRot: {default}")
# Set a new precision
config.set_precision(ResourceSelectPauliRot, precision=1e-5)
new = config.resource_op_precisions[ResourceSelectPauliRot]['precision']
print(f"New precision for SelectPauliRot: {new}")
.. code-block:: pycon
Default precision for SelectPauliRot: 1e-09
New precision for SelectPauliRot: 1e-05
"""
if precision < 0:
raise ValueError(f"Precision must be a non-negative value, but got {precision}.")
if op_type not in self.resource_op_precisions:
configurable_ops = sorted(
[
op.__name__
for op, params in self.resource_op_precisions.items()
if "precision" in params
]
)
raise ValueError(
f"{op_type.__name__} is not a configurable operator. "
f"Configurable operators are: {', '.join(configurable_ops)}"
)
if "precision" not in self.resource_op_precisions[op_type]:
raise ValueError(f"Setting precision for {op_type.__name__} is not supported.")
self.resource_op_precisions[op_type]["precision"] = precision
[docs]
def set_single_qubit_rot_precision(self, precision: float) -> None:
r"""Sets the synthesis precision for all single-qubit rotation gates.
This is a convenience method to update the synthesis precision tolerance
for all standard single-qubit rotation gates and their
controlled versions at once. The synthesis precision dictates the precision
for compiling rotation gates into a discrete gate set, which in turn
affects the number of gates required.
This method updates the ``precision`` value for the following operators:
- :class:`~.ResourceRX`
- :class:`~.ResourceRY`
- :class:`~.ResourceRZ`
- :class:`~.ResourceCRX`
- :class:`~.ResourceCRY`
- :class:`~.ResourceCRZ`
Args:
precision (float): The desired synthesis precision tolerance. A smaller
value corresponds to a higher precision compilation, which may
increase the required gate counts. Must be greater than ``0``.
Raises:
ValueError: If ``precision`` is a negative value.
**Example**
.. code-block:: python
from pennylane.labs.resource_estimation import ResourceConfig
from pennylane.labs.resource_estimation.ops.qubit.parametric_ops_single_qubit import ResourceRX
config = ResourceConfig()
print(f"Default RX precision: {config.resource_op_precisions[ResourceRX]['precision']}")
config.set_single_qubit_rot_precision(1e-5)
print(f"Updated RX precision: {config.resource_op_precisions[ResourceRX]['precision']}")
.. code-block:: pycon
Default RX precision: 1e-09
Updated RX precision: 1e-05
"""
if precision < 0:
raise ValueError(f"Precision must be a non-negative value, but got {precision}.")
self.resource_op_precisions[ResourceRX]["precision"] = precision
self.resource_op_precisions[ResourceCRX]["precision"] = precision
self.resource_op_precisions[ResourceRY]["precision"] = precision
self.resource_op_precisions[ResourceCRY]["precision"] = precision
self.resource_op_precisions[ResourceRZ]["precision"] = precision
self.resource_op_precisions[ResourceCRZ]["precision"] = precision
[docs]
def set_decomp(
self,
op_type: type[ResourceOperator],
decomp_func: Callable,
decomp_type: DecompositionType | None = DecompositionType.BASE,
) -> None:
"""Sets a custom function to override the default resource decomposition.
Args:
op_type (type[ResourceOperator]): the operator class whose decomposition is being overriden.
decomp_func (Callable): the new resource decomposition function to be set as default.
decomp_type (None | DecompositionType): the decomposition type to override. Options are
``"adj"``, ``"pow"``, ``"ctrl"``,
and ``"base"``. Default is ``"base"``.
Raises:
ValueError: If ``decomp_type`` is not a valid decomposition type.
.. note::
The new decomposition function, ``decomp_func``, should have the same signature as the one it replaces.
Specifically, the signature should match the :code:`resource_keys` of the base resource
operator class being overriden.
**Example**
.. code-block:: python
from pennylane.labs import resource_estimation as plre
def custom_res_decomp(**kwargs):
h = plre.resource_rep(plre.ResourceHadamard)
s = plre.resource_rep(plre.ResourceS)
return [plre.GateCount(h, 1), plre.GateCount(s, 2)]
.. code-block:: pycon
>>> print(plre.estimate(plre.ResourceX(), gate_set={"Hadamard", "Z", "S"}))
--- Resources: ---
Total qubits: 1
Total gates : 4
Qubit breakdown:
clean qubits: 0, dirty qubits: 0, algorithmic qubits: 1
Gate breakdown:
{'Hadamard': 2, 'S': 2}
>>> config = plre.ResourceConfig()
>>> config.set_decomp(plre.ResourceX, custom_res_decomp)
>>> print(plre.estimate(plre.ResourceX(), gate_set={"Hadamard", "Z", "S"}, config=config))
--- Resources: ---
Total qubits: 1
Total gates : 3
Qubit breakdown:
clean qubits: 0, dirty qubits: 0, algorithmic qubits: 1
Gate breakdown:
{'S': 1, 'Hadamard': 2}
"""
if decomp_type is None:
decomp_type = DecompositionType("base")
else:
decomp_type = DecompositionType(decomp_type)
if decomp_type == DecompositionType.ADJOINT:
self._adj_custom_decomps[op_type] = decomp_func
elif decomp_type == DecompositionType.CONTROLLED:
self._ctrl_custom_decomps[op_type] = decomp_func
elif decomp_type == DecompositionType.POW:
self._pow_custom_decomps[op_type] = decomp_func
elif decomp_type is None or decomp_type == DecompositionType.BASE:
self._custom_decomps[op_type] = decomp_func
_modules/pennylane/labs/resource_estimation/resource_config
Download Python script
Download Notebook
View on GitHub