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

Noise settings available in Q# #1997

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
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
28 changes: 28 additions & 0 deletions compiler/qsc/src/interpret/circuit_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,34 @@ fn custom_intrinsic_mixed_args() {
assert_eq!(circ.operations.len(), 1);
}

#[test]
fn custom_intrinsic_apply_idle_noise() {
let mut interpreter = interpreter(
r"
namespace Test {
import Std.Diagnostics.*;
@EntryPoint()
operation Main() : Unit {
ConfigurePauliNoise(BitFlipNoise(1.0));
use q = Qubit();
ApplyIdleNoise(q);
}
}",
Profile::Unrestricted,
);

let circ = interpreter
.circuit(CircuitEntryPoint::EntryPoint, false)
.expect("circuit generation should succeed");

// ConfigurePauliNoise has no qubit qrguments so it shouldn't show up.
// ApplyIdleNoise is a quantum operation so it shows up.
expect![[r#"
q_0 ApplyIdleNoise
"#]]
.assert_eq(&circ.to_string());
}

#[test]
fn operation_with_qubits() {
let mut interpreter = interpreter(
Expand Down
42 changes: 34 additions & 8 deletions compiler/qsc_eval/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ pub trait Backend {
fn qubit_is_zero(&mut self, _q: usize) -> bool {
unimplemented!("qubit_is_zero operation");
}
/// Executes custom intrinsic specified by `_name`.
/// Returns None if this intrinsic is unknown.
/// Otherwise returns Some(Result), with the Result from intrinsic.
fn custom_intrinsic(&mut self, _name: &str, _arg: Value) -> Option<Result<Value, String>> {
None
}
Expand Down Expand Up @@ -137,14 +140,17 @@ impl SparseSim {

#[must_use]
pub fn new_with_noise(noise: &PauliNoise) -> Self {
Self {
sim: QuantumSim::new(None),
noise: *noise,
rng: if noise.is_noiseless() {
None
} else {
Some(StdRng::from_entropy())
},
let mut sim = SparseSim::new();
sim.set_noise(noise);
sim
}

fn set_noise(&mut self, noise: &PauliNoise) {
self.noise = *noise;
if noise.is_noiseless() {
self.rng = None;
} else {
self.rng = Some(StdRng::from_entropy());
}
}

Expand Down Expand Up @@ -393,6 +399,26 @@ impl Backend for SparseSim {
| "AccountForEstimatesInternal"
| "BeginRepeatEstimatesInternal"
| "EndRepeatEstimatesInternal" => Some(Ok(Value::unit())),
"ConfigurePauliNoise" => {
DmitryVasilevsky marked this conversation as resolved.
Show resolved Hide resolved
let [xv, yv, zv] = &*arg.unwrap_tuple() else {
panic!("tuple arity for SetPauliNoise intrinsic should be 3");
};
let px = xv.get_double();
let py = yv.get_double();
let pz = zv.get_double();
match PauliNoise::from_probabilities(px, py, pz) {
Ok(noise) => {
self.set_noise(&noise);
Some(Ok(Value::unit()))
}
Err(message) => Some(Err(message)),
}
}
"ApplyIdleNoise" => {
let q = arg.unwrap_qubit().0;
self.apply_noise(q);
Some(Ok(Value::unit()))
}
_ => None,
}
}
Expand Down
22 changes: 21 additions & 1 deletion compiler/qsc_eval/src/intrinsic/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ impl Backend for CustomSim {
match name {
"Add1" => Some(Ok(Value::Int(arg.unwrap_int() + 1))),
"Check" => Some(Err("cannot verify input".to_string())),
_ => None,
_ => self.sim.custom_intrinsic(name, arg),
}
}
}
Expand Down Expand Up @@ -1595,3 +1595,23 @@ fn start_counting_qubits_called_twice_before_stop_fails() {
&expect!["qubits already counted"],
);
}

#[test]
fn check_pauli_noise() {
check_intrinsic_output(
"",
indoc! {"{
import Std.Diagnostics.*;
use q = Qubit();
ConfigurePauliNoise(BitFlipNoise(1.0));
ApplyIdleNoise(q);
ConfigurePauliNoise(NoNoise());
DumpMachine();
Reset(q);
}"},
&expect![[r#"
STATE:
|1⟩: 1.0000+0.0000𝑖
"#]],
);
}
8 changes: 8 additions & 0 deletions compiler/qsc_eval/src/val.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,14 @@ impl Value {
v
}

#[must_use]
pub fn get_double(&self) -> f64 {
let Value::Double(v) = self else {
panic!("value should be Double, got {}", self.type_name());
};
*v
}

/// Convert the [Value] into a global tuple
/// # Panics
/// This will panic if the [Value] is not a [`Value::Global`].
Expand Down
1 change: 1 addition & 0 deletions compiler/qsc_partial_eval/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1344,6 +1344,7 @@ impl<'a> PartialEvaluator<'a> {
| "AccountForEstimatesInternal"
| "BeginRepeatEstimatesInternal"
| "EndRepeatEstimatesInternal"
| "ApplyIdleNoise"
| "GlobalPhase" => Ok(Value::unit()),
// The following intrinsic functions and operations should never make it past conditional compilation and
// the capabilities check pass.
Expand Down
4 changes: 3 additions & 1 deletion compiler/qsc_partial_eval/src/management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@ impl Backend for QuantumIntrinsicsChecker {
) -> Option<std::result::Result<Value, String>> {
match name {
"BeginEstimateCaching" => Some(Ok(Value::Bool(true))),
"EndEstimateCaching" | "GlobalPhase" => Some(Ok(Value::unit())),
"EndEstimateCaching" | "GlobalPhase" | "ConfigurePauliNoise" | "ApplyIdleNoise" => {
Some(Ok(Value::unit()))
}
_ => None,
}
}
Expand Down
32 changes: 32 additions & 0 deletions library/src/tests/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,3 +424,35 @@ fn check_dumpoperation_with_extra_qubits_relative_phase_not_reflected_in_matrix(
"#]]
.assert_eq(&output);
}

#[test]
fn check_bit_flip_noise_values() {
test_expression(
"Std.Diagnostics.BitFlipNoise(0.3)",
&Value::Tuple([Value::Double(0.3), Value::Double(0.0), Value::Double(0.0)].into()),
);
}

#[test]
fn check_phase_flip_noise_values() {
test_expression(
"Std.Diagnostics.PhaseFlipNoise(0.3)",
&Value::Tuple([Value::Double(0.0), Value::Double(0.0), Value::Double(0.3)].into()),
);
}

#[test]
fn check_depolarizing_noise_values() {
test_expression(
"Std.Diagnostics.DepolarizingNoise(0.3)",
&Value::Tuple([Value::Double(0.1), Value::Double(0.1), Value::Double(0.1)].into()),
);
}

#[test]
fn check_no_noise_values() {
test_expression(
"Std.Diagnostics.NoNoise()",
&Value::Tuple([Value::Double(0.0), Value::Double(0.0), Value::Double(0.0)].into()),
);
}
70 changes: 69 additions & 1 deletion library/std/src/Std/Diagnostics.qs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,68 @@ operation StopCountingQubits() : Int {
body intrinsic;
}

/// # Summary
/// Configures Pauli noise for simulation.
///
/// # Description
/// This function configures Pauli noise for simulation. Parameters represent
/// probabilities of applying X, Y, and Z gates and must add up to at most 1.0.
/// Noise is applied after each gate and before each measurement in the simulator
/// backend. Decompositions may affect the number of times noise is applied.
/// Use 0.0 for all parameters to simulate without noise.
///
/// # Input
/// ## px
/// Probability of applying X gate.
/// ## py
/// Probability of applying Y gate.
/// ## pz
/// Probability of applying Z gate.
function ConfigurePauliNoise(px : Double, py : Double, pz : Double) : Unit {
body intrinsic;
}

/// # Summary
/// Applies configured noise to a qubit.
///
/// # Description
/// This operation applies configured noise 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
/// apply noise immediately after qubit allocation.
///
/// # Input
/// ## qubit
/// The qubit to which noise is applied.
operation ApplyIdleNoise(qubit : Qubit) : Unit {
body intrinsic;
}

/// # Summary
/// The bit flip noise with probability `p`.
function BitFlipNoise(p : Double) : (Double, Double, Double) {
(p, 0.0, 0.0)
}

/// # Summary
/// The phase flip noise with probability `p`.
function PhaseFlipNoise(p : Double) : (Double, Double, Double) {
(0.0, 0.0, p)
}

/// # Summary
/// The depolarizing noise with probability `p`.
function DepolarizingNoise(p : Double) : (Double, Double, Double) {
(p / 3.0, p / 3.0, p / 3.0)
}

/// # Summary
/// No noise for noiseless operation.
function NoNoise() : (Double, Double, Double) {
(0.0, 0.0, 0.0)
}

export
DumpMachine,
DumpRegister,
Expand All @@ -381,4 +443,10 @@ export
StartCountingFunction,
StopCountingFunction,
StartCountingQubits,
StopCountingQubits;
StopCountingQubits,
ConfigurePauliNoise,
ApplyIdleNoise,
BitFlipNoise,
PhaseFlipNoise,
DepolarizingNoise,
NoNoise;
2 changes: 1 addition & 1 deletion resource_estimator/src/counts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,6 @@ impl Backend for LogicalCounter {

fn custom_intrinsic(&mut self, name: &str, arg: Value) -> Option<Result<Value, String>> {
match name {
"GlobalPhase" => Some(Ok(Value::unit())),
"BeginEstimateCaching" => {
let values = arg.unwrap_tuple();
let [cache_name, cache_variant] = array::from_fn(|i| values[i].clone());
Expand Down Expand Up @@ -565,6 +564,7 @@ impl Backend for LogicalCounter {
.map(|()| Value::unit()),
)
}
"GlobalPhase" | "ConfigurePauliNoise" | "ApplyIdleNoise" => Some(Ok(Value::unit())),
_ => None,
}
}
Expand Down
Loading