Skip to content

Support for qubit loss #2567

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

Merged
merged 28 commits into from
Jul 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
901955f
Support for qubit loss
swernli Jun 26, 2025
8ed42eb
Add support for global qubit loss to affect histogram runs in extension
swernli Jun 27, 2025
9b16914
Fix missing param
swernli Jun 27, 2025
f065c32
Add Python API for qubit loss
swernli Jun 30, 2025
a23f014
Add `IsLossResult` and additional tests
swernli Jul 1, 2025
7039467
Support checked measurement in OpenQASM
swernli Jul 1, 2025
54b1d12
Fix tests
swernli Jul 1, 2025
0c6f031
Add samples to noise notebook
swernli Jul 1, 2025
d9730e2
Fix test spans
swernli Jul 1, 2025
f5eae85
Use special "qdk.inc" in OpenQASM
swernli Jul 1, 2025
9a51edf
Put tests back
swernli Jul 2, 2025
3e3dfef
Add missing check
swernli Jul 2, 2025
f681ace
Update OpenQASM name
swernli Jul 2, 2025
a39c0bb
Split up panics
swernli Jul 2, 2025
6baac27
Trim leftover comment
swernli Jul 2, 2025
bcd8c1e
Use integer for `mresetz_checked`
swernli Jul 2, 2025
6ac35c5
Fix test spans again
swernli Jul 3, 2025
8754405
PR feedback
swernli Jul 3, 2025
b57f497
fix test line numbers again
swernli Jul 3, 2025
d1b6457
Updates for PR feedback
swernli Jul 7, 2025
0f129cf
Add sample of `mresetz_checked` usage to openqasm.ipynb
swernli Jul 7, 2025
22dea57
Adjust how Option is used in lossy sim
swernli Jul 7, 2025
c723681
Merge remote-tracking branch 'origin/main' into swernli/qubit-loss
swernli Jul 7, 2025
4a1a017
Fix formatting
swernli Jul 7, 2025
aafa4bf
Use `val::Result` directly
swernli Jul 8, 2025
bc330eb
Fix test name
swernli Jul 8, 2025
b89e0ef
Fix formatting
swernli Jul 8, 2025
6f54b0a
Doc string cleanup
swernli Jul 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion library/core/qir.qs
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,9 @@ namespace QIR.Runtime {
}
}

export __quantum__rt__qubit_allocate, __quantum__rt__qubit_release, AllocateQubitArray, ReleaseQubitArray;
function __quantum__rt__read_loss(r : Result) : Bool {
body intrinsic;
}

export __quantum__rt__qubit_allocate, __quantum__rt__qubit_release, AllocateQubitArray, ReleaseQubitArray, __quantum__rt__read_loss;
}
33 changes: 31 additions & 2 deletions library/std/src/Std/Diagnostics.qs
Original file line number Diff line number Diff line change
Expand Up @@ -390,10 +390,34 @@ function ConfigurePauliNoise(px : Double, py : Double, pz : Double) : Unit {
}

/// # Summary
/// Applies configured noise to a qubit.
/// Configures qubit loss during simulation.
///
/// # Description
/// This operation applies configured noise to a qubit during simulation. For example,
/// This function configures qubit loss for simulation. The parameter `p` represents
/// the probability of a qubit loss during simulation. If `p` is greater than 0.0, the simulator will mark qubits
/// as lost with the given probability during each operation that acts on them.
/// Qubits that are lost are reset to the |0⟩ state but are not released. Loss is reported when the qubit is measured,
/// and then the qubit is considered "reloaded" and can be used again.
///
/// # Input
/// ## p
/// The probability of a qubit being lost during simulation. Must be between 0.0 and 1.0.
///
/// # Remarks
/// This operation is useful for simulating qubit loss for those modalities where qubit loss is a factor.
/// Note that the value returned from a measurement of a lost qubit is neither `Zero` nor `One`, but rather a special
/// value indicating that the qubit was lost. This value cannot be used in comparisons and will cause a runtime
/// failure if compared to another value.
/// To perform a measurement that includes a check for qubit loss, use the `MResetZChecked` operation.
function ConfigureQubitLoss(p : Double) : Unit {
body intrinsic;
}

/// # Summary
/// Applies configured noise or loss to a qubit.
///
/// # Description
/// This operation applies configured noise and/or loss to a qubit during simulation. For example,
/// if configured noise is a bit-flip noise with 5% probability, the X gate will be applied
/// with 5% probability. If no noise is configured, no noise is applied.
/// This is useful to simulate noise during idle periods. It could also be used to
Expand All @@ -402,6 +426,10 @@ function ConfigurePauliNoise(px : Double, py : Double, pz : Double) : Unit {
/// # Input
/// ## qubit
/// The qubit to which noise is applied.
///
/// # See Also
/// - [Std.Diagnostics.ConfigurePauliNoise](xref:Qdk.Std.Diagnostics.ConfigurePauliNoise)
/// - [Std.Diagnostics.ConfigureQubitLoss](xref:Qdk.Std.Diagnostics.ConfigureQubitLoss)
operation ApplyIdleNoise(qubit : Qubit) : Unit {
body intrinsic;
}
Expand Down Expand Up @@ -445,6 +473,7 @@ export
StartCountingQubits,
StopCountingQubits,
ConfigurePauliNoise,
ConfigureQubitLoss,
ApplyIdleNoise,
BitFlipNoise,
PhaseFlipNoise,
Expand Down
56 changes: 54 additions & 2 deletions library/std/src/Std/Measurement.qs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
import Std.Core.*;
import Std.Intrinsic.*;
import Std.Diagnostics.*;
open QIR.Intrinsic;
import QIR.Intrinsic.*;
import QIR.Runtime.*;

/// # Summary
/// Jointly measures a register of qubits in the Pauli Z basis.
Expand Down Expand Up @@ -160,5 +161,56 @@ operation MeasureInteger(target : Qubit[]) : Int {

number
}
export MeasureAllZ, MeasureEachZ, MResetEachZ, MResetX, MResetY, MResetZ, MeasureInteger;

/// # Summary
/// Performs a single-qubit measurement in the Pauli Z basis, resetting the `target` to the |0⟩ state after the measurement.
/// Additionally, it checks if the measurement result indicates a loss and returns `true` when a loss is detected. If the qubit
/// is lost, the result value will not be `Zero` or `One` and any use of that result in a comparison will cause a runtime failure.
/// This operation is not supported on all hardware targets.
///
/// # Input
/// ## target
/// A single qubit to be measured.
///
/// # Output
/// A tuple containing the measurement result and a Boolean `true` if the result indicates a loss, otherwise `false`.
///
/// # Remarks
/// This operation is useful for detecting qubit loss during execution. During simulation, qubit loss probability can be
/// configured via the `ConfigureQubitLoss` operation. When compiled to QIR, this uses the `__quantum__rt__read_loss` intrinsic,
/// which may not be supported on all hardware targets and could result in compilation errors when submitting to those targets.
///
/// # See also
/// - [Std.Measurement.IsLossResult](xref:Qdk.Std.Measurement.IsLossResult)
/// - [Std.Diagnostics.ConfigureQubitLoss](xref:Qdk.Std.Diagnostics.ConfigureQubitLoss)
operation MResetZChecked(target : Qubit) : (Result, Bool) {
let res = MResetZ(target);
(res, IsLossResult(res))
}

/// # Summary
/// Checks if the measurement result indicates qubit loss. Such measurement results are not `Zero` or `One`
/// and using such a result in comparisons causes a runtime failure.
/// This operation is not supported on all hardware targets.
///
/// # Input
/// ## res
/// The measurement result to check.
///
/// # Output
/// A Boolean value indicating whether the result indicates a loss (`true`) or not (`false`).
///
/// # Remarks
/// This operation is useful for detecting qubit loss during execution. After measurement result from qubit loss
/// cannot be used in a comparison, and any attempt to do so will result in a runtime failure. During simulation, qubit loss
/// probability can be configured via the `ConfigureQubitLoss` operation. When compiled to QIR, this uses the
/// `__quantum__rt__read_loss` intrinsic, which may not be supported on all hardware targets and could result in compilation errors when submitting to those targets.
///
/// # See also
/// - [Std.Measurement.MResetZChecked](xref:Qdk.Std.Measurement.MResetZChecked)
/// - [Std.Diagnostics.ConfigureQubitLoss](xref:Qdk.Std.Diagnostics.ConfigureQubitLoss)
operation IsLossResult(res : Result) : Bool {
__quantum__rt__read_loss(res)
}

export MeasureAllZ, MeasureEachZ, MResetEachZ, MResetX, MResetY, MResetZ, MeasureInteger, MResetZChecked, IsLossResult;
14 changes: 14 additions & 0 deletions library/std/src/Std/OpenQASM/Intrinsic.qs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export rxx, ryy, rzz;
// that Qiskit wont emit correctly.
export dcx, ecr, r, rzx, cs, csdg, sxdg, csx, rccx, c3sqrtx, c3x, rc3x, xx_minus_yy, xx_plus_yy, ccz;

export mresetz_checked;

export __quantum__qis__barrier__body;

import Std.OpenQASM.Angle.Angle;
Expand Down Expand Up @@ -633,6 +635,18 @@ operation ccz(ctrl1 : Qubit, ctrl2 : Qubit, target : Qubit) : Unit is Adj + Ctl
h(target);
}

/// A resetting measurement operation that checks for qubit loss.
/// Returns 0 if the qubit measurement was `Zero`, 1 if it was `One`,
/// and 2 if the measurement indicated qubit loss.
operation mresetz_checked(q : Qubit) : Int {
let (r, b) = Std.Measurement.MResetZChecked(q);
if b {
2
} else {
Std.OpenQASM.Convert.ResultAsInt(r)
}
}

/// The ``BARRIER`` function is used to implement the `barrier` statement in QASM.
/// The `@SimulatableIntrinsic` attribute is used to mark the operation for QIR
/// generation.
Expand Down
75 changes: 69 additions & 6 deletions samples/notebooks/noise.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"# Simulating Pauli noise\n",
"This notebook shows how to run simulations with Pauli noise, such as bit-flip or depolarizing noise.\n",
"# Simulating Pauli noise and Qubit Loss\n",
"This notebook shows how to run simulations with Pauli noise, such as bit-flip or depolarizing noise, as well as qubit loss.\n",
"\n",
"First, make sure prerequisites are available. Packages `qsharp` and `qsharp_widgets` must be already installed."
]
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -31,7 +31,7 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": null,
"metadata": {
"vscode": {
"languageId": "qsharp"
Expand Down Expand Up @@ -101,7 +101,7 @@
},
{
"cell_type": "code",
"execution_count": 9,
"execution_count": null,
"metadata": {
"vscode": {
"languageId": "qsharp"
Expand Down Expand Up @@ -182,6 +182,69 @@
"result = qsharp.run(\"Cat5()\", 1000, noise=(0.0, 0.1, 0.0))\n",
"display(qsharp_widgets.Histogram(result))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Simulating with Qubit Loss\n",
"\n",
"For some qubit modalities, a qubit may undergo \"loss\" during execution. This means the physical system backing the qubit is unable to be measured or operated on. To test behaviors of an algorithm in the presence of qubit loss, you can use the `qubit_loss` parameter to `qsharp.run` and set the probability of a qubit being lost on each operation. Qubit loss is reported only at measurement time, where a special `Loss` value is returned that is neither `One` nor `Zero`. During simulation, a lost qubit is no longer acted on by gates and remains in the $\\ket{0}$ state until it is measured or reset, at which point the simulation reloads a fresh qubit to support future operations.\n",
"\n",
"In this example, we'll set the qubit loss probability to high 50% to ensure some qubits are lost during simulation:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"result = qsharp.run(\"BellPair()\", 100, qubit_loss=0.5)\n",
"display(qsharp_widgets.Histogram(result))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that the `Loss` value is not usable inside of the simulation, and any comparisons on that value will trigger a runtime failure. To avoid this failure, use `IsLossResult` to check whether the result value corresponds to qubit loss before using it in any branching logic:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"vscode": {
"languageId": "qsharp"
}
},
"outputs": [],
"source": [
"%%qsharp\n",
"\n",
"operation CheckForLoss() : (Bool, Bool) {\n",
" use q = Qubit();\n",
" H(q);\n",
" let res = MResetZ(q);\n",
" if IsLossResult(res) {\n",
" return (true, false);\n",
" } elif res == One {\n",
" return (false, true);\n",
" } else {\n",
" return (false, false);\n",
" }\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"qsharp_widgets.Histogram(qsharp.run(\"CheckForLoss()\", 100, qubit_loss=0.5))"
]
}
],
"metadata": {
Expand All @@ -200,7 +263,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.9"
"version": "3.11.13"
}
},
"nbformat": 4,
Expand Down
66 changes: 64 additions & 2 deletions samples/notebooks/openqasm.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -333,11 +333,73 @@
"\n",
"print(compile(parameterized_program, 1.57))"
]
},
{
"cell_type": "markdown",
"id": "31d20b6c",
"metadata": {},
"source": [
"When running an OpenQASM program in simulation with qubit loss, additional `Result.Loss` values may be returned that indicate the measured qubit was lost during execution:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "168c3226",
"metadata": {},
"outputs": [],
"source": [
"init()\n",
"\n",
"source = \"\"\"\n",
"include \"stdgates.inc\";\n",
"bit[2] c;\n",
"qubit[2] q;\n",
"c = measure q;\n",
"\"\"\"\n",
"\n",
"import_openqasm(source, name=\"measure2\")\n",
"\n",
"from qsharp.code import measure2\n",
"\n",
"Histogram(run(measure2, shots=1000, qubit_loss=0.1))"
]
},
{
"cell_type": "markdown",
"id": "4773ac0e",
"metadata": {},
"source": [
"By using the special include `\"qdk.inc\"` you can check for loss at runtime using the `mresetz_checked` function. It returns an integer with two bits to indicate whether or not loss has occurred, such that `0` or `1` correspond to the qubit measurement and `2` corresponds to loss:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "02a97625",
"metadata": {},
"outputs": [],
"source": [
"source = \"\"\"\n",
"include \"stdgates.inc\";\n",
"include \"qdk.inc\";\n",
"qubit q;\n",
"output int res;\n",
"h q;\n",
"res = mresetz_checked(q);\n",
"\"\"\"\n",
"\n",
"import_openqasm(source, name=\"mresetz_checked_example\")\n",
"\n",
"from qsharp.code import mresetz_checked_example\n",
"\n",
"Histogram(run(mresetz_checked_example, shots=1000, qubit_loss=0.1))\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
Expand All @@ -351,7 +413,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.3"
"version": "3.11.13"
}
},
"nbformat": 4,
Expand Down
8 changes: 8 additions & 0 deletions source/compiler/qsc/src/interpret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -578,11 +578,15 @@ impl Interpreter {
callable: Value,
args: Value,
noise: Option<PauliNoise>,
qubit_loss: Option<f64>,
) -> InterpretResult {
let mut sim = match noise {
Some(noise) => SparseSim::new_with_noise(&noise),
None => SparseSim::new(),
};
if let Some(loss) = qubit_loss {
sim.set_loss(loss);
}
self.invoke_with_sim(&mut sim, receiver, callable, args)
}

Expand All @@ -593,11 +597,15 @@ impl Interpreter {
receiver: &mut impl Receiver,
expr: Option<&str>,
noise: Option<PauliNoise>,
qubit_loss: Option<f64>,
) -> InterpretResult {
let mut sim = match noise {
Some(noise) => SparseSim::new_with_noise(&noise),
None => SparseSim::new(),
};
if let Some(loss) = qubit_loss {
sim.set_loss(loss);
}
self.run_with_sim(&mut sim, receiver, expr)
}

Expand Down
Loading
Loading