Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 source/compiler/qsc_eval/src/backend/noise_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{
use expect_test::{Expect, expect};
use num_bigint::BigUint;
use num_complex::Complex;
use qdk_simulators::noise_config::{NoiseConfig, NoiseTable, encode_pauli};
use qdk_simulators::noise_config::{LossPolicy, NoiseConfig, NoiseTable, encode_pauli};
use std::fmt::Write;

#[test]
Expand Down Expand Up @@ -258,6 +258,7 @@ fn noise_config_with_single_qubit_fault(
qubits: 1,
pauli_strings: vec![encode_pauli(pauli)],
probabilities: vec![1.0],
on_loss: LossPolicy::Skip,
};
set_gate(&mut config, table);
config
Expand All @@ -274,6 +275,7 @@ fn noise_config_with_two_qubit_fault(
qubits: 2,
pauli_strings: vec![encode_pauli(pauli)],
probabilities: vec![1.0],
on_loss: LossPolicy::Skip,
};
set_gate(&mut config, table);
config
Expand Down Expand Up @@ -525,6 +527,7 @@ fn noise_config_mz_with_loss() {
let mut config = NoiseConfig::NOISELESS;
config.mz = NoiseTable {
qubits: 1,
on_loss: LossPolicy::Skip,
pauli_strings: vec![encode_pauli("L")],
probabilities: vec![1.0],
};
Expand All @@ -545,6 +548,7 @@ fn noise_config_gate_loss_causes_measurement_loss() {
let mut config = NoiseConfig::NOISELESS;
config.x = NoiseTable {
qubits: 1,
on_loss: LossPolicy::Skip,
pauli_strings: vec![encode_pauli("L")],
probabilities: vec![1.0],
};
Expand Down
20 changes: 20 additions & 0 deletions source/qdk_package/qdk/_native.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,25 @@ class QirInstruction: ...
class IdleNoiseParams:
s_probability: float

class LossPolicy(Enum):
"""
Specifies the behavior of a multi-qubit gate when at least one of its
qubit operands is lost.
"""

# If any operand of a gate is lost, skip the gate entirely.
SKIP: int
# If any operand of a gate is lost, propagate the loss to the other operands.
PROPAGATE: int
# For multi-qubit rotations, degrade the unitary to its single-qubit version
# on the surviving operand (e.g. rxx -> rx). Falls back to SKIP for gates with
# no single-qubit reduction (cx, cy, cz, swap, and single-qubit gates).
DEGRADE: int
# Skip the gate and instead apply an S adjoint to each surviving operand.
RESIDUAL_S_DAGGER: int
# Apply the unitary anyway, ignoring the loss.
APPLY_ANYWAY: int

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this really 'ignore the loss'? I though this policy was specifically for SWAP, where its often just a relabelling of qubits. In that case, doesn't the loss get swapped as well? (i.e. if before the gate qubit A was lost and B was in state $\alpha$, then after the swap A will be in state $\alpha$ and qubit B will be lost)


class NoiseTable:
# Deprecated. Setting `loss` distributes the per-qubit loss probability
# across the correlated loss fault strings ('L' for a single-qubit
Expand All @@ -876,6 +895,7 @@ class NoiseTable:
# `loss` reconstructs that per-qubit probability. Prefer setting the loss
# fault strings directly via `set_pauli_noise`.
loss: float
on_loss: LossPolicy

def __init__(self, num_qubits: int):
"""
Expand Down
7 changes: 6 additions & 1 deletion source/qdk_package/qdk/simulation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
to individual gate intrinsics to model depolarizing, bit-flip, phase-flip,
or correlated noise channels.

- :class:`~qdk.simulation.LossPolicy` — selects how a gate behaves when one of
its qubit operands is lost. Assign it to a noise table's ``on_loss`` attribute
(e.g. ``noise.cx.on_loss = LossPolicy.SKIP``).

- :func:`~qdk.simulation.run_qir` — simulates QIR as given in one of
three backend simulators: clifford, gpu or cpu.

Expand All @@ -26,7 +30,7 @@
"""

from .._device._atom import NeutralAtomDevice
from ._simulation import NoiseConfig, run_qir
from ._simulation import NoiseConfig, LossPolicy, run_qir
from ._noisy_simulator import (
NoisySimulatorError,
DensityMatrixSimulator,
Expand All @@ -40,6 +44,7 @@
__all__ = [
"NeutralAtomDevice",
"NoiseConfig",
"LossPolicy",
"run_qir",
"NoisySimulatorError",
"Operation",
Expand Down
1 change: 1 addition & 0 deletions source/qdk_package/qdk/simulation/_simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
run_cpu_adaptive,
run_cpu_full_state,
NoiseConfig,
LossPolicy,
GpuContext,
try_create_gpu_adapter,
Result,
Expand Down
4 changes: 3 additions & 1 deletion source/qdk_package/src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::{
},
noisy_simulator::register_noisy_simulator_submodule,
qir_simulation::{
IdleNoiseParams, NoiseConfig, NoiseTable, QirInstruction, QirInstructionId,
IdleNoiseParams, LossPolicy, NoiseConfig, NoiseTable, QirInstruction, QirInstructionId,
cpu_simulators::{
run_clifford, run_clifford_adaptive, run_cpu_adaptive, run_cpu_full_state,
},
Expand Down Expand Up @@ -104,6 +104,7 @@ fn verify_classes_are_sendable() {
is_send::<NoiseConfig>();
is_send::<NoiseTable>();
is_send::<IdleNoiseParams>();
is_send::<LossPolicy>();
}

#[pymodule]
Expand Down Expand Up @@ -133,6 +134,7 @@ fn _native<'a>(py: Python<'a>, m: &Bound<'a, PyModule>) -> PyResult<()> {
m.add_class::<NoiseConfig>()?;
m.add_class::<NoiseTable>()?;
m.add_class::<IdleNoiseParams>()?;
m.add_class::<LossPolicy>()?;
m.add_function(wrap_pyfunction!(physical_estimates, m)?)?;
m.add_function(wrap_pyfunction!(run_clifford, m)?)?;
m.add_function(wrap_pyfunction!(try_create_gpu_adapter, m)?)?;
Expand Down
70 changes: 64 additions & 6 deletions source/qdk_package/src/qir_simulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::qir_simulation::correlated_noise::parse_noise_table;

use num_traits::{Float, Unsigned};
use pyo3::{
Bound, FromPyObject, Py, PyRef, PyResult, Python,
Bound, FromPyObject, Py, PyAny, PyRef, PyResult, Python,
exceptions::{PyAttributeError, PyKeyError, PyTypeError, PyValueError},
pybacked::PyBackedStr,
pyclass, pymethods,
Expand Down Expand Up @@ -88,6 +88,47 @@ pub enum QirInstruction {
),
}

/// Specifies the behavior of a multi-qubit gate when at least one of its
/// qubit operands is lost. Mirrors [`qdk_simulators::noise_config::LossPolicy`].
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[pyclass(eq, eq_int, from_py_object, module = "qdk._native")]
pub enum LossPolicy {
#[pyo3(name = "SKIP")]
Skip = 0,
#[pyo3(name = "PROPAGATE")]
Propagate = 1,
#[pyo3(name = "DEGRADE")]
Degrade = 2,
#[pyo3(name = "RESIDUAL_S_DAGGER")]
ResidualSDagger = 3,
#[pyo3(name = "APPLY_ANYWAY")]
ApplyAnyway = 4,
}

impl From<LossPolicy> for qdk_simulators::noise_config::LossPolicy {
fn from(value: LossPolicy) -> Self {
match value {
LossPolicy::Skip => Self::Skip,
LossPolicy::Propagate => Self::Propagate,
LossPolicy::Degrade => Self::Degrade,
LossPolicy::ResidualSDagger => Self::ResidualSDagger,
LossPolicy::ApplyAnyway => Self::ApplyAnyway,
}
}
}

impl From<qdk_simulators::noise_config::LossPolicy> for LossPolicy {
fn from(value: qdk_simulators::noise_config::LossPolicy) -> Self {
match value {
qdk_simulators::noise_config::LossPolicy::Skip => Self::Skip,
qdk_simulators::noise_config::LossPolicy::Propagate => Self::Propagate,
qdk_simulators::noise_config::LossPolicy::Degrade => Self::Degrade,
qdk_simulators::noise_config::LossPolicy::ResidualSDagger => Self::ResidualSDagger,
qdk_simulators::noise_config::LossPolicy::ApplyAnyway => Self::ApplyAnyway,
}
}
}

#[derive(Debug)]
#[pyclass(module = "qdk._native")]
pub struct NoiseConfig {
Expand Down Expand Up @@ -357,6 +398,8 @@ impl From<qdk_simulators::noise_config::IdleNoiseParams> for IdleNoiseParams {
#[pyclass(from_py_object, module = "qdk._native")]
pub struct NoiseTable {
qubits: u32,
#[pyo3(get)]
pub on_loss: LossPolicy,
pauli_noise: FxHashMap<PauliAndLossString, Probability>,
}

Expand Down Expand Up @@ -522,6 +565,7 @@ impl NoiseTable {
NoiseTable {
qubits: num_qubits,
pauli_noise: FxHashMap::default(),
on_loss: LossPolicy::Skip,
}
}

Expand Down Expand Up @@ -553,11 +597,22 @@ E.g.: `noise_config.cz.IL`"
///
/// for arbitrary pauli fields. Setting an element that was
/// previously set overrides that entry with the new value.
fn __setattr__(&mut self, name: &str, value: Probability) -> PyResult<()> {
if name == "loss" {
self.set_loss(value)
} else {
self.set_pauli_noise_elt(&name.to_uppercase(), value)
///
/// The `on_loss` attribute is special-cased to accept a `LossPolicy`.
fn __setattr__(&mut self, name: &str, value: &Bound<'_, PyAny>) -> PyResult<()> {
match name {
"on_loss" => {
if self.qubits < 2 {
Err(PyAttributeError::new_err(
"Loss policies only apply to multi-qubit gates.".to_string(),
))
} else {
self.on_loss = value.extract::<LossPolicy>()?;
Ok(())
}
}
"loss" => self.set_loss(value.extract::<Probability>()?),
_ => self.set_pauli_noise_elt(&name.to_uppercase(), value.extract::<Probability>()?),
}
}

Expand Down Expand Up @@ -649,6 +704,7 @@ impl<T: Float> From<NoiseTable> for qdk_simulators::noise_config::NoiseTable<T>
qubits: value.qubits,
pauli_strings,
probabilities,
on_loss: value.on_loss.into(),
}
}
}
Expand All @@ -667,6 +723,7 @@ fn from_noise_table_ref<T: Float>(
qubits: value.qubits,
pauli_strings,
probabilities,
on_loss: value.on_loss.into(),
}
}

Expand All @@ -686,6 +743,7 @@ impl<T: Float> From<qdk_simulators::noise_config::NoiseTable<T>> for NoiseTable
NoiseTable {
qubits: value.qubits,
pauli_noise,
on_loss: value.on_loss.into(),
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion source/qdk_package/src/qir_simulation/correlated_noise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use rustc_hash::FxHashMap;
use std::fmt;
use std::str::FromStr;

use crate::qir_simulation::NoiseTable;
use crate::qir_simulation::{LossPolicy, NoiseTable};

/// Errors that can occur while parsing a noise-table CSV.
#[derive(Debug)]
Expand Down Expand Up @@ -84,6 +84,7 @@ pub fn parse_noise_table(contents: &str) -> Result<NoiseTable, ParseError> {
return Ok(NoiseTable {
qubits: qubits.unwrap_or(0),
pauli_noise,
on_loss: LossPolicy::Skip,
});
}

Expand Down Expand Up @@ -156,6 +157,7 @@ pub fn parse_noise_table(contents: &str) -> Result<NoiseTable, ParseError> {
Ok(NoiseTable {
qubits,
pauli_noise,
on_loss: LossPolicy::Skip,
})
}

Expand Down
Loading
Loading