Skip to content

Commit 6f376c4

Browse files
DmitryVasilevskyDmitry Vasilevskyminestarks
authored
Noise settings available in Q# (#1997)
Added ConfigurePauliNoise and ApplyIdleNoise to Diagnostric namespace to control noise from Q# program. --------- Co-authored-by: Dmitry Vasilevsky <[email protected]> Co-authored-by: Mine Starks <[email protected]>
1 parent 7159856 commit 6f376c4

File tree

10 files changed

+222
-12
lines changed

10 files changed

+222
-12
lines changed

compiler/qsc/src/interpret/circuit_tests.rs

+28
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,34 @@ fn custom_intrinsic_mixed_args() {
513513
assert_eq!(circ.operations.len(), 1);
514514
}
515515

516+
#[test]
517+
fn custom_intrinsic_apply_idle_noise() {
518+
let mut interpreter = interpreter(
519+
r"
520+
namespace Test {
521+
import Std.Diagnostics.*;
522+
@EntryPoint()
523+
operation Main() : Unit {
524+
ConfigurePauliNoise(BitFlipNoise(1.0));
525+
use q = Qubit();
526+
ApplyIdleNoise(q);
527+
}
528+
}",
529+
Profile::Unrestricted,
530+
);
531+
532+
let circ = interpreter
533+
.circuit(CircuitEntryPoint::EntryPoint, false)
534+
.expect("circuit generation should succeed");
535+
536+
// ConfigurePauliNoise has no qubit arguments so it shouldn't show up.
537+
// ApplyIdleNoise is a quantum operation so it shows up.
538+
expect![[r#"
539+
q_0 ApplyIdleNoise
540+
"#]]
541+
.assert_eq(&circ.to_string());
542+
}
543+
516544
#[test]
517545
fn operation_with_qubits() {
518546
let mut interpreter = interpreter(

compiler/qsc_eval/src/backend.rs

+34-8
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ pub trait Backend {
101101
fn qubit_is_zero(&mut self, _q: usize) -> bool {
102102
unimplemented!("qubit_is_zero operation");
103103
}
104+
/// Executes custom intrinsic specified by `_name`.
105+
/// Returns None if this intrinsic is unknown.
106+
/// Otherwise returns Some(Result), with the Result from intrinsic.
104107
fn custom_intrinsic(&mut self, _name: &str, _arg: Value) -> Option<Result<Value, String>> {
105108
None
106109
}
@@ -137,14 +140,17 @@ impl SparseSim {
137140

138141
#[must_use]
139142
pub fn new_with_noise(noise: &PauliNoise) -> Self {
140-
Self {
141-
sim: QuantumSim::new(None),
142-
noise: *noise,
143-
rng: if noise.is_noiseless() {
144-
None
145-
} else {
146-
Some(StdRng::from_entropy())
147-
},
143+
let mut sim = SparseSim::new();
144+
sim.set_noise(noise);
145+
sim
146+
}
147+
148+
fn set_noise(&mut self, noise: &PauliNoise) {
149+
self.noise = *noise;
150+
if noise.is_noiseless() {
151+
self.rng = None;
152+
} else {
153+
self.rng = Some(StdRng::from_entropy());
148154
}
149155
}
150156

@@ -393,6 +399,26 @@ impl Backend for SparseSim {
393399
| "AccountForEstimatesInternal"
394400
| "BeginRepeatEstimatesInternal"
395401
| "EndRepeatEstimatesInternal" => Some(Ok(Value::unit())),
402+
"ConfigurePauliNoise" => {
403+
let [xv, yv, zv] = &*arg.unwrap_tuple() else {
404+
panic!("tuple arity for ConfigurePauliNoise intrinsic should be 3");
405+
};
406+
let px = xv.get_double();
407+
let py = yv.get_double();
408+
let pz = zv.get_double();
409+
match PauliNoise::from_probabilities(px, py, pz) {
410+
Ok(noise) => {
411+
self.set_noise(&noise);
412+
Some(Ok(Value::unit()))
413+
}
414+
Err(message) => Some(Err(message)),
415+
}
416+
}
417+
"ApplyIdleNoise" => {
418+
let q = arg.unwrap_qubit().0;
419+
self.apply_noise(q);
420+
Some(Ok(Value::unit()))
421+
}
396422
_ => None,
397423
}
398424
}

compiler/qsc_eval/src/intrinsic/tests.rs

+21-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ impl Backend for CustomSim {
143143
match name {
144144
"Add1" => Some(Ok(Value::Int(arg.unwrap_int() + 1))),
145145
"Check" => Some(Err("cannot verify input".to_string())),
146-
_ => None,
146+
_ => self.sim.custom_intrinsic(name, arg),
147147
}
148148
}
149149
}
@@ -1595,3 +1595,23 @@ fn start_counting_qubits_called_twice_before_stop_fails() {
15951595
&expect!["qubits already counted"],
15961596
);
15971597
}
1598+
1599+
#[test]
1600+
fn check_pauli_noise() {
1601+
check_intrinsic_output(
1602+
"",
1603+
indoc! {"{
1604+
import Std.Diagnostics.*;
1605+
use q = Qubit();
1606+
ConfigurePauliNoise(BitFlipNoise(1.0));
1607+
ApplyIdleNoise(q);
1608+
ConfigurePauliNoise(NoNoise());
1609+
DumpMachine();
1610+
Reset(q);
1611+
}"},
1612+
&expect![[r#"
1613+
STATE:
1614+
|1⟩: 1.0000+0.0000𝑖
1615+
"#]],
1616+
);
1617+
}

compiler/qsc_eval/src/val.rs

+8
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,14 @@ impl Value {
263263
v
264264
}
265265

266+
#[must_use]
267+
pub fn get_double(&self) -> f64 {
268+
let Value::Double(v) = self else {
269+
panic!("value should be Double, got {}", self.type_name());
270+
};
271+
*v
272+
}
273+
266274
/// Convert the [Value] into a global tuple
267275
/// # Panics
268276
/// This will panic if the [Value] is not a [`Value::Global`].

compiler/qsc_partial_eval/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1344,6 +1344,7 @@ impl<'a> PartialEvaluator<'a> {
13441344
| "AccountForEstimatesInternal"
13451345
| "BeginRepeatEstimatesInternal"
13461346
| "EndRepeatEstimatesInternal"
1347+
| "ApplyIdleNoise"
13471348
| "GlobalPhase" => Ok(Value::unit()),
13481349
// The following intrinsic functions and operations should never make it past conditional compilation and
13491350
// the capabilities check pass.

compiler/qsc_partial_eval/src/management.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,9 @@ impl Backend for QuantumIntrinsicsChecker {
133133
) -> Option<std::result::Result<Value, String>> {
134134
match name {
135135
"BeginEstimateCaching" => Some(Ok(Value::Bool(true))),
136-
"EndEstimateCaching" | "GlobalPhase" => Some(Ok(Value::unit())),
136+
"EndEstimateCaching" | "GlobalPhase" | "ConfigurePauliNoise" | "ApplyIdleNoise" => {
137+
Some(Ok(Value::unit()))
138+
}
137139
_ => None,
138140
}
139141
}

compiler/qsc_partial_eval/src/tests/intrinsics.rs

+25
Original file line numberDiff line numberDiff line change
@@ -921,6 +921,31 @@ fn call_to_dump_register_does_not_generate_instructions() {
921921
);
922922
}
923923

924+
#[test]
925+
fn use_of_noise_does_not_generate_instructions() {
926+
let program = get_rir_program(indoc! {
927+
r#"
928+
namespace Test {
929+
import Std.Diagnostics.*;
930+
@EntryPoint()
931+
operation Main() : Unit {
932+
use q = Qubit();
933+
ConfigurePauliNoise(0.2, 0.2, 0.2);
934+
ApplyIdleNoise(q);
935+
}
936+
}
937+
"#,
938+
});
939+
assert_block_instructions(
940+
&program,
941+
BlockId(0),
942+
&expect![[r#"
943+
Block:
944+
Call id(1), args( Integer(0), Pointer, )
945+
Return"#]],
946+
);
947+
}
948+
924949
#[test]
925950
#[should_panic(expected = "`CheckZero` is not a supported by partial evaluation")]
926951
fn call_to_check_zero_panics() {

library/src/tests/diagnostics.rs

+32
Original file line numberDiff line numberDiff line change
@@ -424,3 +424,35 @@ fn check_dumpoperation_with_extra_qubits_relative_phase_not_reflected_in_matrix(
424424
"#]]
425425
.assert_eq(&output);
426426
}
427+
428+
#[test]
429+
fn check_bit_flip_noise_values() {
430+
test_expression(
431+
"Std.Diagnostics.BitFlipNoise(0.3)",
432+
&Value::Tuple([Value::Double(0.3), Value::Double(0.0), Value::Double(0.0)].into()),
433+
);
434+
}
435+
436+
#[test]
437+
fn check_phase_flip_noise_values() {
438+
test_expression(
439+
"Std.Diagnostics.PhaseFlipNoise(0.3)",
440+
&Value::Tuple([Value::Double(0.0), Value::Double(0.0), Value::Double(0.3)].into()),
441+
);
442+
}
443+
444+
#[test]
445+
fn check_depolarizing_noise_values() {
446+
test_expression(
447+
"Std.Diagnostics.DepolarizingNoise(0.3)",
448+
&Value::Tuple([Value::Double(0.1), Value::Double(0.1), Value::Double(0.1)].into()),
449+
);
450+
}
451+
452+
#[test]
453+
fn check_no_noise_values() {
454+
test_expression(
455+
"Std.Diagnostics.NoNoise()",
456+
&Value::Tuple([Value::Double(0.0), Value::Double(0.0), Value::Double(0.0)].into()),
457+
);
458+
}

library/std/src/Std/Diagnostics.qs

+69-1
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,68 @@ operation StopCountingQubits() : Int {
368368
body intrinsic;
369369
}
370370

371+
/// # Summary
372+
/// Configures Pauli noise for simulation.
373+
///
374+
/// # Description
375+
/// This function configures Pauli noise for simulation. Parameters represent
376+
/// probabilities of applying X, Y, and Z gates and must add up to at most 1.0.
377+
/// Noise is applied after each gate and before each measurement in the simulator
378+
/// backend. Decompositions may affect the number of times noise is applied.
379+
/// Use 0.0 for all parameters to simulate without noise.
380+
///
381+
/// # Input
382+
/// ## px
383+
/// Probability of applying X gate.
384+
/// ## py
385+
/// Probability of applying Y gate.
386+
/// ## pz
387+
/// Probability of applying Z gate.
388+
function ConfigurePauliNoise(px : Double, py : Double, pz : Double) : Unit {
389+
body intrinsic;
390+
}
391+
392+
/// # Summary
393+
/// Applies configured noise to a qubit.
394+
///
395+
/// # Description
396+
/// This operation applies configured noise to a qubit during simulation. For example,
397+
/// if configured noise is a bit-flip noise with 5% probability, the X gate will be applied
398+
/// with 5% probability. If no noise is configured, no noise is applied.
399+
/// This is useful to simulate noise during idle periods. It could also be used to
400+
/// apply noise immediately after qubit allocation.
401+
///
402+
/// # Input
403+
/// ## qubit
404+
/// The qubit to which noise is applied.
405+
operation ApplyIdleNoise(qubit : Qubit) : Unit {
406+
body intrinsic;
407+
}
408+
409+
/// # Summary
410+
/// The bit flip noise with probability `p`.
411+
function BitFlipNoise(p : Double) : (Double, Double, Double) {
412+
(p, 0.0, 0.0)
413+
}
414+
415+
/// # Summary
416+
/// The phase flip noise with probability `p`.
417+
function PhaseFlipNoise(p : Double) : (Double, Double, Double) {
418+
(0.0, 0.0, p)
419+
}
420+
421+
/// # Summary
422+
/// The depolarizing noise with probability `p`.
423+
function DepolarizingNoise(p : Double) : (Double, Double, Double) {
424+
(p / 3.0, p / 3.0, p / 3.0)
425+
}
426+
427+
/// # Summary
428+
/// No noise for noiseless operation.
429+
function NoNoise() : (Double, Double, Double) {
430+
(0.0, 0.0, 0.0)
431+
}
432+
371433
export
372434
DumpMachine,
373435
DumpRegister,
@@ -381,4 +443,10 @@ export
381443
StartCountingFunction,
382444
StopCountingFunction,
383445
StartCountingQubits,
384-
StopCountingQubits;
446+
StopCountingQubits,
447+
ConfigurePauliNoise,
448+
ApplyIdleNoise,
449+
BitFlipNoise,
450+
PhaseFlipNoise,
451+
DepolarizingNoise,
452+
NoNoise;

resource_estimator/src/counts.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,6 @@ impl Backend for LogicalCounter {
522522

523523
fn custom_intrinsic(&mut self, name: &str, arg: Value) -> Option<Result<Value, String>> {
524524
match name {
525-
"GlobalPhase" => Some(Ok(Value::unit())),
526525
"BeginEstimateCaching" => {
527526
let values = arg.unwrap_tuple();
528527
let [cache_name, cache_variant] = array::from_fn(|i| values[i].clone());
@@ -565,6 +564,7 @@ impl Backend for LogicalCounter {
565564
.map(|()| Value::unit()),
566565
)
567566
}
567+
"GlobalPhase" | "ConfigurePauliNoise" | "ApplyIdleNoise" => Some(Ok(Value::unit())),
568568
_ => None,
569569
}
570570
}

0 commit comments

Comments
 (0)