Compilation transforms

Transforms for transpiling textbook gates into native trapped-ion gates.

The main transform in this module, ionizer.transforms.ionize(), performs end-to-end transpilation and optimization of circuits. It calls a number of helper transforms which can also be used individually.

All transforms contain a mechanism for under-the-hood equivalence checking (up to a global phase) through the verify_equivalence flag. When set, an error will be raised if the transpiled circuit is not equivalent to the original. For details and example usage see Equivalence validation and ionizer.utils.flag_non_equivalence().

transforms.ionize(verify_equivalence=False)

Apply a sequence of passes to transpile and optimize a circuit over the trapped-ion gate set \(GPI\), \(GPI2\), and \(MS\).

The following sequence of passes is performed:

  • Decompose all operations into Paulis/Pauli rotations, Hadamard, and \(CNOT\)

  • Cancel inverses and merge single-qubit rotations

  • Convert everything except \(RZ\) to \(GPI\), \(GPI2\), and \(MS\) gates

  • Virtually apply \(RZ\) gates

  • Repeatedly apply single-qubit gate fusion and commutation through \(MS\) gates, and perform simplification based on a database of circuit identities.

Note

When verify_equivalence is set to True, equivalence checking up to a global phase is performed with respect to the initial and final circuit matrices only. It is not checked for intermediate transforms.

Parameters:
  • tape (pennylane.QuantumTape) – A quantum tape to transform.

  • verify_equivalence (bool) – Whether to perform background equivalence checking (up to global phase) of the circuit before and after the transform.

Returns:

qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]: The transformed circuit as described in qml.transform.

Example

import pennylane as qml
from ionizer.transforms import ionize

dev = qml.device("default.qubit", wires=3)

@qml.qnode(dev)
@ionize
def circuit():
    qml.Hadamard(wires=0)
    qml.PauliX(wires=1)
    qml.RX(0.2, wires=2)
    qml.CRY(0.3, wires=[0, 1])
    return qml.probs()
>>> qml.draw(circuit)()
0: ─────────────────────────────────────╭MS──────────────────────────────────────╭MS──GPI2(-1.57)─┤  Probs
1: ──GPI2(1.57)──GPI(-0.86)──GPI2(1.42)─╰MS──GPI2(-1.57)──GPI(2.43)──GPI2(-1.42)─╰MS──────────────┤  Probs
2: ──GPI2(1.57)──GPI(-1.47)──GPI2(1.57)───────────────────────────────────────────────────────────┤  Probs
transforms.commute_through_ms_gates(direction='right', verify_equivalence=False)

Commute \(GPI\) and \(GPI2\) gates with special angle values through MS gates.

The following gates commute with \(MS\) gates when applied to either qubit in the \(MS\) gate: \(GPI2(0)\), \(GPI2(\pm \pi)\), \(GPI(0)\), \(GPI(\pm \pi)\).

When there are multiple adjacent \(MS\) gates, commuting \(GPI\) and \(GPI2\) gates are pushed as far as possible in the specified direction (see example).

This function is based on PennyLane’s commute_controlled transform.

Parameters:
  • tape (pennylane.QuantumTape) – A quantum tape to transform.

  • direction (str) – Either "right" (default) or "left" to indicate the direction gates should move (from a circuit diagram perspective).

  • verify_equivalence (bool) – Whether to perform background equivalence checking (up to global phase) of the circuit before and after the transform.

Returns:

qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]: The transformed circuit as described in qml.transform.

Example

import pennylane as qml
from pennylane import numpy as np
from functools import partial

dev = qml.device("default.qubit", wires=3)

@qml.qnode(dev)
@partial(commute_through_ms_gates, direction="left")
def circuit():
    MS(wires=[0, 1])
    MS(wires=[1, 2])
    GPI2(np.pi, wires=0)
    GPI(0, wires=1)
    GPI(0.3, wires=2)
    return qml.probs()
>>> qml.draw(circuit)()
0: ──GPI2(3.14)─╭MS────────────────┤  Probs
1: ──GPI(0.00)──╰MS─╭MS────────────┤  Probs
2: ─────────────────╰MS──GPI(0.30)─┤  Probs
transforms.virtualize_rz_gates(verify_equivalence=False)

Apply \(RZ\) gates virtually by adjusting the phase of adjacent \(GPI\) and \(GPI2\) gates.

This transform reads a circuit from left to right, and applies the following circuit identities (expressed in matrix order):

  • \(GPI(x) RZ(z) = GPI(x - z/2)\)

  • \(GPI2(x) RZ(z) = RZ(z) GPI2(x - z)\)

\(RZ\) are pushed as far right as possible, until \(MS\) gates are encountered or the end of the circuit is reached. Any \(RZ(\phi)\) that are not absorbed into native gates are then implemented as

\[RZ(\phi) = GPI(0) GPI(-\phi/2).\]
Parameters:
  • tape (pennylane.QuantumTape) – A quantum tape to transform

  • verify_equivalence (bool) – Whether to perform background equivalence checking (up to global phase) of the circuit before and after the transform.

Returns:

qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]: The transformed circuit as described in qml.transform.

Example

import pennylane as qml
from pennylane import numpy as np

dev = qml.device("default.qubit", wires=2)

@qml.qnode(dev)
@virtualize_rz_gates
def circuit():
    qml.RZ(0.3, wires=0)
    GPI(0.5, wires=0)

    GPI2(0.2, wires=1)
    qml.RZ(np.pi/2, wires=1)
    GPI2(0.4, wires=1)
    GPI(-np.pi/2, wires=1)

    MS(wires=[0, 1])

    qml.RZ(0.8, wires=1)
    return qml.probs()
>>> qml.draw(circuit)()
0: ──GPI(0.35)───────────────────────────╭MS────────────────────────┤  Probs
1: ──GPI2(0.20)──GPI2(-1.17)──GPI(-2.36)─╰MS──GPI(-0.40)──GPI(0.00)─┤  Probs
transforms.single_qubit_fusion_gpi(verify_equivalence=False)

Simplify sequences of \(GPI\) and \(GPI2\) gates using gate fusion and circuit identities.

Any sequence of more than 3 gates will be fused and re-implemented up to global phase using \(GPI\) and \(GPI2\) (see ionizer.utils.extract_gpi2_gpi_gpi2_angles()).

Sequences of two or three gates (including those obtained through gate fusion) are then checked against the database of known circuit identities for simplifications (see ionizer.identity_hunter.lookup_gate_identity()).

This transform is based on PennyLane’s single_qubit_fusion transform.

Parameters:
  • tape (pennylane.QuantumTape) – A quantum tape to transform.

  • verify_equivalence (bool) – Whether to perform background equivalence checking (up to global phase) of the circuit before and after the transform.

Returns:

qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]: The transformed circuit as described in qml.transform.

Example

import pennylane as qml
from pennylane import numpy as np

dev = qml.device("default.qubit", wires=3)

@qml.qnode(dev)
@single_qubit_fusion_gpi
def circuit():
    # Known circuit identity
    GPI(np.pi/4, wires=0)
    GPI2(-3 * np.pi/4, wires=0)

    # Already three gates, but no known identity
    GPI2(0.2, wires=1)
    GPI2(0.4, wires=1)
    GPI(-np.pi/2, wires=1)

    # Squished down to three gates
    GPI2(0.1, wires=2)
    GPI2(0.2, wires=2)
    GPI(0.3, wires=2)
    GPI(0.4, wires=2)
    GPI2(0.5, wires=2)

    return qml.probs()
>>> qml.draw(circuit)()
0: ──GPI2(0.79)───────────────────────────┤  Probs
1: ──GPI2(0.20)───GPI2(0.40)──GPI(-1.57)──┤  Probs
2: ──GPI2(-1.57)──GPI(2.65)───GPI2(-0.97)─┤  Probs
transforms.convert_to_gpi(exclude_list=None, verify_equivalence=False)

Transpile desired gates in a circuit to trapped-ion gates.

Parameters:
  • tape (pennylane.QuantumTape) – A quantum tape to transform.

  • exclude_list (list[str]) – A list of names of gates to exclude from conversion (see the ionize transform for an example).

  • verify_equivalence (bool) – Whether to perform background equivalence checking (up to global phase) of the circuit before and after the transform.

Returns:

qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]: The transformed circuit as described in qml.transform.

Example

import pennylane as qml
from pennylane import numpy as np

dev = qml.device("default.qubit", wires=3)

@qml.qnode(dev)
@partial(convert_to_gpi, exclude_list=["IsingYY"])
def circuit():
    # Gates with known decompositions
    qml.Hadamard(wires=0)
    qml.PauliX(wires=1)
    qml.RX(0.2, wires=2)

    # Should not be decomposed
    qml.IsingYY(0.1, wires=[0, 1])
    qml.IsingYY(0.2, wires=[0, 2])

    # Gets expanded into two RY and two CNOT gates, which are then decomposed
    qml.CRY(0.3, wires=[0, 1])

    return qml.probs()
>>> qml.draw(circuit)()
0: ──GPI(0.00)───GPI2(-1.57)─╭IsingYY(0.10)─╭IsingYY(0.20)──GPI2(1.57)────────────────────────╭MS───GPI2(3.14)──GPI2(-1.57)──GPI2(1.57)─────────────╭MS──GPI2(3.14)──GPI2(-1.57)─┤  Probs
1: ──GPI(0.00)───────────────╰IsingYY(0.10)─│───────────────GPI2(3.14)──GPI(0.07)──GPI2(3.14)─╰MS───GPI2(3.14)──GPI2(3.14)───GPI(-0.07)──GPI2(3.14)─╰MS──GPI2(3.14)──────────────┤  Probs
2: ──GPI2(1.57)──GPI(-1.47)───GPI2(1.57)────╰IsingYY(0.20)───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤  Probs