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
5.24k stars 2.36k forks source link

Complex conjugate of QuantumCircuits #7701

Open Qottmann opened 2 years ago

Qottmann commented 2 years ago

What is the expected behavior?

It would be extremely useful for quantum-assisted-quantum-compilation to have a method complex_conjugate() for QuantumCircuit objects that returns the same circuit but complex conjugated (not transposed).

If that is too much to ask, already having the option for individual circuit elements in the (circuit library)[https://qiskit.org/documentation/apidoc/circuit_library.html] additional to the inverse() (=adjoint for unitaries) method.

At the moment, one can do this by hand for Two-Local gates knowing that ry(x)* = ry(x), rx(x)*=rx(-x) and rz(x)* = rz(-x), assuming one is only using ry, rz and entangling gates that are not complex-valued. Then one can apply a mask consisting of +/-1 for the respective parameters.

I provide a short example that highlights how the problem can be circumvented and at the same time where this feature could be useful. Here, we compute the Hilbert Schmidt Test (HST) circuit, see fig. 4 in QAQC. I create a simple TwoLocal Ansatz circ and assign it random parameters. I then perform the HST between circ and itself. The output probablity of all-zeros (i.e. "0000") is proportional to tr(UV^\dagger) for two circuits U and V. So in the case of using the same circuit, we expect probability 1, which is the case when the parameters and therefore the target circuit is transformed appropriately, i.e. to the complex conjugate.

import numpy as np
import qiskit
from qiskit.visualization import plot_histogram
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.providers import aer

def cost_HST(U_target, U_ansatz, num_qubits=2):
    """ Circuit measuring the (global) Hilbert Schmidt Test, 
        i.e. a test to measure the Hilbert Schmidt distance between two unitaries U_target and U_ansatz: trace(U_target U_ansatz^\dagger)
        Note that U_target needs to be complex conjugated!
        see https://quantum-journal.org/papers/q-2019-05-13-140/pdf/ fig. 4
    """

    # Target and Ansatz circuits
    U_target = U_target.to_instruction(label="U_target*") #.to_instruction() is just to have a better view
    U_ansatz = U_ansatz.to_instruction(label="U_ansatz")

    # Construct HST circuit
    qr = QuantumRegister(2*num_qubits)
    cr = ClassicalRegister(2*num_qubits)
    A = [qr[i] for i in range(num_qubits)]
    B = [qr[i] for i in range(num_qubits,num_qubits*2)]

    qc = QuantumCircuit(qr,cr, name="LHST")
    qc.h(A)
    qc.cx(A,B)
    qc.compose(U_ansatz,qubits=A,inplace=True)
    qc.compose(U_target,qubits=B,inplace=True)
    qc.cx(A,B)
    qc.h(A)
    qc.measure(qr,cr)
    return qc

num_qubits = 2
circ = qiskit.circuit.library.TwoLocal(num_qubits = num_qubits, rotation_blocks = ["ry","rz"], entanglement_blocks = ["cx"], entanglement = "full", reps = 1)
num_params = len(circ.parameters)
parameters = np.random.rand(num_params)

conjugation_mask = np.array([(-1)**(i//num_qubits) for i in range(num_params)])
print("conjugation mask: ", conjugation_mask)
HST_circ = cost_HST(U_ansatz = circ.assign_parameters(parameters), U_target=circ.assign_parameters(conjugation_mask * parameters))
print("HST circuit: ", HST_circ)

backend = qiskit.providers.aer.backends.aer_simulator.AerSimulator()
plot_histogram(qiskit.execute(HST_circ, backend).result().get_counts())

Output:

conjugation mask:  [ 1  1 -1 -1  1  1 -1 -1]
       ┌───┐          ┌───────────┐      ┌───┐        ┌─┐   
q40_0: ┤ H ├──■───────┤0          ├───■──┤ H ├────────┤M├───
       ├───┤  │       │  U_ansatz │   │  └───┘┌───┐   └╥┘┌─┐
q40_1: ┤ H ├──┼────■──┤1          ├───┼────■──┤ H ├────╫─┤M├
       └───┘┌─┴─┐  │  ├───────────┴┐┌─┴─┐  │  └┬─┬┘    ║ └╥┘
q40_2: ─────┤ X ├──┼──┤0           ├┤ X ├──┼───┤M├─────╫──╫─
            └───┘┌─┴─┐│  U_target* │└───┘┌─┴─┐ └╥┘ ┌─┐ ║  ║ 
q40_3: ──────────┤ X ├┤1           ├─────┤ X ├──╫──┤M├─╫──╫─
                 └───┘└────────────┘     └───┘  ║  └╥┘ ║  ║ 
 c2: 4/═════════════════════════════════════════╩═══╩══╩══╩═
                                                2   3  0  1 

and probability p("0000")=1

In case asking for a general method in QuantumCircuit is too much to ask, one could at least include it for TwoLocal circuits, which should we rather straight-forward in a similar fashion as in the example above. I would be happy to assist.

Cryoris commented 2 years ago

That's an interesting feature! Are you aware of any other use-cases where the complex conjugate might be useful?

If we were to implement this, we could probably do this analogously to the inverse: add complex_conjugate methods to the gates and then delegate QuantumCircuit.complex_conjugate to the gates. Technically, I don't think that would be a lot of effort and if you need this feature now, @Qottmann, you could also monkey-patch these methods onto the circuit and the gates you need 🙂

Qottmann commented 2 years ago

Maybe some quantum-info related stuff, otherwise I am not aware of any other use-cases but I'd say this is a pretty big one.

That sounds like exactly the behavior I was looking for!

For the moment though, my workaround above seems to work just fine as well.

ShellyGarion commented 2 years ago

If the QuantumCircuit is small (or of a special type like Clifford) then one can convert it into and Operator and use: https://qiskit.org/documentation/stubs/qiskit.quantum_info.Operator.conjugate.html