qml.transforms.qcut.find_and_place_cuts¶
-
find_and_place_cuts
(graph, cut_method=<function kahypar_cut>, cut_strategy=None, replace_wire_cuts=False, local_measurement=False, **kwargs)[source]¶ Automatically finds and places optimal
WireCut
nodes into a given tape-converted graph using a customizable graph partitioning function. Preserves existing placed cuts.- Parameters
graph (MultiDiGraph) – The original (tape-converted) graph to be cut.
cut_method (Callable) – A graph partitioning function that takes an input graph and returns a list of edges to be cut based on a given set of constraints and objective. Defaults to
kahypar_cut()
which requires KaHyPar to be installed usingpip install kahypar
for Linux and Mac users or visiting the instructions here to compile from source for Windows users.cut_strategy (CutStrategy) – Strategy for optimizing cutting parameters based on device constraints. Defaults to
None
in which casekwargs
must be fully specified for passing to thecut_method
.replace_wire_cuts (bool) – Whether to replace
WireCut
nodes withMeasureNode
andPrepareNode
pairs. Defaults toFalse
.local_measurement (bool) – Whether to use the local-measurement circuit-cutting objective, i.e. the maximum node-degree of the communication graph, for cut evaluation. Defaults to
False
which assumes global measurement and uses the total number of cuts as the cutting objective.kwargs – Additional keyword arguments to be passed to the callable
cut_method
.
- Returns
Copy of the input graph with
WireCut
nodes inserted.- Return type
nx.MultiDiGraph
Example
Consider the following 4-wire circuit with a single CNOT gate connecting the top (wires
[0, 1]
) and bottom (wires["a", "b"]
) halves of the circuit. Note there’s aWireCut
manually placed into the circuit already.with qml.tape.QuantumTape() as tape: qml.RX(0.1, wires=0) qml.RY(0.2, wires=1) qml.RX(0.3, wires="a") qml.RY(0.4, wires="b") qml.CNOT(wires=[0, 1]) qml.WireCut(wires=1) qml.CNOT(wires=["a", "b"]) qml.CNOT(wires=[1, "a"]) qml.CNOT(wires=[0, 1]) qml.CNOT(wires=["a", "b"]) qml.RX(0.5, wires="a") qml.RY(0.6, wires="b") qml.expval(qml.PauliX(wires=[0]) @ qml.PauliY(wires=["a"]) @ qml.PauliZ(wires=["b"]))
>>> print(qml.drawer.tape.text(tape)) 0: ──RX(0.1)──╭●──────────╭●───────────╭┤ ⟨X ⊗ Y ⊗ Z⟩ 1: ──RY(0.2)──╰X──//──╭●──╰X───────────│┤ a: ──RX(0.3)──╭●──────╰X──╭●──RX(0.5)──├┤ ⟨X ⊗ Y ⊗ Z⟩ b: ──RY(0.4)──╰X──────────╰X──RY(0.6)──╰┤ ⟨X ⊗ Y ⊗ Z⟩
Since the existing
WireCut
doesn’t sufficiently fragment the circuit, we can find the remaining cuts using the default KaHyPar partitioner:>>> graph = qml.transforms.qcut.tape_to_graph(tape) >>> cut_graph = qml.transforms.qcut.find_and_place_cuts( graph=graph, num_fragments=2, imbalance=0.5, )
Visualizing the newly-placed cut:
>>> print(qml.transforms.qcut.graph_to_tape(cut_graph).draw()) 0: ──RX(0.1)──╭●───────────────╭●────────╭┤ ⟨X ⊗ Y ⊗ Z⟩ 1: ──RY(0.2)──╰X──//──╭●───//──╰X────────│┤ a: ──RX(0.3)──╭●──────╰X──╭●────RX(0.5)──├┤ ⟨X ⊗ Y ⊗ Z⟩ b: ──RY(0.4)──╰X──────────╰X────RY(0.6)──╰┤ ⟨X ⊗ Y ⊗ Z⟩
We can then proceed with the usual process of replacing
WireCut
nodes with pairs ofMeasureNode
andPrepareNode
, and then break the graph into fragments. Or, alternatively, we can directly get such processed graph by passingreplace_wire_cuts=True
:>>> cut_graph = qml.transforms.qcut.find_and_place_cuts( graph=graph, num_fragments=2, imbalance=0.5, replace_wire_cuts=True, ) >>> frags, comm_graph = qml.transforms.qcut.fragment_graph(cut_graph) >>> for t in frags: ... print(qml.transforms.qcut.graph_to_tape(t).draw())
0: ──RX(0.1)──────╭●───────────────╭●──┤ ⟨X⟩ 1: ──RY(0.2)──────╰X──MeasureNode──│───┤ 2: ──PrepareNode───────────────────╰X──┤ a: ──RX(0.3)──────╭●──╭X──╭●────────────RX(0.5)──╭┤ ⟨Y ⊗ Z⟩ b: ──RY(0.4)──────╰X──│───╰X────────────RY(0.6)──╰┤ ⟨Y ⊗ Z⟩ 1: ──PrepareNode──────╰●───MeasureNode────────────┤
Alternatively, if all we want to do is to find the optimal way to fit a circuit onto a smaller device, a
CutStrategy
can be used to populate the necessary explorations of cutting parameters. As an extreme example, if the only device at our disposal is a 2-qubit device, a simple cut strategy is to simply specify the themax_free_wires
argument (or equivalently directly passing apennylane.Device
to thedevice
argument):>>> cut_strategy = qml.transforms.qcut.CutStrategy(max_free_wires=2) >>> print(cut_strategy.get_cut_kwargs(graph)) [{'num_fragments': 2, 'imbalance': 0.5714285714285714}, {'num_fragments': 3, 'imbalance': 1.4}, {'num_fragments': 4, 'imbalance': 1.75}, {'num_fragments': 5, 'imbalance': 2.3333333333333335}, {'num_fragments': 6, 'imbalance': 2.0}, {'num_fragments': 7, 'imbalance': 3.0}, {'num_fragments': 8, 'imbalance': 2.5}, {'num_fragments': 9, 'imbalance': 2.0}, {'num_fragments': 10, 'imbalance': 1.5}, {'num_fragments': 11, 'imbalance': 1.0}, {'num_fragments': 12, 'imbalance': 0.5}, {'num_fragments': 13, 'imbalance': 0.05}, {'num_fragments': 14, 'imbalance': 0.1}]
The printed list above shows all the possible cutting configurations one can attempt to perform in order to search for the optimal cut. This is done by directly passing a
CutStrategy
tofind_and_place_cuts()
:>>> cut_graph = qml.transforms.qcut.find_and_place_cuts( graph=graph, cut_strategy=cut_strategy, ) >>> print(qml.transforms.qcut.graph_to_tape(cut_graph).draw()) 0: ──RX──//─╭●──//────────╭●──//─────────┤ ╭<[email protected]@Z> 1: ──RY──//─╰X──//─╭●──//─╰X─────────────┤ │ a: ──RX──//─╭●──//─╰X──//─╭●──//──RX──//─┤ ├<[email protected]@Z> b: ──RY──//─╰X──//────────╰X──//──RY─────┤ ╰<[email protected]@Z>
As one can tell, quite a few cuts have to be made in order to execute the circuit on solely 2-qubit devices. To verify, let’s print the fragments:
>>> qml.transforms.qcut.replace_wire_cut_nodes(cut_graph) >>> frags, comm_graph = qml.transforms.qcut.fragment_graph(cut_graph) >>> for t in frags: ... print(qml.transforms.qcut.graph_to_tape(t).draw())
0: ──RX──MeasureNode─┤ 1: ──RY──MeasureNode─┤ a: ──RX──MeasureNode─┤ b: ──RY──MeasureNode─┤ 0: ──PrepareNode─╭●──MeasureNode─┤ 1: ──PrepareNode─╰X──MeasureNode─┤ a: ──PrepareNode─╭●──MeasureNode─┤ b: ──PrepareNode─╰X──MeasureNode─┤ 1: ──PrepareNode─╭●──MeasureNode─┤ a: ──PrepareNode─╰X──MeasureNode─┤ 0: ──PrepareNode─╭●──MeasureNode─┤ 1: ──PrepareNode─╰X──────────────┤ b: ──PrepareNode─╭X──MeasureNode─┤ a: ──PrepareNode─╰●──MeasureNode─┤ a: ──PrepareNode──RX──MeasureNode─┤ b: ──PrepareNode──RY─┤ <Z> 0: ──PrepareNode─┤ <X> a: ──PrepareNode─┤ <Y>