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.31k stars 2.38k forks source link

Allow gates to be inserted mid QuantumCircuit #4736

Closed knsmith closed 4 years ago

knsmith commented 4 years ago

Unless there is something I am missing, it is difficult to add gates mid-circuit without using private QuantumCircuit variables such as _data. The best work around involves merging two circuits together like in the following example, but it would be great to be able to add new gates to a predefined circuit.

image

Cryoris commented 4 years ago

Technically this is quite straightforward to do, the difficulty lies in how to specify where you want to insert gates? Say you have a circuit looking like

     ┌───┐
q_0: ┤ X ├──■─────────■──
     ├───┤  │  ┌───┐┌─┴─┐
q_1: ┤ H ├──■──┤ H ├┤ X ├
     └───┘┌─┴─┐└───┘└───┘
q_2: ─────┤ X ├──────────
          └───┘

and want to go to

     ┌───┐
q_0: ┤ X ├──■────────────■──
     ├───┤  │  ┌───┐   ┌─┴─┐
q_1: ┤ H ├──■──┤ H ├─X─┤ X ├
     └───┘┌─┴─┐└───┘ │ └───┘
q_2: ─────┤ X ├──────X──────
          └───┘

How would the syntax for this look like?

(Though if we find a way to define this properly I think this would be a great feature!)

knsmith commented 4 years ago

I know that you can index a circuit to return gates and associated registers in the order of the circuit (what inspired the approach above, but here you stitch together two circuits), but it is still not intuitive to insert random gates using this technique. So perhaps in a manner like circuit.gate(qubit).at(index)?

Vismai-Khanderao commented 4 years ago

Though the method of splicing circuits may cause an inconvenience for bigger circuits as the instruction order would need to be found. e.g.

qc.h(qr[0])
qc.h(qr[1])
qc.x(qr[0])
qc.x(qr[1])

Looks the same as

qc.h(qr[0])
qc.x(qr[0])
qc.h(qr[1])
qc.x(qr[1])

So adding a Y gate between the column of H and X by adding qc.y(qr[0]) in line 3 pushing original line 3 and 4 down would work in the first scenario but not the second.

A possible solution to this I was thinking of is:

i is essentially an index number for the to be added gate position on the bit. instruction_index in each bit is a tuple which holds the index numbers to instructions in self._data which attach something to this bit.

Since there are several gates, I have implemented and tested this for the h gate on a branch of mine, I've added examples below

from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit

qr = QuantumRegister(3, 'q')
anc = QuantumRegister(1, 'ancilla')
cr = ClassicalRegister(3, 'c')
qc = QuantumCircuit(qr, anc, cr)

qc.x(anc[0])
qc.h(anc[0])
qc.h(qr[0:3])
qc.cx(qr[0:3], anc[0])
qc.h(qr[0:3])
qc.barrier(qr)
qc.measure(qr, cr)

qc.draw()

           ┌───┐          ┌───┐           ░ ┌─┐      
      q_0: ┤ H ├───────■──┤ H ├───────────░─┤M├──────
           ├───┤       │  └───┘┌───┐      ░ └╥┘┌─┐   
      q_1: ┤ H ├───────┼────■──┤ H ├──────░──╫─┤M├───
           ├───┤       │    │  └───┘┌───┐ ░  ║ └╥┘┌─┐
      q_2: ┤ H ├───────┼────┼────■──┤ H ├─░──╫──╫─┤M├
           ├───┤┌───┐┌─┴─┐┌─┴─┐┌─┴─┐└───┘ ░  ║  ║ └╥┘
ancilla_0: ┤ X ├┤ H ├┤ X ├┤ X ├┤ X ├─────────╫──╫──╫─
           └───┘└───┘└───┘└───┘└───┘         ║  ║  ║ 
      c: 3/══════════════════════════════════╩══╩══╩═
                                             0  1  2 

qc.h(anc[0],i=0)
qc.draw()

           ┌───┐               ┌───┐           ░ ┌─┐      
      q_0: ┤ H ├────────────■──┤ H ├───────────░─┤M├──────
           ├───┤            │  └───┘┌───┐      ░ └╥┘┌─┐   
      q_1: ┤ H ├────────────┼────■──┤ H ├──────░──╫─┤M├───
           ├───┤            │    │  └───┘┌───┐ ░  ║ └╥┘┌─┐
      q_2: ┤ H ├────────────┼────┼────■──┤ H ├─░──╫──╫─┤M├
           ├───┤┌───┐┌───┐┌─┴─┐┌─┴─┐┌─┴─┐└───┘ ░  ║  ║ └╥┘
ancilla_0: ┤ H ├┤ X ├┤ H ├┤ X ├┤ X ├┤ X ├─────────╫──╫──╫─
           └───┘└───┘└───┘└───┘└───┘└───┘         ║  ║  ║ 
      c: 3/═══════════════════════════════════════╩══╩══╩═
                                                  0  1  2 
qc.h(qr[1],i=2)
qc.draw()
           ┌───┐               ┌───┐           ░ ┌─┐      
      q_0: ┤ H ├────────────■──┤ H ├───────────░─┤M├──────
           ├───┤            │  └───┘┌───┐┌───┐ ░ └╥┘┌─┐   
      q_1: ┤ H ├────────────┼────■──┤ H ├┤ H ├─░──╫─┤M├───
           ├───┤            │    │  └───┘├───┤ ░  ║ └╥┘┌─┐
      q_2: ┤ H ├────────────┼────┼────■──┤ H ├─░──╫──╫─┤M├
           ├───┤┌───┐┌───┐┌─┴─┐┌─┴─┐┌─┴─┐└───┘ ░  ║  ║ └╥┘
ancilla_0: ┤ H ├┤ X ├┤ H ├┤ X ├┤ X ├┤ X ├─────────╫──╫──╫─
           └───┘└───┘└───┘└───┘└───┘└───┘         ║  ║  ║ 
      c: 3/═══════════════════════════════════════╩══╩══╩═
                                                  0  1  2 
qc.h(anc[0],i=6)
qc.draw()
           ┌───┐               ┌───┐           ░ ┌─┐      
      q_0: ┤ H ├────────────■──┤ H ├───────────░─┤M├──────
           ├───┤            │  └───┘┌───┐┌───┐ ░ └╥┘┌─┐   
      q_1: ┤ H ├────────────┼────■──┤ H ├┤ H ├─░──╫─┤M├───
           ├───┤            │    │  └───┘├───┤ ░  ║ └╥┘┌─┐
      q_2: ┤ H ├────────────┼────┼────■──┤ H ├─░──╫──╫─┤M├
           ├───┤┌───┐┌───┐┌─┴─┐┌─┴─┐┌─┴─┐├───┤ ░  ║  ║ └╥┘
ancilla_0: ┤ H ├┤ X ├┤ H ├┤ X ├┤ X ├┤ X ├┤ H ├────╫──╫──╫─
           └───┘└───┘└───┘└───┘└───┘└───┘└───┘    ║  ║  ║ 
      c: 3/═══════════════════════════════════════╩══╩══╩═
                                                  0  1  2 

qc.h(qr[2],i=5)
qc.draw()
           ┌───┐               ┌───┐           ░ ┌─┐           
      q_0: ┤ H ├────────────■──┤ H ├───────────░─┤M├───────────
           ├───┤            │  └───┘┌───┐┌───┐ ░ └╥┘┌─┐        
      q_1: ┤ H ├────────────┼────■──┤ H ├┤ H ├─░──╫─┤M├────────
           ├───┤            │    │  └───┘├───┤ ░  ║ └╥┘┌─┐┌───┐
      q_2: ┤ H ├────────────┼────┼────■──┤ H ├─░──╫──╫─┤M├┤ H ├
           ├───┤┌───┐┌───┐┌─┴─┐┌─┴─┐┌─┴─┐├───┤ ░  ║  ║ └╥┘└───┘
ancilla_0: ┤ H ├┤ X ├┤ H ├┤ X ├┤ X ├┤ X ├┤ H ├────╫──╫──╫──────
           └───┘└───┘└───┘└───┘└───┘└───┘└───┘    ║  ║  ║      
      c: 3/═══════════════════════════════════════╩══╩══╩══════
                                                  0  1  2      

i is the user input for which attachment on the bit this gate needs to be put before, or another way of looking at it is if the line gaps between gates are visualised as numbers e.g. (0) -- H -- (1) -- X -- (2) This works by the circuit checking if i is in between other gates on a bit using length of instruction_index, if it is, then self._data is split and this instruction is inserted/'slid' in just before the instruction for the gate in front.

For gates such as cx, an arbitrary choice would have to be made in which either the control or target bit i is given for, though I suggest the control bit for ease of top down viewing.

It may be better to share a link to my branch as three tests which compare bits fail when running make test due to the extra attribute. https://github.com/Qiskit/qiskit-terra/compare/master...Vismai-Khanderao:issue4736branch

Help would greatly be appreciated for testing, and further implementation as I've recently picked up qiskit.

I'd love to discuss further on this.

Update: Made a pull request with aforementioned implementation ~~Also to mention, the three tests which fail are: test_qasm_text test_from_ast_to_dag test_lookahead_swap_maps_measurements~~

Update Below

Vismai-Khanderao commented 4 years ago

I have made a commit to the pull request, I got rid of the added attribute in the Bit class so now the tests do pass and def _append is now reverted to it's original state. The parameter i is now index for clarity.

Change comes through an addition to def append, the add gate instruction is now appended at the end of self._data as before. Since instructions in _data are listed chronologically in the order they are run, the function now loops through self._data and counts the number of gates attached to the qubit in question. Once the number of gates is equal to the index number intended, the instructions are split using an instruction count in the loop and the last instruction is now moved in between, same as the initial commit.

Otherwise, if index is None or is greater than the number of gates attached, then the gate added remains at the end of the qubit.

Functionality is same as mentioned before, this implementation may be slower for large circuits but is neater and more compatible.

@knsmith I did try to implement your method of circuit.gate(qubit).at(index) as it would be a neat solution, but the circuit.gate(qubit) functions return instruction sets, to which the circuit._data is not available to.

knsmith commented 4 years ago

@Vismai-Khanderao neat solution! When I originally proposed circuit.gate(qubit).at(index), I was thinking the index would be the gate index in the entire list of gates for a circuit object. Your implementation, however, is much more elegant and will be easier to use. Does your method allow you to delete gates in place?

Vismai-Khanderao commented 4 years ago

@knsmith Thanks! For now I haven't thought of a delete gate function, though it may be quite simple as the same method that's used to find the gates on a qubit can be used to pop an instruction. When we match the gate, qubit, and it's index on bit we can use the instruction index counted to pop the instruction from _data. @quantumjim did layout the popping method in https://quantumcomputing.stackexchange.com/a/5209, it would be handy for bigger circuits if a function could handle it, maybe qc.delete_gate(qubit(s), gate, qubit_index)?

quantumjim commented 4 years ago

What's all this about ._data being private? Is it different to .data? Is .data getting deprecated?

Vismai-Khanderao commented 4 years ago

@quantumjim .data isn't being deprecated.

For example circuit.h(qr[0]) returns an InstructionSet, the instructions in this instruction set is stored in instruction_set.instructions, these are also a part of circuit._data whose entire instruction list can be retrieved with the getter circuit.data or circuit._data

Making a function def at(--- in InstructionSet would require the passing of the QuantumCircuit into it, i.e. circuit.h(qr[0]).at(circuit, index=1), then only can the circuit instruction list be retrieved in the InstructionSet class with circuit.data or circuit._data and the order can be changed to add a gate mid circuit. (ideally circuit.data should be used outside of QuantumCircuit as PEP 8 standards)

Hence, I proposed the qubit gate index be passed within the gate itself as circuit.h(qr[0], index=1)

knsmith commented 4 years ago

@Vismai-Khanderao I like the idea of being able to insert and delete in the case where you may be running/tuning a circuit in a hybrid quantum classical implementation so that you don't have to rebuild the circuit every time. I agree that your indexing approach for each qubit may be great for that!

Vismai-Khanderao commented 4 years ago

@knsmith that would indeed make things much smoother. I'm making a push to the draft pull request soon, from my initial testing, I have got 2-qubit gate commands such as qc.cx(qr[0:3], anc[0], index=[2,1]) to work, this would puts three CX gates with individual control bits as q_0, q_1, q_2 and the target bit as anc_0 at indexes 2 for the q's and 1 for the ancilla. A circuit error is raised if for example, there is another CX gate at q_0 index 2 and anc_0 index 0, as the instructions cannot be rearranged while maintaining gate orders.

I haven't got it to quite work with 3+ qubit gates such as a CCX gate, and I would be cleaning up code with additional helpers and clearer variable names with future commits.

1ucian0 commented 4 years ago

I'm not sure if this feature worths the effort and the complicated API that might create. What's the problem/use-case to solve?

Creating circuits/gates is kinda easy... editing existing circuits/gates seems like it might create a lot of methods with complex parameters...

Vismai-Khanderao commented 4 years ago

@1ucian0 I agree, I did eventually get it to work with 3+ qubit gates and did simple testing but it's needlessly complicated and since some gates have different internal attributes, e.g barriers, it's difficult to test a robust general solution to all gates. I'll be closing my WIP PR #4809 . It seems easier in most cases to build a new circuit, especially when it's not too large.

1ucian0 commented 4 years ago

@knsmith?

1ucian0 commented 4 years ago

Alternative, a gate can be set as placeholder and be replaced by concrete instances.

I'm closing this as wont fix. If a use-case is not covered by these approaches and you can make a stronger case to justify increasing the API complexity then please reopen.