Source code for pennylane.transforms.optimization.commute_controlled
# Copyright 2018-2021 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 KIND, either express or implied.# See the License for the specific language governing permissions and# limitations under the License."""Transforms for pushing commuting gates through targets/control qubits."""frompennylane.tapeimportQuantumScript,QuantumScriptBatchfrompennylane.transformsimporttransformfrompennylane.typingimportPostprocessingFnfrompennylane.wiresimportWiresfrom.optimization_utilsimportfind_next_gatedef_commute_controlled_right(op_list):"""Push commuting single qubit gates to the right of controlled gates. Args: op_list (list[Operation]): The initial list of operations. Returns: list[Operation]: The modified list of operations with all single-qubit gates as far right as possible. """# We will go through the list backwards; whenever we find a single-qubit# gate, we will extract it and push it through 2-qubit gates as far as# possible to the right.current_location=len(op_list)-1whilecurrent_location>=0:current_gate=op_list[current_location]# We are looking only at the gates that can be pushed through# controls/targets; these are single-qubit gates with the basis# property specified.ifgetattr(current_gate,"basis",None)isNoneorlen(current_gate.wires)!=1:current_location-=1continue# Find the next gate that contains an overlapping wirenext_gate_idx=find_next_gate(current_gate.wires,op_list[current_location+1:])new_location=current_location# Loop as long as a valid next gate existswhilenext_gate_idxisnotNone:next_gate=op_list[new_location+next_gate_idx+1]# Only go ahead if information is availableifgetattr(next_gate,"basis",None)isNone:break# If the next gate does not have control_wires defined, it is not# controlled so we can't push through.iflen(next_gate.control_wires)==0:breakshared_controls=Wires.shared_wires([Wires(current_gate.wires),next_gate.control_wires])# Case 1: overlap is on the control wires. Only Z-type gates go throughiflen(shared_controls)>0:ifcurrent_gate.basis=="Z":new_location+=next_gate_idx+1else:break# Case 2: since we know the gates overlap somewhere, and it's a# single-qubit gate, if it wasn't on a control it's the target.else:ifcurrent_gate.basis==next_gate.basis:new_location+=next_gate_idx+1else:breaknext_gate_idx=find_next_gate(current_gate.wires,op_list[new_location+1:])# After we have gone as far as possible, move the gate to new locationop_list.insert(new_location+1,current_gate)op_list.pop(current_location)current_location-=1returnop_listdef_commute_controlled_left(op_list):"""Push commuting single qubit gates to the left of controlled gates. Args: op_list (list[Operation]): The initial list of operations. Returns: list[Operation]: The modified list of operations with all single-qubit gates as far left as possible. """# We will go through the list forwards; whenever we find a single-qubit# gate, we will extract it and push it through 2-qubit gates as far as# possible back to the left.current_location=0whilecurrent_location<len(op_list):current_gate=op_list[current_location]ifcurrent_gate.basisisNoneorlen(current_gate.wires)!=1:current_location+=1continue# Pass a backwards copy of the listprev_gate_idx=find_next_gate(current_gate.wires,op_list[:current_location][::-1])new_location=current_locationwhileprev_gate_idxisnotNone:prev_gate=op_list[new_location-prev_gate_idx-1]ifprev_gate.basisisNone:breakiflen(prev_gate.control_wires)==0:breakshared_controls=Wires.shared_wires([Wires(current_gate.wires),prev_gate.control_wires])iflen(shared_controls)>0:ifcurrent_gate.basis=="Z":new_location=new_location-prev_gate_idx-1else:breakelse:ifcurrent_gate.basis==prev_gate.basis:new_location=new_location-prev_gate_idx-1else:breakprev_gate_idx=find_next_gate(current_gate.wires,op_list[:new_location][::-1])op_list.pop(current_location)op_list.insert(new_location,current_gate)current_location+=1returnop_list
[docs]@transformdefcommute_controlled(tape:QuantumScript,direction="right")->tuple[QuantumScriptBatch,PostprocessingFn]:"""Quantum transform to move commuting gates past control and target qubits of controlled operations. Args: tape (QNode or QuantumTape or Callable): A quantum circuit. direction (str): The direction in which to move single-qubit gates. Options are "right" (default), or "left". Single-qubit gates will be pushed through controlled operations as far as possible in the specified direction. Returns: qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]: The transformed circuit as described in :func:`qml.transform <pennylane.transform>`. **Example** >>> dev = qml.device('default.qubit', wires=3) You can apply the transform directly on :class:`QNode`: .. code-block:: python @partial(commute_controlled, direction="right") @qml.qnode(device=dev) def circuit(theta): qml.CZ(wires=[0, 2]) qml.X(2) qml.S(wires=0) qml.CNOT(wires=[0, 1]) qml.Y(1) qml.CRY(theta, wires=[0, 1]) qml.PhaseShift(theta/2, wires=0) qml.Toffoli(wires=[0, 1, 2]) qml.T(wires=0) qml.RZ(theta/2, wires=1) return qml.expval(qml.Z(0)) >>> circuit(0.5) 0.9999999999999999 .. details:: :title: Usage Details You can also apply it on quantum function. .. code-block:: python def qfunc(theta): qml.CZ(wires=[0, 2]) qml.X(2) qml.S(wires=0) qml.CNOT(wires=[0, 1]) qml.Y(1) qml.CRY(theta, wires=[0, 1]) qml.PhaseShift(theta/2, wires=0) qml.Toffoli(wires=[0, 1, 2]) qml.T(wires=0) qml.RZ(theta/2, wires=1) return qml.expval(qml.Z(0)) >>> qnode = qml.QNode(qfunc, dev) >>> print(qml.draw(qnode)(0.5)) 0: ─╭●──S─╭●────╭●─────────Rϕ(0.25)─╭●──T────────┤ <Z> 1: ─│─────╰X──Y─╰RY(0.50)───────────├●──RZ(0.25)─┤ 2: ─╰Z──X───────────────────────────╰X───────────┤ Diagonal gates on either side of control qubits do not affect the outcome of controlled gates; thus we can push all the single-qubit gates on the first qubit together on the right (and fuse them if desired). Similarly, X gates commute with the target of ``CNOT`` and ``Toffoli`` (and ``PauliY`` with ``CRY``). We can use the transform to push single-qubit gates as far as possible through the controlled operations: >>> optimized_qfunc = commute_controlled(qfunc, direction="right") >>> optimized_qnode = qml.QNode(optimized_qfunc, dev) >>> print(qml.draw(optimized_qnode)(0.5)) 0: ─╭●─╭●─╭●───────────╭●──S─────────Rϕ(0.25)──T─┤ <Z> 1: ─│──╰X─╰RY(0.50)──Y─├●──RZ(0.25)──────────────┤ 2: ─╰Z─────────────────╰X──X─────────────────────┤ """ifdirectionnotin("left","right"):raiseValueError("Direction for commute_controlled must be 'left' or 'right'")ifdirection=="right":op_list=_commute_controlled_right(tape.operations)else:op_list=_commute_controlled_left(tape.operations)new_tape=tape.copy(operations=op_list)defnull_postprocessing(results):"""A postprocesing function returned by a transform that only converts the batch of results into a result for a single ``QuantumTape``. """returnresults[0]return[new_tape],null_postprocessing