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**
>>> from pennylane import 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:
>>> from pennylane import estimator as qre
>>> from pennylane.estimator import GateCount, resource_rep
>>> def resource_decomp(num_ctrl_wires=3, num_ctrl_values=0, **kwargs):
... gate_list = []
...
... gate_list.append(GateCount(resource_rep(qre.TempAND), 1))
... gate_list.append(GateCount(resource_rep(qre.Adjoint, {"base_cmpr_op": resource_rep(qre.TempAND)}), 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)
>>> print(res.WireResourceManager)
WireResourceManager(zeroed wires =0, any_state wires=0, algorithmic wires=4, tight budget=False)
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:
>>> from pennylane import estimator as qre
>>> from pennylane.estimator import GateCount, resource_rep
>>> def resource_decomp():
... gate_list = []
... gate_list.append(qre.Allocate(num_wires=1))
...
... gate_list.append(GateCount(resource_rep(qre.TempAND), 1))
... gate_list.append(GateCount(resource_rep(qre.Adjoint, {"base_cmpr_op": resource_rep(qre.TempAND)}), 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)
>>> print(res.WireResourceManager)
WireResourceManager(zeroed wires=1, any_state wires=0, algorithmic wires=4, tight budget=False)
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.
>>> from pennylane import estimator as qre
>>> from pennylane.estimator import GateCount, resource_rep
>>> def resource_decomp(num_ctrl_wires=3, num_ctrl_values=0, **kwargs):
... gate_list = []
... gate_list.append(qre.Allocate(num_wires=1))
...
... gate_list.append(GateCount(resource_rep(qre.TempAND), 1))
... gate_list.append(GateCount(resource_rep(qre.Adjoint, {"base_cmpr_op": resource_rep(qre.TempAND)}), 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)
>>> print(res.WireResourceManager)
WireResourceManager(zeroed wires=0, any_state wires=1, algorithmic wires=4, tight budget=False)
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:
>>> from pennylane import estimator as qre
>>> from pennylane.estimator import GateCount, resource_rep
>>> def resource_decomp():
... gate_list = []
... gate_list.append(qre.Allocate(num_wires=1))
...
... gate_list.append(GateCount(resource_rep(qre.TempAND), 1))
... gate_list.append(GateCount(resource_rep(qre.Adjoint, {"base_cmpr_op": resource_rep(qre.TempAND)}), 1))
... gate_list.append(GateCount(resource_rep(qre.Toffoli), 1))
...
... gate_list.append(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)
>>> print(res.WireResourceManager)
WireResourceManager(zeroed wires=1, any_state wires=0, algorithmic wires=4, tight budget=False)
Now, the auxiliary wire is freed and is returned in the zeroed state after the decomposition, and can
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