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 @@ -259,6 +259,7 @@ fn noise_config_with_single_qubit_fault(
pauli_strings: vec![encode_pauli(pauli)],
probabilities: vec![1.0],
loss: 0.0,
on_loss: LossPolicy::Skip,
};
set_gate(&mut config, table);
config
Expand All @@ -276,6 +277,7 @@ fn noise_config_with_two_qubit_fault(
pauli_strings: vec![encode_pauli(pauli)],
probabilities: vec![1.0],
loss: 0.0,
on_loss: LossPolicy::Skip,
};
set_gate(&mut config, table);
config
Expand Down Expand Up @@ -530,6 +532,7 @@ fn noise_config_mz_with_loss() {
pauli_strings: vec![],
probabilities: vec![],
loss: 1.0,
on_loss: LossPolicy::Skip,
};
let mut sim = SparseSim::new_with_noise_config(config.into());
let q = sim.qubit_allocate().expect("sparse simulator is infinite");
Expand All @@ -551,6 +554,7 @@ fn noise_config_gate_loss_causes_measurement_loss() {
pauli_strings: vec![],
probabilities: vec![],
loss: 1.0,
on_loss: LossPolicy::Skip,
};
let mut sim = SparseSim::new_with_noise_config(config.into());
let q = sim.qubit_allocate().expect("sparse simulator is infinite");
Expand Down
19 changes: 19 additions & 0 deletions source/qdk_package/qdk/_native.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -844,8 +844,27 @@ class QirInstruction: ...
class IdleNoiseParams:
s_probability: float

class LossPolicy(Enum):
"""
Specifies the behavior of a 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

class NoiseTable:
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
69 changes: 62 additions & 7 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 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 @@ -360,6 +401,9 @@ pub struct NoiseTable {
pauli_noise: FxHashMap<u64, Probability>,
#[pyo3(get, set)]
pub loss: Probability,
/// The behavior of this gate when at least one of its operands is lost.
#[pyo3(get)]
pub on_loss: LossPolicy,
}

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

Expand Down Expand Up @@ -531,12 +576,19 @@ impl NoiseTable {
///
/// 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.loss = value;
Ok(())
} 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" => {
self.on_loss = value.extract::<LossPolicy>()?;
Ok(())
}
"loss" => {
self.loss = value.extract::<Probability>()?;
Ok(())
}
_ => self.set_pauli_noise_elt(&name.to_uppercase(), value.extract::<Probability>()?),
}
}

Expand Down Expand Up @@ -629,6 +681,7 @@ impl<T: Float> From<NoiseTable> for qdk_simulators::noise_config::NoiseTable<T>
pauli_strings,
probabilities,
loss: generic_float_cast(value.loss),
on_loss: value.on_loss.into(),
}
}
}
Expand All @@ -648,6 +701,7 @@ fn from_noise_table_ref<T: Float>(
pauli_strings,
probabilities,
loss: generic_float_cast(value.loss),
on_loss: value.on_loss.into(),
}
}

Expand All @@ -668,6 +722,7 @@ impl<T: Float> From<qdk_simulators::noise_config::NoiseTable<T>> for NoiseTable
qubits: value.qubits,
pauli_noise,
loss: generic_float_cast(value.loss),
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 @@ -85,6 +85,7 @@ pub fn parse_noise_table(contents: &str) -> Result<NoiseTable, ParseError> {
qubits: qubits.unwrap_or(0),
pauli_noise,
loss: 0.0,
on_loss: LossPolicy::Skip,
});
}

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

Expand Down
Loading
Loading