Qiskit / qiskit

Qiskit is an open-source SDK for working with quantum computers at the level of extended quantum circuits, operators, and primitives.
https://www.ibm.com/quantum/qiskit
Apache License 2.0
4.83k stars 2.29k forks source link

Support GateDirection for custom asymmetric gates #12647

Open eendebakpt opened 1 week ago

eendebakpt commented 1 week ago

What should we add?

For custom gates translation rules can be added to the default equivalence library using the SessionEquivalence object. This is not always enough for the transpiler to compile circuits with custom asymmetric gates. A minimal example with a custom gate, some equivalence rules and 3 circuits to transpile.

import qiskit
from qiskit import QuantumCircuit, transpile
from qiskit.circuit import Gate, Measure, Parameter
from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary
from qiskit.circuit.library import CZGate, RXGate, RZGate
from qiskit.providers import BackendV2 as Backend
from qiskit.providers import Options
from qiskit.transpiler import Target

class WGate(Gate):
    def __init__(self, label=None):
        super().__init__("w", 2, [], label=label)

    def _define(self):
        qc = QuantumCircuit(2)
        q.x(1)
        q.cz(0, 1)
        q.x(2)
        self.definition = qc

def_cz_in = qiskit.QuantumCircuit(2)
def_cz_in.x(1)
def_cz_in.append(WGate(), [0, 1])
def_cz_in.x(1)
SessionEquivalenceLibrary.add_equivalence(CZGate(), def_cz_in)

def_cz_in = qiskit.QuantumCircuit(2)
def_cz_in.x(0)
def_cz_in.append(WGate(), [1, 0])
def_cz_in.x(0)

SessionEquivalenceLibrary.add_equivalence(CZGate(), def_cz_in)

# does rule should allow the transpiler to replace W(0,1) with W(1, 0)
w_swap = qiskit.QuantumCircuit(2)
w_swap.x(0)
w_swap.x(1)
w_swap.append(WGate(), [1, 0])
w_swap.x(0)
w_swap.x(1)
SessionEquivalenceLibrary.add_equivalence(WGate(), w_swap)

_qc = qiskit.QuantumCircuit(2)
_qc.x(1)
_qc.cz(0, 1)
_qc.x(1)
SessionEquivalenceLibrary.add_equivalence(WGate(), _qc)

class MyBackend(Backend):
    def __init__(self):
        super().__init__()

        number_of_qubits = 2
        self._target = Target("Test")
        theta = Parameter("ϴ")
        single_qubit_props = {(qubit,): None for qubit in range(number_of_qubits)}
        self._target.add_instruction(RXGate(theta), single_qubit_props)
        self._target.add_instruction(RZGate(theta), single_qubit_props)

        w_props = {(1, 0): None}
        self._target.add_instruction(WGate(), w_props)

        meas_props = {(qubit,): None for qubit in range(number_of_qubits)}
        self._target.add_instruction(Measure(), meas_props)

        self.options.set_validator("shots", (1, 24096))
        self.options.set_validator("memory", bool)

    @property
    def target(self):
        return self._target

    @property
    def max_circuits(self):
        return 1024

    @classmethod
    def _default_options(cls):
        return Options(shots=1024, memory=False)

    def run(circuits, **kwargs):
        pass

if __name__ == "__main__":
    d=SessionEquivalenceLibrary.get_entry(WGate())
    for c in d:
        print(c.draw())

if __name__ == "__main__":
    b = MyBackend()

    q = QuantumCircuit(2)
    q.cx(0, 1)
    qc = transpile(q, backend=b) # fine
    print(qc.draw())

    q = QuantumCircuit(2)
    q.append(WGate(), [1, 0])
    qc = transpile(q, backend=b) # fine
    print(qc.draw())

    q = QuantumCircuit(2)
    q.append(WGate(), [0, 1])
    qc = transpile(q, backend=b) # raises error
    print(qc.draw())

On Slack some suggestions for adding this functionality have already been made. See https://qiskit.slack.com/archives/C7SS31917/p1719247227017329