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.03k stars 2.32k forks source link

DAG circuit error #8515

Closed kharazity closed 2 years ago

kharazity commented 2 years ago

Environment

What is happening?

I have compiled a circuit from a list of dictionaries of one and two-qubit unitary matrices. Each list corresponds to a "layer", and each element of the layer is either a one or two-qubit unitary. Each one and two qubit unitary are compiled to a gateset via OneQubitEulerDecomposer()(unitary) and TwoQubitWeylDecomposition(unitary).circuit() respectively into a circuit for the whole layer. Then the entire circuit is built from appending each layer. However, when I attempt to decompose() the final circuit so I can view it's structure I get an error from the DAG saying it received 2x as many wires as it was supposed to, even though qc.num_qubits = 4:

Input In [241], in build_circuit(mpo)
     61     layer_circ = build_layer(layer_dict, Nq)
     62     total_circ.append(layer_circ.decompose(),[qr[i] for i in range(Nq)])
---> 63 return total_circ.decompose()

File ~/.local/lib/python3.10/site-packages/qiskit/circuit/quantumcircuit.py:1569, in QuantumCircuit.decompose(self, gates_to_decompose, reps)
   [1567](file:///home/kharazi/.local/lib/python3.10/site-packages/qiskit/circuit/quantumcircuit.py?line=1566) dag = circuit_to_dag(self)
   [1568](file:///home/kharazi/.local/lib/python3.10/site-packages/qiskit/circuit/quantumcircuit.py?line=1567) for _ in range(reps):
-> [1569](file:///home/kharazi/.local/lib/python3.10/site-packages/qiskit/circuit/quantumcircuit.py?line=1568)     dag = pass_.run(dag)
   [1570](file:///home/kharazi/.local/lib/python3.10/site-packages/qiskit/circuit/quantumcircuit.py?line=1569) return dag_to_circuit(dag)

File ~/.local/lib/python3.10/site-packages/qiskit/transpiler/passes/basis/decompose.py:100, in Decompose.run(self, dag)
     [98](file:///home/kharazi/.local/lib/python3.10/site-packages/qiskit/transpiler/passes/basis/decompose.py?line=97)         else:
     [99](file:///home/kharazi/.local/lib/python3.10/site-packages/qiskit/transpiler/passes/basis/decompose.py?line=98)             decomposition = circuit_to_dag(node.op.definition)
--> [100](file:///home/kharazi/.local/lib/python3.10/site-packages/qiskit/transpiler/passes/basis/decompose.py?line=99)             dag.substitute_node_with_dag(node, decomposition)
    [102](file:///home/kharazi/.local/lib/python3.10/site-packages/qiskit/transpiler/passes/basis/decompose.py?line=101) return dag

File ~/.local/lib/python3.10/site-packages/qiskit/dagcircuit/dagcircuit.py:1150, in DAGCircuit.substitute_node_with_dag(self, node, input_dag, wires)
   [1148](file:///home/kharazi/.local/lib/python3.10/site-packages/qiskit/dagcircuit/dagcircuit.py?line=1147)     wires = in_dag.wires
   [1149](file:///home/kharazi/.local/lib/python3.10/site-packages/qiskit/dagcircuit/dagcircuit.py?line=1148) wire_set = set(wires)
-> [1150](file:///home/kharazi/.local/lib/python3.10/site-packages/qiskit/dagcircuit/dagcircuit.py?line=1149) self._check_wires_list(wires, node)
   [1152](file:///home/kharazi/.local/lib/python3.10/site-packages/qiskit/dagcircuit/dagcircuit.py?line=1151) # Create a proxy wire_map to identify fragments and duplicates
   [1153](file:///home/kharazi/.local/lib/python3.10/site-packages/qiskit/dagcircuit/dagcircuit.py?line=1152) # and determine what registers need to be added to self
   [1154](file:///home/kharazi/.local/lib/python3.10/site-packages/qiskit/dagcircuit/dagcircuit.py?line=1153) add_qregs = self._check_edgemap_registers(wires, in_dag.qregs.values())

File ~/.local/lib/python3.10/site-packages/qiskit/dagcircuit/dagcircuit.py:961, in DAGCircuit._check_wires_list(self, wires, node)
    [958](file:///home/kharazi/.local/lib/python3.10/site-packages/qiskit/dagcircuit/dagcircuit.py?line=957)     wire_tot += node.op.condition[0].size
    [960](file:///home/kharazi/.local/lib/python3.10/site-packages/qiskit/dagcircuit/dagcircuit.py?line=959) if len(wires) != wire_tot:
--> [961](file:///home/kharazi/.local/lib/python3.10/site-packages/qiskit/dagcircuit/dagcircuit.py?line=960)     raise DAGCircuitError("expected %d wires, got %d" % (wire_tot, len(wires)))

DAGCircuitError: 'expected 4 wires, got 8'

How can we reproduce the issue?

mpo_data.zip

#unzip the mpo_data.zip file and you'll have a .npy file
import pickle
import qiskit as qk
from qiskit.quantum_info.synthesis import two_qubit_decompose, one_qubit_decompose
from qiskit.quantum_info import Operator
from qiskit import QuantumCircuit
from qiskit.compiler import transpile

def closest_unitary(A):
    """ Calculate the unitary matrix U that is closest with respect to the
        operator norm distance to the general matrix A.

        Return U as a numpy matrix.
    """
    V, __, Wh = np.linalg.svd(A)
    U = np.matrix(V.dot(Wh))
    return U

def build_unitary_layers(mpo):
    layer_list = []

    for L in range(len(mpo)):
        unitary_dict = dict()
        for site in range(1,len(mpo[L])+1):
            Op = mpo[L][-site]
            shape = np.shape(Op)
            U=np.reshape(Op,(shape[0]*shape[1], shape[0]*shape[1]))
            n_qubits = shape[0]
            if n_qubits > 1:
                qubits = (site-1, site)
            else:
                qubits = (site-1,)
            unitary_dict[qubits] = U
        layer_list.append(unitary_dict)
    return layer_list

def decompose_unitary(unitary):
    unitary = closest_unitary(unitary)
    if unitary.shape[0] == 4:
        circ = two_qubit_decompose.TwoQubitWeylDecomposition(unitary).circuit()
    else:
        circ = one_qubit_decompose.OneQubitEulerDecomposer()(unitary)
    return circ

def build_layer(unitary_layer, Nq):
    qc = QuantumCircuit(Nq,Nq)
    qr = qc.qregs[0]
    for key in unitary_layer.keys():
        unitary = unitary_layer[key]
        qubits = list(key)
        U_circ = decompose_unitary(unitary)
        qc.append(U_circ.to_instruction(),[qr[i] for i in qubits])
    qc = transpile(qc,optimization_level=3)
    return qc

def build_circuit(mpo):
    layer_list = build_unitary_layers(mpo)
    Nq = len(mpo[0])
    total_circ = QuantumCircuit(Nq,Nq)
    qr = qc.qregs[0]
    for layer_dict in layer_list:
        layer_circ = build_layer(layer_dict, Nq)
        total_circ.append(layer_circ.decompose(),[qr[i] for i in range(Nq)])
    return total_circ.decompose()

mpo = np.load('mpo_data.npy',allow_pickle=True)
qc = build_circuit(mpo)

What should happen?

It should ideally just compile the circuit to a more optimized form, or at the very least spit out the one and two-qubit representation of the system.

Any suggestions?

No response

kharazity commented 2 years ago

Also, it would be really nice, if one could also control how the unitary layers get "black boxed" when printing the circuit diagram. For example, the current output for a 4 layer 4 qubit circuit is: b613071f-9d31-4fc2-b5fe-110d654bc92a But each one of those 4 qubit layers are built of one and two qubit unitaries, how can I have it display the black-boxed one and 2 qubit unitaries U_circ as found in build_layer instead of the large black box over the entire "layer" formed at build_circuit? It's confusing because it seems like decompose() ought to do this, but it doesn't display the circuit, just the black box.

alexanderivrii commented 2 years ago

Hi @kharazity. I have taken a brief look at your code, without really trying to understand what it does.

Each layer_circ, created by layer_circ = build_layer(layer_dict, Nq), is a subcircuit with 4 quantum and 4 classical registers. The total circuit total_circ is also a quantum circuit with 4 quantum and 4 classical registers. So you want to change the build_circuit to something like this:

def build_circuit(mpo):
    layer_list = build_unitary_layers(mpo)
    Nq = len(mpo[0])
    total_circ = QuantumCircuit(Nq, Nq)
    qr = total_circ.qregs[0]
    cr = total_circ.cregs[0]
    for layer_dict in layer_list:
        layer_circ = build_layer(layer_dict, Nq)
        total_circ.append(layer_circ.decompose(), [qr[i] for i in range(Nq)], [cr[i] for i in range(Nq)])
    return total_circ.decompose()

Note that append takes both the list of quantum registers and the list of classical registers. Then everything works fine.

I actually did not understand why you need classical registers at all, especially for layer_circ. So another possible fix is to let qc in build_layer be defined as qc = QuantumCircuit(Nq) (with your original build_circuit).

Hope this helps.

alexanderivrii commented 2 years ago

I also did not understand why you call decompose on layer circuits, especially in view of your second question. Try this:

def build_layer(unitary_layer, Nq):
    qc = QuantumCircuit(Nq)
    qr = qc.qregs[0]
    for key in unitary_layer.keys():
        unitary = unitary_layer[key]
        qubits = list(key)
        U_circ = decompose_unitary(unitary)
        qc.append(U_circ, [qr[i] for i in qubits])
    qc = transpile(qc,optimization_level=3)
    return qc

def build_circuit(mpo):
    layer_list = build_unitary_layers(mpo)
    Nq = len(mpo[0])
    total_circ = QuantumCircuit(Nq, Nq)
    qr = total_circ.qregs[0]
    for layer_dict in layer_list:
        layer_circ = build_layer(layer_dict, Nq)
        total_circ.append(layer_circ, [qr[i] for i in range(Nq)])
    return total_circ

if __name__ == "__main__":
    mpo = np.load('mpo_data.npy',allow_pickle=True)

    qc = build_circuit(mpo)
    print(qc)
    print(qc.decompose())
    print(qc.decompose().decompose())
alexanderivrii commented 2 years ago

Hmm, also appending transpiled subcircuits seems strange (as transpilation may change the order of the wires), so you probably also want to call transpile only on the final circuit.

kharazity commented 2 years ago

Hey @alexanderivrii thanks so much! I'm a bit of a qiskit newbie, these were very helpful suggestions and now the program outputs as desired.

Follow up question, is there any advantage to "local" circuit optimization prior to "global" circuit optimization? I.e. is it worth attempting to perform a qubit order preserving transformation on each layer to simplify the layer before then running a full gate-level optimization on the entire circuit?

alexanderivrii commented 2 years ago

@kharazity, I don't think there is any advantage doing "local" optimizations first.

alexanderivrii commented 2 years ago

Since this is not a bug, we can close this issue. @kharazity, please do not hesitate to ask more questions.