Open tcNickolas opened 3 months ago
@swernli I can see how the current behavior is by design, and technically, continuing to execute statements after a runtime error is inadvisable -- but this is super common to encounter as you're iterating on code and it's mildly annoying. I think it's simple enough to accommodate.
Is it worth fixing this by just calling sim.qubit_release()
before throwing a runtime failure?
For this specific case of qubits released in a non-zero state, I think that is an excellent solution. We are already in the process of releasing the qubits, and the language makes it hard to continue referring to them to even have a way to clean them up manually, so we should just release unconditionally. Maybe even something like:
let is_zero = qubit_is_zero(qubit);
sim.qubit_release(qubit);
if is_zero {
Ok(Value::unit())
} else {
Err(Error::ReleasedQubitNotZero(qubit, arg_span))
}
It's worth noting that this would fix a very common problem but not all the problems, so code like:
open Microsoft.Quantum.Diagnostics;
operation Demo() : Unit {
use q = Qubit();
X(q);
let _ = 1 / 0;
}
Demo()
Would still leak a qubit each time.
Can we do qubit_release upon any error, as part of error processing?
Can we do qubit_release upon any error, as part of error processing?
There might be mechanisms we can employ, but it's tricky to differentiate between qubits in a contained scope that should be released:
{
use q = Qubit();
X(q);
let _ = 1 / 0;
}
vs qubits from top-level statements that shouldn't be released:
use q = Qubit();
X(q);
let _ = 1 / 0;
The evaluator doesn't do any special tracking of qubits to know which ones are in scope, but rather depends on one of the transformation passes that runs during compilation that turns qubit statements into explicit calls to allocate and release. So for example, the first snippet above with the explicit scope will internally be transformed into:
{
let q = __quantum__rt__qubit_allocate();
X(q);
let _ = 1 / 0;
__quantum__rt__qubit_release(q);
}
That final statement is what performs the release, not any special tracking in the evaluator, and the error short-circuits that statement.
I think the feature suggestion here is essentially "stack unwinding" - making fail
s behave like exceptions do, deallocating resources as the stack gets unwound.
Currently fail
s in Q# don't have exception semantics: they don't unwind the stack, they can't be caught. Rather they're more of an "abort": program simply terminates (it's supposed to, anyway). This works fine in a standalone Q# program. But in a REPL/notebook environment, we get into weird "undefined behavior" territory. You can continue to execute statements, but we're essentially limping along at this point and can't guarantee a consistent program state.
Another hamfisted suggestion I have is to actually abort the program in Python when a fail
occurs: throw away the Q# interpreter and reinitialize it. The downside is you may need to re-run some cells you've previously. The upside is that the state will always be consistent, no limping along.
I think the feature suggestion here is essentially "stack unwinding" - making
fail
s behave like exceptions do, deallocating resources as the stack gets unwound.
Yeah, that's what we'd need, and it would need to be smart enough to know not to deallocate qubits from the top-level/global scope of the notebook.
Another hamfisted suggestion I have is to actually abort the program in Python when a
fail
occurs: throw away the Q# interpreter and reinitialize it. The downside is you may need to re-run some cells you've previously. The upside is that the state will always be consistent, no limping along.
I think from the users perspective this would look awfully similar to a crash, so if we went this route we'd want to have a very clear message that guides them on how to recuperate their state (rerun appropriate notebook cells but don't rerun all if there's job submissions, for example).
Describe the bug
In Jupyter notebook, if simulation ended with runtime exception, allocated qubits stay around. They show up in the next
DumpMachine
outputs, even though there's no way to access them or clear them up.To Reproduce
As you re-run this code, it throws an exception each time, and the number of allocated qubits grows by 1 each time, all of them in |1> state.
Expected behavior
I expect the allocated qubits to not show up in consecutive runs.
System information