Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error running dynamic circuits on IBMQ #11917

Open
prakharb10 opened this issue Feb 5, 2024 · 12 comments
Open

Error running dynamic circuits on IBMQ #11917

prakharb10 opened this issue Feb 5, 2024 · 12 comments
Labels
affects extended support This issue (also) affects extended support bug Something isn't working

Comments

@prakharb10
Copy link
Contributor

Environment

  • Qiskit version: 0.45.1
  • Python version: 3.11
  • Operating system: macOS Sonoma 14.3

What is happening?

I have a dense circuit (for an error-correcting code) with classical control flow instructions. The circuit runs on the simulator but fails on real device with the error - ERROR Failed to execute program: "'ClassicalRegister(6, 'measure_bit')' is not defined in the current context".

How can we reproduce the issue?

The qpy format of the circuit is attached below.

fq_flag.qpy.zip

I'm happy to provide further details here or on Slack.

What should happen?

The circuit should successfully run on the hardware.

Any suggestions?

The only reference I could find for the error message is

raise KeyError(f"'{variable}' is not defined in the current context")

which indicates an error in exporting the circuit to QASM3 for the backend.


I ran into other issues also when building the circuit initially. Some clarification on this would be appreciated:

  • Are multiple classical registers not supported if you use control flow instructions?
    Initially, I had different classical registers for easy reference (syndrome, ancilla, flag, etc.) and if and switch on these separately. Running it on the simulator failed with the message Cannot convert condition in circuit with multiple classical registers to instruction. It also failed on a real device (with a different message).
  • Are switch statements supported on the backend? It is not listed in the feature table.
@prakharb10 prakharb10 added the bug Something isn't working label Feb 5, 2024
@1ucian0
Copy link
Member

1ucian0 commented Feb 28, 2024

Are switch statements supported on the backend? It is not listed in the feature table.

Maybe that's the problem? I'm moving this to qiskit-ibm-runtime. Consider updating to 0.46/1.0 and extend on your example.

@1ucian0 1ucian0 transferred this issue from Qiskit/qiskit Feb 28, 2024
@prakharb10
Copy link
Contributor Author

prakharb10 commented Feb 28, 2024

Hi @1ucian0
I'm unsure if the runtime repo is the right place for this since, on the surface, the trouble looks like a part of qiskit.

Maybe that's the problem?

I did try replacing the switch statement with if, but the issue persists.

Consider updating to 0.46/1.0 and extend on your example.

The issue is present in Qiskit 1.0

Also, the issue happens with both qiskit-ibm-provider and qiskit-ibm-runtime (which doesn't support transpiling dynamic circuits out of the box - Qiskit/qiskit-ibm-runtime#1253).

@prakharb10
Copy link
Contributor Author

Patrick, from the IBM Quantum Support Team, shared the following concise code snippets, which raise the same error:

qr = QuantumRegister(4, "q")
cr = ClassicalRegister(3, "cr")
qc = QuantumCircuit(qr, cr)

with qc.if_test((cr[1], 1)) as _else:
    qc.x(0, label="X1111i").c_if(cr, 4)
with _else:
   qr1 = QuantumRegister(2, "qr1")
   cr1 = ClassicalRegister(2, "cr1")
   inst = QuantumCircuit(2, 2, name='t').to_instruction()
   qc.append(inst, [qr[0], qr[1]], [cr[0], cr[1]])
# qc.draw('mpl') 
qc.draw(style={"showindex": True})
from qiskit.circuit import QuantumCircuit, Qubit, Clbit
q_bits = [Qubit(), Qubit(), Qubit()]
c_bits = ClassicalRegister(2,'cr')
qc = QuantumCircuit(q_bits, c_bits)

with qc.if_test((c_bits[1], 0)) as else_:
qc.x(0, label="pat").c_if(c_bits, 4)
with else_:
inst = QuantumCircuit(1, 1, name='t').to_instruction()
qc.append(inst, [0],[1])

@1ucian0
Copy link
Member

1ucian0 commented Feb 28, 2024

I had to readjust the snippets, but they both worked when I ran them on qiskit 1.0.1 and qiskit 0.46.0:

from qiskit.circuit import QuantumCircuit, ClassicalRegister, QuantumRegister

q_bits = QuantumRegister(2, 'qr')
c_bits = ClassicalRegister(2,'cr')
qc = QuantumCircuit(q_bits, c_bits)

with qc.if_test((c_bits[1], 0)) as else_:
    qc.x(0, label="pat").c_if(c_bits, 4)
with else_:
    inst = QuantumCircuit(1, 1, name='t').to_instruction()
qc.append(inst, [0],[1])
from qiskit.circuit import QuantumCircuit, ClassicalRegister, QuantumRegister

qr = QuantumRegister(4, "q")
cr = ClassicalRegister(3, "cr")
qc = QuantumCircuit(qr, cr)

with qc.if_test((cr[1], 1)) as _else:
    qc.x(0, label="X1111i").c_if(cr, 4)
with _else:
   qr1 = QuantumRegister(2, "qr1")
   cr1 = ClassicalRegister(2, "cr1")
   inst = QuantumCircuit(2, 2, name='t').to_instruction()
   qc.append(inst, [qr[0], qr[1]], [cr[0], cr[1]])
qc.draw(style={"showindex": True})   # or qc.draw('mpl')

@prakharb10
Copy link
Contributor Author

but they both worked when I ran them on qiskit 1.0.1 and qiskit 0.46.0

Could you clarify if by "running" you mean the circuit builds or that it runs on the hardware? The former always worked, and the latter is where the issue pops up.

@jakelishman
Copy link
Member

jakelishman commented Feb 29, 2024

@prakharb10: apologies for the slow reply from us. It's no excuse, this issue just happened to come in right in the middle of the Qiskit 1.0 final release push, and I think it got missed.

I can reproduce your issue when running on our hardware with this most minimal reproducer:

from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister

qr = QuantumRegister(27, "q")
cr = ClassicalRegister(3, "cr")
qc = QuantumCircuit(qr, cr)

with qc.if_test((cr[1], 1)):
    with qc.if_test((cr, 4)):
        qc.x(0)

That said, I got that reproducer from one of our internal support team, not from your QPY file, and then minimised it myself, so it might not be exactly the same problem you're seeing.

With my minimal reproducer, the problem seems to enter the circuit in our internal code that prepares the circuits to run. That said, I think the actual root cause might be the "nested" issue I was foreseeing in this comment on Qiskit #10108 (comment) rearing its head (but I'm not certain).

In your QPY file, it looks like you're using a lot of nested classical instructions that act on classical data, with a lot of nested conditionals that act on registers, so the second part of that definitely appears to be an effect of what I mentioned in #10108 (comment), which is triggered by our internal code.

We're looking into how we reach that error state internally, but for right now, the status is that nested control-flow with register conditions will fail with this error.

It will look very ugly, but as a very temporary workaround, if you can write your conditions in terms of bitwise expressions, you might be able to get something working successfully. Looking at your QPY file, that might be quite tricky (and certainly I don't love that I need to make this suggestion), but here I've written a function that converts conditions on registers to bitwise conditions. If you replace all (register, value) tuples in your if_test calls with condition_to_expr(register, value), I think you should get something working:

from qiskit.circuit.classical import expr

def condition_to_expr(reg, value):
    def bit_compare(bit, value):
        return expr.lift(bit) if value else expr.logic_not(bit)

    new_condition = bit_compare(reg[0], value & 1)
    value >>= 1
    for bit in reg[1:]:
        new_condition = expr.logic_and(new_condition, bit_compare(bit, value & 1))
        value >>= 1
    return new_condition

So as an example, I modified my example above to this:

from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister

qr = QuantumRegister(27, "q")
cr = ClassicalRegister(3, "cr")
qc = QuantumCircuit(qr, cr)

with qc.if_test(condition_to_expr(cr, 1)):
    with qc.if_test(condition_to_expr(cr, 4)):
        qc.x(0)

and that executed successfully on hardware.

@jakelishman
Copy link
Member

jakelishman commented Feb 29, 2024

Luciano: can you transfer this back to Qiskit for now, because at a minimum I think there's issues we need to solve on our side, and there's a separate tracking issue on the internal code base as well. (I don't have write on this repo, so can't transfer it back.)

(edit: I also just noticed that you're using the base of the same reproducer I was haha.)

@mtreinish mtreinish transferred this issue from Qiskit/qiskit-ibm-runtime Feb 29, 2024
@prakharb10
Copy link
Contributor Author

Thank you for highlighting the root cause, @jakelishman. The fact that you could foresee this brings a degree of comfort, in some sense.

The workaround will undoubtedly help when running shallow circuits with nested control-flow instructions. For now, I will hold off on using it for the initial dense circuit. I'm already doing a "bit shift" for the current circuit in parts where I only want to condition on a subset of the classical register, e.g., bits 2-4. I ran into another error when using conditionals on multiple classical registers (details in the footer of the original comment).

I don't know if you already have an agreed design for tackling this, but please let me know if I can help with the implementation or any other way.

@jakelishman
Copy link
Member

Oh, I missed those comments at the bottom, sorry. In general, you can't wrap up conditions on registers into an Instruction (which is what happens if you try to QuantumCircuit.append with a QuantumCircuit argument); the Instruction data model is not designed to close over classical registers at all, so any places where it works are unexpected (and I'd take care with).

switch is supported on all IBM backends that support if. The IBM hardware requires (or at least used to require) a default branch in the switch statement, whether or not it was reachable, in case you're running into problems.

@prakharb10
Copy link
Contributor Author

(which is what happens if you try to QuantumCircuit.append with a QuantumCircuit argument)

I ran into dissonating behavior between QuantumCircuit.append and QuantumCircuit.compose. Is there a particular scenario when either should be used? I ended up using QuantumCircuit.compose (with inplace=True) throughout.

In general, you can't wrap up conditions on registers into an Instruction

Is that what happens when you try to run such a circuit (one with conditionals on multiple classical registers) on the simulator - the circuit is converted to an instruction?

My methodology was to build parts of the circuit piecewise and then compose them together. I'm wondering if I need to switch to building a single circuit at the start and imperatively add operations to it.

The IBM hardware requires (or at least used to require) a default branch in the switch statement, whether or not it was reachable, in case you're running into problems.

The issue with the nested control-flow instructions is the only message I saw. My initial concern was to check if errors with switch statements might be bubbling up as the context error message. I do not think that is the case after your explanation.

@1ucian0
Copy link
Member

1ucian0 commented Apr 9, 2024

This affects 0.46 and 0.1?

@1ucian0 1ucian0 added the affects extended support This issue (also) affects extended support label Apr 9, 2024
@prakharb10
Copy link
Contributor Author

prakharb10 commented Apr 9, 2024

This affects 0.46 and 0.1?

Did you mean 1.0? Yes, @1ucian0. (last I checked)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
affects extended support This issue (also) affects extended support bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants