qml.decomposition.DecompositionGraph¶
- class DecompositionGraph(operations, gate_set, fixed_decomps=None, alt_decomps=None)[source]¶
Bases:
object
A graph that models a decomposition problem.
The decomposition graph contains two types of nodes: operator nodes and decomposition nodes. Each decomposition node is a
pennylane.decomposition.DecompositionRule
, and each operator node is aCompressedResourceOp
which contains an operator type and any additional parameters that affect the resource requirements of the operator. Essentially, two instances of the same operator type are represented by the same node in the graph if they’re expected to have the same decompositions.There are also two types of directed edges: edges that connect operators to the decomposition rules that contain them, and edges that connect decomposition rules to the operators that they decompose. The edge weights represent the difference in the total weights of the gates between the two states. Edges that connect decomposition rules to operators have a weight of 0 because an operator can be replaced with its decomposition at no additional cost.
On the other hand, edges that connect operators to the decomposition rule that contains them will have a weight that is the total resource estimate of the decomposition minus the resource estimate of the operator. Edges that connect an operator node to a decomposition node have a weight calculated by the difference of the sum of the gate counts multiplied by their respective gate weights in the decomposition, minus the weight of the operator of the operator node.
For example, if the graph was initialized with
{qml.CNOT: 10.0, qml.H: 1.0}
as the gate set, the edge that connects aCNOT
to the following decomposition rule:import pennylane as qml @qml.register_resources({qml.H: 2, qml.CNOT: 1}) def my_cz(wires): qml.H(wires=wires[1]) qml.CNOT(wires=wires) qml.H(wires=wires[1])
will have a weight of (10.0 + 2 * 1.0) - 10.0 = 2, because the decomposition rule contains 2 additional
H
gates. Note that this gate count is in terms of gates in the target gate set. IfH
isn’t supported and is in turn decomposed to twoRZ
gates and oneRX
gate, the weight of this edge becomes 2 * 3 = 6, ifRZ
andRX
have weights of 1.0 (the default). This way, the total distance from the basis gate set to a high-level gate is by default the total number of basis gates required to decompose this high-level gate, which allows us to use Dijkstra’s algorithm to find the most efficient decomposition. By specifying weights in the target gate set, the total distance calculation involves a sum of weighted gate counts, which can represent the relative cost of executing a particular element of the target gate set on the target hardware i.e. aT
gate.- Parameters:
operations (list[Operator or CompressedResourceOp]) – The list of operations to decompose.
gate_set (set[str | type] | dict[type | str, float]) – A set of gates in the target gate set or a dictionary mapping gates in the target gate set to their respective weights. All weights must be positive.
fixed_decomps (dict) – A dictionary mapping operator names to fixed decompositions.
alt_decomps (dict) – A dictionary mapping operator names to alternative decompositions.
Example
from pennylane.decomposition import DecompositionGraph op = qml.CRX(0.5, wires=[0, 1]) graph = DecompositionGraph( operations=[op], gate_set={"RZ", "RX", "CNOT", "GlobalPhase"}, ) graph.solve()
>>> with qml.queuing.AnnotatedQueue() as q: ... graph.decomposition(op)(0.5, wires=[0, 1]) >>> q.queue [RZ(1.5707963267948966, wires=[1]), RY(0.25, wires=[1]), CNOT(wires=[0, 1]), RY(-0.25, wires=[1]), CNOT(wires=[0, 1]), RZ(-1.5707963267948966, wires=[1])] >>> graph.resource_estimate(op) <num_gates=10, gate_counts={RZ: 6, CNOT: 2, RX: 2}, weighted_cost=10.0>
Methods
decomposition
(op)Returns the optimal decomposition rule for a given operator.
is_solved_for
(op)Tests whether the decomposition graph is solved for a given operator.
Returns the resource estimate for a given operator.
solve
([lazy])Solves the graph using the Dijkstra search algorithm.
- decomposition(op)[source]¶
Returns the optimal decomposition rule for a given operator.
- Parameters:
op (Operator) – The operator for which to return the optimal decomposition.
- Returns:
The optimal decomposition.
- Return type:
Example
The decomposition rule is a quantum function that takes
(*op.parameters, wires=op.wires, **op.hyperparameters)
as arguments.op = qml.CRY(0.2, wires=[0, 2]) graph = DecompositionGraph( operations=[op], gate_set={"RZ", "RX", "CNOT", "GlobalPhase"}, ) graph.solve() rule = graph.decomposition(op)
>>> with qml.queuing.AnnotatedQueue() as q: ... rule(*op.parameters, wires=op.wires, **op.hyperparameters) >>> q.queue [RY(0.1, wires=[2]), CNOT(wires=[0, 2]), RY(-0.1, wires=[2]), CNOT(wires=[0, 2])]
- resource_estimate(op)[source]¶
Returns the resource estimate for a given operator.
- Parameters:
op (Operator) – The operator for which to return the resource estimates.
- Returns:
The resource estimate.
- Return type:
Example
The resource estimate is a gate count in terms of the target gate set, not the immediate set of gates that the operator decomposes to.
op = qml.CRX(0.5, wires=[0, 1]) graph = DecompositionGraph( operations=[op], gate_set={"RZ", "RX", "CNOT", "GlobalPhase"}, ) graph.solve()
>>> with qml.queuing.AnnotatedQueue() as q: ... graph.decomposition(op)(0.5, wires=[0, 1]) >>> q.queue [RZ(1.5707963267948966, wires=[1]), RY(0.25, wires=[1]), CNOT(wires=[0, 1]), RY(-0.25, wires=[1]), CNOT(wires=[0, 1]), RZ(-1.5707963267948966, wires=[1])] >>> graph.resource_estimate(op) <num_gates=10, gate_counts={RZ: 6, CNOT: 2, RX: 2}, weighted_cost=10.0>