qp.transforms.cancel_inverses¶
- cancel_inverses(tape, recursive=True)[source]¶
Quantum function transform to remove any operations that are applied next to their (self-)inverses or adjoint.
- Parameters:
tape (QNode or QuantumTape or Callable) – A quantum circuit.
recursive (bool) –
Whether or not to recursively cancel inverses after a first pair of mutual inverses has been cancelled. Enabled by default.
Note
This argument is not supported within a
qjit()workflow.
- Returns:
The transformed circuit as described in
qp.transform.- Return type:
qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]
Example
You can apply the cancel inverses transform directly on
QNode.>>> dev = qp.device('default.qubit', wires=3)
@qp.transforms.cancel_inverses @qp.qnode(device=dev) def circuit(x, y, z): qp.Hadamard(wires=0) qp.Hadamard(wires=1) qp.Hadamard(wires=0) qp.RX(x, wires=2) qp.RY(y, wires=1) qp.X(1) qp.RZ(z, wires=0) qp.RX(y, wires=2) qp.CNOT(wires=[0, 2]) qp.X(1) return qp.expval(qp.Z(0))
>>> print(circuit(0.1, 0.2, 0.3)) 1.0
Usage Details
You can also apply it on quantum functions:
def qfunc(x, y, z): qp.Hadamard(wires=0) qp.Hadamard(wires=1) qp.Hadamard(wires=0) qp.RX(x, wires=2) qp.RY(y, wires=1) qp.X(1) qp.RZ(z, wires=0) qp.RX(y, wires=2) qp.CNOT(wires=[0, 2]) qp.X(1) return qp.expval(qp.Z(0))
The circuit before optimization:
>>> qnode = qp.QNode(qfunc, dev) >>> print(qp.draw(qnode)(1, 2, 3)) 0: ──H─────────H─────────RZ(3.00)─╭●────┤ <Z> 1: ──H─────────RY(2.00)──X────────│───X─┤ 2: ──RX(1.00)──RX(2.00)───────────╰X────┤
We can see that there are two adjacent Hadamards on the first qubit that should cancel each other out. Similarly, there are two Pauli-X gates on the second qubit that should cancel. We can obtain a simplified circuit by running the
cancel_inversestransform:>>> optimized_qfunc = qp.transforms.cancel_inverses(qfunc) >>> optimized_qnode = qp.QNode(optimized_qfunc, dev) >>> print(qp.draw(optimized_qnode)(1, 2, 3)) 0: ──RZ(3.00)───────────╭●─┤ <Z> 1: ──H─────────RY(2.00)─│──┤ 2: ──RX(1.00)──RX(2.00)─╰X─┤
Usage with qjit
There are two key differences to note when using
cancel_inverseswithqjit:The
recursiveargument is not supoprted, and an error will be raised if a value forrecursiveis specified.Only the following gates can be optimized by
cancel_inverseswithqjit:
dev = qp.device("lightning.qubit", wires=1) @qp.qjit(capture=True) @qp.transforms.cancel_inverses @qp.qnode(dev) def circuit(): qp.RX(0.1, wires=0) qp.Hadamard(wires=0) qp.Hadamard(wires=0) return qp.expval(qp.PauliZ(0))
>>> print(qp.specs(circuit, level=1)()) Device: lightning.qubit Device wires: 1 Shots: Shots(total=None) Level: cancel-inverses Wire allocations: 1 Total gates: 1 Gate counts: - RX: 1 Measurements: - expval(PauliZ): 1 Depth: Not computed
Additionally, the
cancel_inversestransform withqjitsupports loop-boundary optimization.For more technical information on how this transform behaves, consult the Catalyst documentation for
catalyst.passes.cancel_inverses().