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
WireCutnodes 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 kahyparfor 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
Nonein which casekwargsmust be fully specified for passing to thecut_method.replace_wire_cuts (bool) – Whether to replace
WireCutnodes withMeasureNodeandPrepareNodepairs. 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
Falsewhich 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
WireCutnodes 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 aWireCutmanually 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
WireCutdoesn’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
WireCutnodes with pairs ofMeasureNodeandPrepareNode, 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
CutStrategycan 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_wiresargument (or equivalently directly passing apennylane.devices.Deviceto thedeviceargument):>>> 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
CutStrategytofind_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>