Source code for pennylane.estimator.wires_manager
# 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_STATE KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""This module contains the base class for wire management."""
from pennylane.queuing import QueuingManager
[docs]
class WireResourceManager:
r"""Manages and tracks the auxiliary and algorithmic wires used in a quantum circuit.
This class provides a high-level abstraction for managing wire resources within a quantum
circuit.
The manager tracks the state of three distinct types of wires:
* Zeroed state wires: Auxiliary wires that are in the :math:`|0\rangle` state. They are converted
to an unknown state upon allocation.
* Any state wires: Auxiliary wires that are in an unknown state. They are converted to
zeroed wires when they are freed.
* Algorithmic wires: The core wires used by the quantum algorithm.
Args:
zeroed (int): Number of zeroed state work wires.
any_state (int): Number of work wires in an unknown state, default is ``0``.
algo_wires (int): Number of algorithmic wires, default value is ``0``.
tight_budget (bool): Determines whether extra zeroed state wires can be allocated when they
exceed the available amount. The default is ``False``.
**Example**
>>> import pennylane.estimator as qre
>>> q = qre.WireResourceManager(
... zeroed=2,
... any_state=2,
... tight_budget=False,
... )
>>> print(q)
WireResourceManager(zeroed wires=2, any_state wires=2, algorithmic wires=0, tight budget=False)
"""
def __init__(
self, zeroed: int, any_state: int = 0, algo_wires: int = 0, tight_budget: bool = False
) -> None:
self.tight_budget = tight_budget
self._algo_wires = algo_wires
self.zeroed = zeroed
self.any_state = any_state
def __str__(self) -> str:
return (
f"WireResourceManager(zeroed wires={self.zeroed}, any_state wires={self.any_state}, "
f"algorithmic wires={self.algo_wires}, tight budget={self.tight_budget})"
)
def __repr__(self) -> str:
return (
f"WireResourceManager(zeroed={self.zeroed}, any_state={self.any_state}, algo_wires={self.algo_wires}, "
f"tight_budget={self.tight_budget})"
)
def __eq__(self, other: object) -> bool:
return (
isinstance(other, self.__class__)
and (self.zeroed == other.zeroed)
and (self.any_state == other.any_state)
and (self.algo_wires == other.algo_wires)
and (self.tight_budget == other.tight_budget)
)
@property
def algo_wires(self) -> int:
r"""Returns the number of algorithmic wires."""
return self._algo_wires
@property
def total_wires(self) -> int:
r"""Returns the number of total wires."""
return self.zeroed + self.any_state + self.algo_wires
@algo_wires.setter
def algo_wires(self, count: int): # these get set manually, the rest are dynamically updated
r"""Setter for algorithmic wires."""
self._algo_wires = count
[docs]
def grab_zeroed(self, num_wires: int) -> None:
r"""Grabs zeroed wires, and moves them to an arbitrary state; incrementing the number of any_state wires.
Args:
num_wires(int) : number of zeroed wires to be grabbed
Raises:
ValueError: If tight_budget is `True` and the number of wires to be grabbed is greater than
available zeroed wires.
"""
available_zeroed = self.zeroed
if num_wires > available_zeroed:
if self.tight_budget:
raise ValueError(
f"Grabbing more wires than available zeroed wires. "
f"Number of zeroed wires available is {available_zeroed}, while {num_wires} are being grabbed."
)
self.zeroed = 0
else:
self.zeroed -= num_wires
self.any_state += num_wires
[docs]
def free_wires(self, num_wires: int) -> None:
r"""Frees any_state wires and converts them into zeroed wires.
Args:
num_wires(int) : number of wires to be freed
Raises:
ValueError: If number of wires to be freed is greater than available any_state wires.
"""
if num_wires > self.any_state:
raise ValueError(
f"Freeing more wires than available any_state wires. "
f"Number of any_state wires available is {self.any_state}, while {num_wires} wires are being released."
)
self.any_state -= num_wires
self.zeroed += num_wires
class _WireAction:
"""Base class for operations that manage wire resources."""
def __init__(self, num_wires):
self.num_wires = num_wires
if QueuingManager.recording():
self.queue()
def queue(self, context=QueuingManager):
r"""Adds the wire action object to a queue."""
context.append(self)
return self
def __eq__(self, other: "_WireAction") -> bool:
return isinstance(other, self.__class__) and self.num_wires == other.num_wires
def __mul__(self, other):
if isinstance(other, int):
return self.__class__(self.num_wires * other)
raise NotImplementedError
[docs]
class Allocate(_WireAction):
r"""Allows allocation of work wires through :class:`~pennylane.estimator.WireResourceManager`.
Args:
num_wires (int): number of work wires to be allocated
.. details::
:title: Usage Details
The ``Allocate`` class is typically used within a decomposition function to track the
allocation of auxiliary wires. This allows determination of a circuit's wire overhead.
In this example, we show the decomposition for a
3-controlled ``X`` gate, which requires one work wire.
First, we define a custom decomposition which doesn't track the extra work wire:
>>> import pennylane.estimator as qre
>>> from pennylane.estimator import GateCount, resource_rep
>>> def resource_decomp(num_ctrl_wires=3, num_zero_ctrl=0, **kwargs):
... gate_list = []
... gate_list.append(GateCount(resource_rep(qre.TemporaryAND), 1))
... gate_list.append(GateCount(resource_rep(qre.Adjoint, {"base_cmpr_op": resource_rep(qre.TemporaryAND)}), 1))
... gate_list.append(GateCount(resource_rep(qre.Toffoli), 1))
... return gate_list
>>> config = qre.ResourceConfig()
>>> config.set_decomp(qre.MultiControlledX, resource_decomp)
>>> res = qre.estimate(qre.MultiControlledX(3, 0), config=config)
>>> print(res.algo_wires, res.zeroed_wires, res.any_state_wires)
4 0 0
This decomposition uses a total of ``4`` wires and doesn't track the work wires.
Now, if we want to track the allocation of wires using ``Allocate``, the decomposition
can be redefined as:
>>> import pennylane.estimator as qre
>>> from pennylane.estimator import GateCount, resource_rep
>>> def resource_decomp(num_ctrl_wires=3, num_zero_ctrl=0, **kwargs):
... gate_list = []
... gate_list.append(qre.Allocate(num_wires=1))
... gate_list.append(GateCount(resource_rep(qre.TemporaryAND), 1))
... gate_list.append(GateCount(resource_rep(qre.Adjoint, {"base_cmpr_op": resource_rep(qre.TemporaryAND)}), 1))
... gate_list.append(GateCount(resource_rep(qre.Toffoli), 1))
... gate_list.append(qre.Deallocate(num_wires=1))
... return gate_list
>>> config = qre.ResourceConfig()
>>> config.set_decomp(qre.MultiControlledX, resource_decomp)
>>> res = qre.estimate(qre.MultiControlledX(3, 0), config=config)
>>> print(res.algo_wires, res.zeroed_wires, res.any_state_wires)
4 1 0
Now, the one extra auxiliary wire is being tracked.
"""
def __repr__(self) -> str:
return f"Allocate({self.num_wires})"
[docs]
class Deallocate(_WireAction):
r"""Allows freeing ``any_state`` work wires through :class:`~pennylane.estimator.WireResourceManager`.
Args:
num_wires (int): number of ``any_state`` work wires to be freed.
.. details::
:title: Usage Details
The ``Deallocate`` class is typically used within a decomposition function to track the
allocation of auxiliary wires. This allows to accurately determine the wire overhead
of a circuit. In this example, we show the decomposition for a
3-controlled ``X`` gate, which requires one work wire that is returned in a zeroed state.
First, we define a custom decomposition which allocates the work wire but doesn't free it.
>>> import pennylane.estimator as qre
>>> from pennylane.estimator import GateCount, resource_rep
>>> def resource_decomp(num_ctrl_wires=3, num_zero_ctrl=0, **kwargs):
... gate_list = []
... gate_list.append(qre.Allocate(num_wires=1))
... gate_list.append(GateCount(resource_rep(qre.TemporaryAND), 1))
... gate_list.append(GateCount(resource_rep(qre.Adjoint, {"base_cmpr_op": resource_rep(qre.TemporaryAND)}), 1))
... gate_list.append(GateCount(resource_rep(qre.Toffoli), 1))
... return gate_list
>>> config = qre.ResourceConfig()
>>> config.set_decomp(qre.MultiControlledX, resource_decomp)
>>> res = qre.estimate(qre.MultiControlledX(3, 0), config=config)
>>> print(res.algo_wires, res.zeroed_wires, res.any_state_wires)
4 0 1
This decomposition uses a total of ``4`` algorithmic wires and ``1`` work wire which is returned in an arbitrary state.
We can free this wire using ``Deallocate``, allowing it to be reused with more operations.
The decomposition can be redefined as:
>>> import pennylane.estimator as qre
>>> from pennylane.estimator import GateCount, resource_rep
>>> def resource_decomp(num_ctrl_wires=3, num_zero_ctrl=0, **kwargs):
... gate_list = []
... gate_list.append(qre.Allocate(num_wires=1))
... gate_list.append(GateCount(resource_rep(qre.TemporaryAND), 1))
... gate_list.append(GateCount(resource_rep(qre.Adjoint, {"base_cmpr_op": resource_rep(qre.TemporaryAND)}), 1))
... gate_list.append(GateCount(resource_rep(qre.Toffoli), 1))
... gate_list.append(qre.Deallocate(num_wires=1))
... return gate_list
>>> config = qre.ResourceConfig()
>>> config.set_decomp(qre.MultiControlledX, resource_decomp)
>>> res = qre.estimate(qre.MultiControlledX(3, 0), config=config)
>>> print(res.algo_wires, res.zeroed_wires, res.any_state_wires)
4 1 0
Now, the auxiliary wire is freed, meaning that it is described as being in the zeroed state
after the decomposition, and that it can now be used for other operators which require zeroed auxiliary wires.
"""
def __repr__(self) -> str:
return f"Deallocate({self.num_wires})"
_modules/pennylane/estimator/wires_manager
Download Python script
Download Notebook
View on GitHub