qml.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.ops = [ 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"), ] measurements = [qml.expval(qml.X(0) @ qml.Y("a") @ qml.Z("b"))] tape = qml.tape.QuantumTape(ops, measurements)
>>> print(qml.drawer.tape_text(tape, decimals=1)) 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.qcut.tape_to_graph(tape) >>> cut_graph = qml.qcut.find_and_place_cuts( ... graph=graph, ... num_fragments=2, ... imbalance=0.5, ... )
Visualizing the newly-placed cut:
>>> print(qml.qcut.graph_to_tape(cut_graph).draw(decimals=1)) 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.qcut.find_and_place_cuts( ... graph=graph, ... num_fragments=2, ... imbalance=0.5, ... replace_wire_cuts=True, ... ) >>> frags, comm_graph = qml.qcut.fragment_graph(cut_graph) >>> for t in frags: ... print(qml.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.qcut.CutStrategy(max_free_wires=2) >>> cut_strategy.get_cut_kwargs(graph) [{'num_fragments': 2, 'imbalance': 0.2857142857142858}, {'num_fragments': 3, 'imbalance': 0.2857142857142858}, {'num_fragments': 4, 'imbalance': 0.2857142857142858}, {'num_fragments': 5, 'imbalance': 0.2857142857142858}, {'num_fragments': 6, 'imbalance': 0.2857142857142858}, {'num_fragments': 7, 'imbalance': 0.2857142857142858}, {'num_fragments': 8, 'imbalance': 0.2857142857142858}, {'num_fragments': 9, 'imbalance': 0.2857142857142858}, {'num_fragments': 10, 'imbalance': 0.2857142857142858}, {'num_fragments': 11, 'imbalance': 0.2857142857142858}, {'num_fragments': 12, 'imbalance': 0.2857142857142858}, {'num_fragments': 13, 'imbalance': 0.0}, {'num_fragments': 14, 'imbalance': 0.0}]
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.qcut.find_and_place_cuts( graph=graph, cut_strategy=cut_strategy, ) >>> print(qml.qcut.graph_to_tape(cut_graph).draw()) 0: ──RX──//─╭●──//────────╭●──//────────┤ ╭<X@Y@Z> 1: ──RY──//─╰X──//─╭●──//─╰X────────────┤ │ a: ──RX──//─╭●──//─╰X──//─╭●──//──RX─//─┤ ├<X@Y@Z> b: ──RY──//─╰X──//────────╰X──//──RY────┤ ╰<X@Y@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.qcut.replace_wire_cut_nodes(cut_graph) >>> frags, comm_graph = qml.qcut.fragment_graph(cut_graph) >>> for t in frags: ... print(qml.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>