diff --git a/source/compiler/stim_compiler/src/parser.rs b/source/compiler/stim_compiler/src/parser.rs index d6a658d9a40..09e6992b0c6 100644 --- a/source/compiler/stim_compiler/src/parser.rs +++ b/source/compiler/stim_compiler/src/parser.rs @@ -107,6 +107,14 @@ impl Display for Arg { } } +pub fn args_span(args: &[Arg]) -> Span { + let (first, rest) = args.split_first().expect("argument list must not be empty"); + Span { + lo: first.span.lo, + hi: rest.last().unwrap_or(first).span.hi, + } +} + #[derive(Debug, Clone, Copy)] pub enum ArgValue { Default(f64), diff --git a/source/compiler/stim_compiler/src/qir.rs b/source/compiler/stim_compiler/src/qir.rs index aa25a8ac514..c22b4565f05 100644 --- a/source/compiler/stim_compiler/src/qir.rs +++ b/source/compiler/stim_compiler/src/qir.rs @@ -431,6 +431,14 @@ pub enum Error { #[label] span: Span, }, + #[error("probabilities for {instruction} must sum to at most 1.0, but they sum to {total}")] + #[diagnostic(code("Qdk.Stim.Compiler.InvalidProbabilitySum"))] + InvalidProbabilitySum { + instruction: String, + total: f64, + #[label] + span: Span, + }, #[error("NOTLEAKED cannot reference a record produced by PEEK_LOSS")] #[diagnostic(code("Qdk.Stim.Compiler.NotLeakedOnPeekLoss"))] NotLeakedOnPeekLoss { @@ -472,6 +480,14 @@ pub enum Error { #[label] span: Span, }, + #[error("qubit {qubit} is repeated in instruction: {instruction}")] + #[diagnostic(code("Qdk.Stim.Compiler.RepeatedQubit"))] + RepeatedQubit { + instruction: String, + qubit: StimQubitId, + #[label] + span: Span, + }, #[error("measurement record target in an unsupported position in instruction: {instruction}")] #[diagnostic(code("Qdk.Stim.Compiler.MisplacedMeasurementRecord"))] MisplacedMeasurementRecord { @@ -509,20 +525,6 @@ pub enum Error { #[label] span: Span, }, - #[error("noise probabilities must sum to at most 1.0, but they sum to {total}")] - #[diagnostic(code("Qdk.Stim.Compiler.NoiseProbabilitiesExceedOne"))] - NoiseProbabilitiesExceedOne { - total: f64, - #[label] - span: Span, - }, - #[error("noise probabilities must be non-negative, but found {probability}")] - #[diagnostic(code("Qdk.Stim.Compiler.NegativeNoiseProbability"))] - NegativeNoiseProbability { - probability: f64, - #[label] - span: Span, - }, #[error("a REPEAT count of zero is not supported")] #[diagnostic(code("Qdk.Stim.Compiler.ZeroRepeatCount"))] ZeroRepeatCount { @@ -639,12 +641,6 @@ fn select_label(scope: u32) -> String { struct CorrelatedRow { terms: Vec<(FaultChar, StimQubitId)>, probability: f64, - span: Span, -} - -struct CorrelatedGroup { - rows: Vec, - span: Span, } #[derive(PartialEq, Eq, Hash)] @@ -669,7 +665,7 @@ impl NoiseKey { struct NoiseAccumulator<'noise> { config: &'noise mut NoiseConfig, intrinsic_ids: FxHashMap, - current_correlated_group: Option, + current_correlated_group: Option>, } impl<'noise> NoiseAccumulator<'noise> { @@ -693,47 +689,27 @@ impl<'noise> NoiseAccumulator<'noise> { } fn push_correlated_row(&mut self, row: CorrelatedRow) { - let current_group = self - .current_correlated_group - .get_or_insert(CorrelatedGroup { - rows: Vec::new(), - span: row.span, - }); - - current_group.span = Span { - lo: current_group.span.lo, - hi: row.span.hi, - }; - current_group.rows.push(row); + self.current_correlated_group + .get_or_insert_with(Vec::new) + .push(row); } - fn try_build_noise_table( + fn build_noise_table( &self, num_qubits: u32, pauli_strings: Vec, probabilities: Vec, - span: Span, - ) -> Result, Error> { - if let Some(&probability) = probabilities.iter().find(|&&p| p < 0.0) { - return Err(Error::NegativeNoiseProbability { probability, span }); - } - let total_probability: f64 = probabilities.iter().sum(); - if total_probability > 1.0 { - return Err(Error::NoiseProbabilitiesExceedOne { - total: total_probability, - span, - }); - } - Ok(NoiseTable { + ) -> NoiseTable { + NoiseTable { qubits: num_qubits, pauli_strings, probabilities, on_loss: LossPolicy::Skip, // required field; Skip is the default policy - }) + } } - fn flush_correlated_group(&mut self) -> Result<(NoiseTable, Vec), Error> { - let CorrelatedGroup { rows, span } = self + fn flush_correlated_group(&mut self) -> (NoiseTable, Vec) { + let rows = self .current_correlated_group .take() .expect("a correlated group must be present to flush"); // this is a compiler invariant @@ -754,13 +730,9 @@ impl<'noise> NoiseAccumulator<'noise> { probabilities.push(remaining_probability * row.probability); // each row fires only if all previous ones didn't remaining_probability *= 1.0 - row.probability; } - let noise_table = self.try_build_noise_table( - pauli_string_width as u32, - pauli_strings, - probabilities, - span, - )?; - Ok((noise_table, qubits)) + let noise_table = + self.build_noise_table(pauli_string_width as u32, pauli_strings, probabilities); + (noise_table, qubits) } fn collect_qubits(&self, rows: &[CorrelatedRow]) -> Vec { @@ -1068,10 +1040,7 @@ impl<'noise> Compiler<'noise> { s.op_2("cx", q1, q0); s.op("h", q1); }), - "II" => { - self.unsupported_args(instruction); - self.expect_target_pairs(instruction); - } + "II" => self.broadcast_pair(instruction, |_, _, _| {}), "ISWAP" => self.broadcast_pair(instruction, |s, q0, q1| { // Stim decomposition (into H, S, CX, M, R): H 0; CX 0 1; CX 1 0; H 1; S 1; S 0 s.op("h", q0); @@ -1218,18 +1187,15 @@ impl<'noise> Compiler<'noise> { "ELSE_CORRELATED_ERROR" => self.continue_correlated_noise(instruction), "DEPOLARIZE1" => self.broadcast_noise(instruction, |s, q, p| { - let Some(table) = s.build_noise_table( + let table = s.noise_accumulator.build_noise_table( 1, ["X", "Y", "Z"].map(encode_pauli).to_vec(), vec![p / 3.0; 3], - instruction.span, - ) else { - return; - }; + ); s.op_noise(table, &[q]); }), "DEPOLARIZE2" => self.broadcast_pair_noise(instruction, |s, q0, q1, p| { - let Some(table) = s.build_noise_table( + let table = s.noise_accumulator.build_noise_table( 2, [ "IX", "IY", "IZ", "XI", "XX", "XY", "XZ", "YI", "YX", "YY", "YZ", "ZI", @@ -1238,30 +1204,22 @@ impl<'noise> Compiler<'noise> { .map(encode_pauli) .to_vec(), vec![p / 15.0; 15], - instruction.span, - ) else { - return; - }; + ); s.op_noise(table, &[q0, q1]); }), "HERALDED_ERASE" | "HERALDED_PAULI_CHANNEL_1" => self.unsupported(instruction), - "II_ERROR" => { - self.expect_target_pairs(instruction); - } + "II_ERROR" => self.for_each_pair(instruction, |_, _, _| {}), "I_ERROR" => (), "PAULI_CHANNEL_1" => { let Some(probabilities) = self.expect_probabilities(instruction, 3) else { return; }; - let Some(table) = self.build_noise_table( + let table = self.noise_accumulator.build_noise_table( 1, ["X", "Y", "Z"].map(encode_pauli).to_vec(), probabilities, - instruction.span, - ) else { - return; - }; + ); self.for_each_qubit(instruction, |s, q| { s.op_noise(table.clone(), &[q]); }); @@ -1271,7 +1229,7 @@ impl<'noise> Compiler<'noise> { return; }; - let Some(table) = self.build_noise_table( + let table = self.noise_accumulator.build_noise_table( 2, [ "IX", "IY", "IZ", "XI", "XX", "XY", "XZ", "YI", "YX", "YY", "YZ", "ZI", @@ -1280,10 +1238,7 @@ impl<'noise> Compiler<'noise> { .map(encode_pauli) .to_vec(), probabilities, - instruction.span, - ) else { - return; - }; + ); self.for_each_pair(instruction, |s, q0, q1| { s.op_noise(table.clone(), &[q0, q1]); }); @@ -1291,14 +1246,11 @@ impl<'noise> Compiler<'noise> { "X_ERROR" | "Y_ERROR" | "Z_ERROR" | "LOSS_ERROR" => { let fault = FaultChar::from_instruction_name(&instruction.name); self.broadcast_noise(instruction, |s, q, p| { - let Some(table) = s.build_noise_table( + let table = s.noise_accumulator.build_noise_table( 1, vec![encode_pauli(fault.as_str())], vec![p], - instruction.span, - ) else { - return; - }; + ); s.op_noise(table, &[q]); }); } @@ -1541,10 +1493,7 @@ impl<'noise> Compiler<'noise> { return; }; for pair in pairs { - let Some((q0, _)) = self.expect_qubit(instruction, &pair[0], false) else { - continue; - }; - let Some((q1, _)) = self.expect_qubit(instruction, &pair[1], false) else { + let Some([(q0, _), (q1, _)]) = self.expect_qubit_pair(instruction, pair, false) else { continue; }; operation(self, q0, q1); @@ -1560,10 +1509,8 @@ impl<'noise> Compiler<'noise> { return; }; for pair in pairs { - let Some((q0, neg0)) = self.expect_qubit(instruction, &pair[0], true) else { - continue; - }; - let Some((q1, neg1)) = self.expect_qubit(instruction, &pair[1], true) else { + let Some([(q0, neg0), (q1, neg1)]) = self.expect_qubit_pair(instruction, pair, true) + else { continue; }; operation(self, q0, q1, neg0 ^ neg1); @@ -1579,13 +1526,7 @@ impl<'noise> Compiler<'noise> { return; }; for triple in triples { - let Some((q0, _)) = self.expect_qubit(instruction, &triple[0], false) else { - continue; - }; - let Some((q1, _)) = self.expect_qubit(instruction, &triple[1], false) else { - continue; - }; - let Some((q2, _)) = self.expect_qubit(instruction, &triple[2], false) else { + let Some([q0, q1, q2]) = self.expect_qubit_triple(instruction, triple) else { continue; }; operation(self, q0, q1, q2); @@ -1728,10 +1669,9 @@ impl<'noise> Compiler<'noise> { for pair in pairs { match (&pair[0].kind, &pair[1].kind) { (TargetKind::Qubit { .. }, TargetKind::Qubit { .. }) => { - let Some((control, _)) = self.expect_qubit(instruction, &pair[0], false) else { - continue; - }; - let Some((target, _)) = self.expect_qubit(instruction, &pair[1], false) else { + let Some([(control, _), (target, _)]) = + self.expect_qubit_pair(instruction, pair, false) + else { continue; }; quantum(self, control, target); @@ -1818,13 +1758,8 @@ impl<'noise> Compiler<'noise> { terms.push((fault, qubit)); } - let row = CorrelatedRow { - probability, - terms, - span: instruction.span, - }; - - self.noise_accumulator.push_correlated_row(row); + self.noise_accumulator + .push_correlated_row(CorrelatedRow { probability, terms }); } fn continue_correlated_noise(&mut self, instruction: &Instruction) { @@ -1841,10 +1776,8 @@ impl<'noise> Compiler<'noise> { if self.noise_accumulator.current_correlated_group.is_none() { return; } - match self.noise_accumulator.flush_correlated_group() { - Ok((noise_table, qubits)) => self.op_noise(noise_table, &qubits), - Err(error) => self.push_error(error), - } + let (noise_table, qubits) = self.noise_accumulator.flush_correlated_group(); + self.op_noise(noise_table, &qubits); } /// Converts a Pauli product to a canonical form: one factor per qubit, sorted by @@ -2036,27 +1969,6 @@ impl<'noise> Compiler<'noise> { } } - fn build_noise_table( - &mut self, - num_qubits: u32, - pauli_strings: Vec, - probabilities: Vec, - span: Span, - ) -> Option> { - match self.noise_accumulator.try_build_noise_table( - num_qubits, - pauli_strings, - probabilities, - span, - ) { - Ok(table) => Some(table), - Err(error) => { - self.push_error(error); - None - } - } - } - fn compile_require(&mut self, instruction: &Instruction) { let Some(record_metadata) = self.validate_select_condition(instruction) else { return; @@ -2245,6 +2157,52 @@ impl<'noise> Compiler<'noise> { Some((value, negated)) } + fn expect_qubit_pair( + &mut self, + instruction: &Instruction, + pair: &[Target], + allow_negated: bool, + ) -> Option<[(StimQubitId, bool); 2]> { + let (q0, neg0) = self.expect_qubit(instruction, &pair[0], allow_negated)?; + let (q1, neg1) = self.expect_qubit(instruction, &pair[1], allow_negated)?; + + if q0 == q1 { + self.push_error(Error::RepeatedQubit { + instruction: instruction.name.clone(), + qubit: q1, + span: pair[1].span, + }); + return None; + } + + Some([(q0, neg0), (q1, neg1)]) + } + + fn expect_qubit_triple( + &mut self, + instruction: &Instruction, + triple: &[Target], + ) -> Option<[StimQubitId; 3]> { + let (q0, _) = self.expect_qubit(instruction, &triple[0], false)?; + let (q1, _) = self.expect_qubit(instruction, &triple[1], false)?; + let (q2, _) = self.expect_qubit(instruction, &triple[2], false)?; + + let (repeated_qubit_value, repeated_qubit_span) = if q0 == q1 { + (q1, triple[1].span) + } else if q0 == q2 || q1 == q2 { + (q2, triple[2].span) + } else { + return Some([q0, q1, q2]); + }; + + self.push_error(Error::RepeatedQubit { + instruction: instruction.name.clone(), + qubit: repeated_qubit_value, + span: repeated_qubit_span, + }); + None + } + fn expect_fault_char( &mut self, instruction: &Instruction, @@ -2391,7 +2349,7 @@ impl<'noise> Compiler<'noise> { let mut probabilities = Vec::with_capacity(args.len()); let mut has_invalid_probability = false; - for arg in args { + for &arg in &args { match arg.value { ArgValue::Default(value) => { if (0.0..=1.0).contains(&value) { @@ -2400,7 +2358,7 @@ impl<'noise> Compiler<'noise> { self.push_error(Error::InvalidProbability { instruction: instruction.name.clone(), probability: value, - span: instruction.span, + span: arg.span, }); has_invalid_probability = true; } @@ -2414,11 +2372,21 @@ impl<'noise> Compiler<'noise> { } } } - if !has_invalid_probability { - Some(probabilities) - } else { - None + if has_invalid_probability { + return None; + } + + let total: f64 = probabilities.iter().sum(); + if total > 1.0 { + self.push_error(Error::InvalidProbabilitySum { + instruction: instruction.name.clone(), + total, + span: args_span(&args), + }); + return None; } + + Some(probabilities) } fn expect_args(&mut self, instruction: &Instruction, expected: usize) -> Option> { @@ -2436,7 +2404,7 @@ impl<'noise> Compiler<'noise> { instruction: instruction.name.clone(), expected, found: args.len(), - span: instruction.span, + span: args_span(&args[expected..]), }); return None; } else if args.len() < expected { @@ -2444,7 +2412,7 @@ impl<'noise> Compiler<'noise> { instruction: instruction.name.clone(), expected, found: args.len(), - span: instruction.span, + span: args_span(args), }); return None; } @@ -2462,7 +2430,7 @@ impl<'noise> Compiler<'noise> { if !instruction.args.is_empty() { self.push_error(Error::UnsupportedArgument { instruction: instruction.name.clone(), - span: instruction.span, + span: args_span(&instruction.args), }); } } diff --git a/source/compiler/stim_compiler/src/qir/tests/collapsing_gates.rs b/source/compiler/stim_compiler/src/qir/tests/collapsing_gates.rs index 463120820f8..43ae8651b31 100644 --- a/source/compiler/stim_compiler/src/qir/tests/collapsing_gates.rs +++ b/source/compiler/stim_compiler/src/qir/tests/collapsing_gates.rs @@ -171,7 +171,7 @@ fn m_gate_with_invalid_readout_noise_yields_error() { x probability for M must be between 0 and 1; found 1.1 ,---- 1 | M(1.1) 0 - : ^^^^^^^^ + : ^^^ `---- "#]], ); @@ -179,14 +179,14 @@ fn m_gate_with_invalid_readout_noise_yields_error() { check( "M(-0.1) 0", &expect![[r#" - Qdk.Stim.Compiler.InvalidProbability + Qdk.Stim.Compiler.InvalidProbability - x probability for M must be between 0 and 1; found -0.1 - ,---- - 1 | M(-0.1) 0 - : ^^^^^^^^^ - `---- - "#]], + x probability for M must be between 0 and 1; found -0.1 + ,---- + 1 | M(-0.1) 0 + : ^^^^ + `---- + "#]], ); } @@ -216,7 +216,7 @@ fn m_gate_with_two_args_yields_error() { x too many arguments for instruction M; expected 1, found 2 ,---- 1 | M(0.1, 0.2) 0 - : ^^^^^^^^^^^^^ + : ^^^ `---- "#]], ); diff --git a/source/compiler/stim_compiler/src/qir/tests/generalized_pauli_product_gates.rs b/source/compiler/stim_compiler/src/qir/tests/generalized_pauli_product_gates.rs index b511daaa35f..205353542e8 100644 --- a/source/compiler/stim_compiler/src/qir/tests/generalized_pauli_product_gates.rs +++ b/source/compiler/stim_compiler/src/qir/tests/generalized_pauli_product_gates.rs @@ -924,26 +924,26 @@ fn mpp_with_invalid_readout_noise_yields_error() { check( "MPP(1.1) Z1*Z2", &expect![[r#" - Qdk.Stim.Compiler.InvalidProbability + Qdk.Stim.Compiler.InvalidProbability - x probability for MPP must be between 0 and 1; found 1.1 - ,---- - 1 | MPP(1.1) Z1*Z2 - : ^^^^^^^^^^^^^^ - `---- - "#]], + x probability for MPP must be between 0 and 1; found 1.1 + ,---- + 1 | MPP(1.1) Z1*Z2 + : ^^^ + `---- + "#]], ); check( "MPP(-0.1) Z1*Z2", &expect![[r#" - Qdk.Stim.Compiler.InvalidProbability + Qdk.Stim.Compiler.InvalidProbability - x probability for MPP must be between 0 and 1; found -0.1 - ,---- - 1 | MPP(-0.1) Z1*Z2 - : ^^^^^^^^^^^^^^^ - `---- - "#]], + x probability for MPP must be between 0 and 1; found -0.1 + ,---- + 1 | MPP(-0.1) Z1*Z2 + : ^^^^ + `---- + "#]], ); } @@ -1369,14 +1369,14 @@ fn spp_with_argument_yields_error() { check( "SPP(0.001) Z0", &expect![[r#" - Qdk.Stim.Compiler.UnsupportedArgument + Qdk.Stim.Compiler.UnsupportedArgument - x unsupported argument in instruction: SPP - ,---- - 1 | SPP(0.001) Z0 - : ^^^^^^^^^^^^^ - `---- - "#]], + x unsupported argument in instruction: SPP + ,---- + 1 | SPP(0.001) Z0 + : ^^^^^ + `---- + "#]], ); } diff --git a/source/compiler/stim_compiler/src/qir/tests/noise_channels.rs b/source/compiler/stim_compiler/src/qir/tests/noise_channels.rs index 54f9a11b45f..f179e75f171 100644 --- a/source/compiler/stim_compiler/src/qir/tests/noise_channels.rs +++ b/source/compiler/stim_compiler/src/qir/tests/noise_channels.rs @@ -125,21 +125,21 @@ fn correlated_error_with_invalid_probability_yields_error() { x probability for CORRELATED_ERROR must be between 0 and 1; found 1.5 ,---- 1 | CORRELATED_ERROR(1.5) X0 - : ^^^^^^^^^^^^^^^^^^^^^^^^ + : ^^^ `---- "#]], ); check( "CORRELATED_ERROR(-0.1) X0", &expect![[r#" - Qdk.Stim.Compiler.InvalidProbability + Qdk.Stim.Compiler.InvalidProbability - x probability for CORRELATED_ERROR must be between 0 and 1; found -0.1 - ,---- - 1 | CORRELATED_ERROR(-0.1) X0 - : ^^^^^^^^^^^^^^^^^^^^^^^^^ - `---- - "#]], + x probability for CORRELATED_ERROR must be between 0 and 1; found -0.1 + ,---- + 1 | CORRELATED_ERROR(-0.1) X0 + : ^^^^ + `---- + "#]], ); } @@ -206,52 +206,77 @@ fn correlated_error_with_probability_of_exactly_one_is_valid() { } #[test] -fn else_correlated_error_with_preceding_correlated_error_yields_expected_qir() { +fn correlated_error_chain_with_input_probabilities_summing_above_one_is_valid() { + // The resulting mutually exclusive probabilities sum to at most 1 + // when each input probability is in [0, 1]. let source = indoc! {" - CORRELATED_ERROR(0.01) X0 - ELSE_CORRELATED_ERROR(0.02) Z0 + CORRELATED_ERROR(0.8) X0 + ELSE_CORRELATED_ERROR(0.8) Y0 + ELSE_CORRELATED_ERROR(0.8) Z0 "}; check( source, &expect![[r#" - NoiseConfig: - intrinsics: - 0: NoiseTable: - qubits: 1 - X: 0.01 - Z: 0.0198 + NoiseConfig: + intrinsics: + 0: NoiseTable: + qubits: 1 + X: 0.8 + Y: 0.15999999999999998 + Z: 0.03199999999999999 - define i64 @ENTRYPOINT__main() #0 { - call void @__quantum__rt__initialize(ptr null) - call void @noise_intrinsic_0(ptr inttoptr (i64 0 to ptr)) - call void @__quantum__rt__array_record_output(i64 0, ptr null) - ret i64 0 - } + define i64 @ENTRYPOINT__main() #0 { + call void @__quantum__rt__initialize(ptr null) + call void @noise_intrinsic_0(ptr inttoptr (i64 0 to ptr)) + call void @__quantum__rt__array_record_output(i64 0, ptr null) + ret i64 0 + } - declare void @noise_intrinsic_0(ptr) #2 - declare void @__quantum__rt__result_record_output(ptr, ptr) - declare void @__quantum__rt__array_record_output(i64, ptr) - declare void @__quantum__rt__initialize(ptr) + declare void @noise_intrinsic_0(ptr) #2 + declare void @__quantum__rt__result_record_output(ptr, ptr) + declare void @__quantum__rt__array_record_output(i64, ptr) + declare void @__quantum__rt__initialize(ptr) - attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="0" } - attributes #1 = { "irreversible" } + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="0" } + attributes #1 = { "irreversible" } - ; module flags + ; module flags - attributes #2 = { "qdk_noise" } + attributes #2 = { "qdk_noise" } - !llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7} + !llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7} - !0 = !{i32 1, !"qir_major_version", i32 2} - !1 = !{i32 7, !"qir_minor_version", i32 1} - !2 = !{i32 1, !"dynamic_qubit_management", i1 false} - !3 = !{i32 1, !"dynamic_result_management", i1 false} - !4 = !{i32 5, !"int_computations", !{!"i64"}} - !5 = !{i32 5, !"float_computations", !{!"double"}} - !6 = !{i32 7, !"backwards_branching", i2 3} - !7 = !{i32 1, !"arrays", i1 true} - "#]], + !0 = !{i32 1, !"qir_major_version", i32 2} + !1 = !{i32 7, !"qir_minor_version", i32 1} + !2 = !{i32 1, !"dynamic_qubit_management", i1 false} + !3 = !{i32 1, !"dynamic_result_management", i1 false} + !4 = !{i32 5, !"int_computations", !{!"i64"}} + !5 = !{i32 5, !"float_computations", !{!"double"}} + !6 = !{i32 7, !"backwards_branching", i2 3} + !7 = !{i32 1, !"arrays", i1 true} + "#]], + ); +} + +#[test] +fn else_correlated_error_with_invalid_probability_yields_error() { + let source = indoc! {" + CORRELATED_ERROR(0.01) X0 + ELSE_CORRELATED_ERROR(1.5) X0 + "}; + check( + source, + &expect![[r#" + Qdk.Stim.Compiler.InvalidProbability + + x probability for ELSE_CORRELATED_ERROR must be between 0 and 1; found 1.5 + ,-[2:23] + 1 | CORRELATED_ERROR(0.01) X0 + 2 | ELSE_CORRELATED_ERROR(1.5) X0 + : ^^^ + `---- + "#]], ); } @@ -552,7 +577,7 @@ fn depolarize1_with_invalid_probability_yields_error() { x probability for DEPOLARIZE1 must be between 0 and 1; found 1.5 ,---- 1 | DEPOLARIZE1(1.5) 0 - : ^^^^^^^^^^^^^^^^^^ + : ^^^ `---- "#]], ); @@ -560,14 +585,14 @@ fn depolarize1_with_invalid_probability_yields_error() { check( "DEPOLARIZE1(-0.1) 0", &expect![[r#" - Qdk.Stim.Compiler.InvalidProbability + Qdk.Stim.Compiler.InvalidProbability - x probability for DEPOLARIZE1 must be between 0 and 1; found -0.1 - ,---- - 1 | DEPOLARIZE1(-0.1) 0 - : ^^^^^^^^^^^^^^^^^^^ - `---- - "#]], + x probability for DEPOLARIZE1 must be between 0 and 1; found -0.1 + ,---- + 1 | DEPOLARIZE1(-0.1) 0 + : ^^^^ + `---- + "#]], ); } @@ -865,7 +890,7 @@ fn pauli_channel_1_with_wrong_number_of_args_yields_error() { x too few arguments for instruction PAULI_CHANNEL_1; expected 3, found 2 ,---- 1 | PAULI_CHANNEL_1(0.1, 0.2) 0 - : ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + : ^^^^^^^^ `---- "#]], ); @@ -877,14 +902,15 @@ fn pauli_channel_1_with_probabilities_exceeding_one_yields_error() { check( source, &expect![[r#" - Qdk.Stim.Compiler.NoiseProbabilitiesExceedOne + Qdk.Stim.Compiler.InvalidProbabilitySum - x noise probabilities must sum to at most 1.0, but they sum to 1.5 - ,---- - 1 | PAULI_CHANNEL_1(0.5, 0.5, 0.5) 0 - : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - `---- - "#]], + x probabilities for PAULI_CHANNEL_1 must sum to at most 1.0, but they sum to + | 1.5 + ,---- + 1 | PAULI_CHANNEL_1(0.5, 0.5, 0.5) 0 + : ^^^^^^^^^^^^^ + `---- + "#]], ); } @@ -947,7 +973,7 @@ fn pauli_channel_1_with_invalid_probability_yields_error() { x probability for PAULI_CHANNEL_1 must be between 0 and 1; found -0.1 ,---- 1 | PAULI_CHANNEL_1(-0.1, 0.2, 0.3) 0 - : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + : ^^^^ `---- "#]], ); @@ -955,14 +981,14 @@ fn pauli_channel_1_with_invalid_probability_yields_error() { check( "PAULI_CHANNEL_1(1.5, 0.0, 0.0) 0", &expect![[r#" - Qdk.Stim.Compiler.InvalidProbability + Qdk.Stim.Compiler.InvalidProbability - x probability for PAULI_CHANNEL_1 must be between 0 and 1; found 1.5 - ,---- - 1 | PAULI_CHANNEL_1(1.5, 0.0, 0.0) 0 - : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - `---- - "#]], + x probability for PAULI_CHANNEL_1 must be between 0 and 1; found 1.5 + ,---- + 1 | PAULI_CHANNEL_1(1.5, 0.0, 0.0) 0 + : ^^^ + `---- + "#]], ); } @@ -1070,7 +1096,24 @@ fn pauli_channel_2_with_wrong_number_of_args_yields_error() { x too few arguments for instruction PAULI_CHANNEL_2; expected 15, found 1 ,---- 1 | PAULI_CHANNEL_2(0.1) 0 1 - : ^^^^^^^^^^^^^^^^^^^^^^^^ + : ^^^ + `---- + "#]], + ); +} + +#[test] +fn pauli_channel_2_with_probabilities_exceeding_one_yields_error() { + check( + "PAULI_CHANNEL_2(0.6,0.6,0,0,0,0,0,0,0,0,0,0,0,0,0) 0 1", + &expect![[r#" + Qdk.Stim.Compiler.InvalidProbabilitySum + + x probabilities for PAULI_CHANNEL_2 must sum to at most 1.0, but they sum to + | 1.2 + ,---- + 1 | PAULI_CHANNEL_2(0.6,0.6,0,0,0,0,0,0,0,0,0,0,0,0,0) 0 1 + : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `---- "#]], ); @@ -1150,7 +1193,7 @@ fn x_error_with_probability_exceeding_one_yields_error() { x probability for X_ERROR must be between 0 and 1; found 1.5 ,---- 1 | X_ERROR(1.5) 0 - : ^^^^^^^^^^^^^^ + : ^^^ `---- "#]], ); diff --git a/source/compiler/stim_compiler/src/qir/tests/noise_channels_broadcasting.rs b/source/compiler/stim_compiler/src/qir/tests/noise_channels_broadcasting.rs index 7496fb76f6e..f6adb2a11ec 100644 --- a/source/compiler/stim_compiler/src/qir/tests/noise_channels_broadcasting.rs +++ b/source/compiler/stim_compiler/src/qir/tests/noise_channels_broadcasting.rs @@ -351,7 +351,7 @@ fn pauli_channel_1_with_wrong_number_of_args_yields_error() { x too few arguments for instruction PAULI_CHANNEL_1; expected 3, found 2 ,---- 1 | PAULI_CHANNEL_1(0.1, 0.2) 0 1 - : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + : ^^^^^^^^ `---- "#]], ); @@ -446,7 +446,7 @@ fn pauli_channel_2_with_wrong_number_of_args_yields_error() { x too few arguments for instruction PAULI_CHANNEL_2; expected 15, found 1 ,---- 1 | PAULI_CHANNEL_2(0.1) 0 1 2 3 - : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + : ^^^ `---- "#]], ); diff --git a/source/compiler/stim_compiler/src/qir/tests/non_clifford_gates.rs b/source/compiler/stim_compiler/src/qir/tests/non_clifford_gates.rs index ac2a9e7f866..4a419854a07 100644 --- a/source/compiler/stim_compiler/src/qir/tests/non_clifford_gates.rs +++ b/source/compiler/stim_compiler/src/qir/tests/non_clifford_gates.rs @@ -87,7 +87,7 @@ fn t_gate_with_argument_yields_error() { x unsupported argument in instruction: T ,---- 1 | T(0.5) 0 - : ^^^^^^^^ + : ^^^ `---- "#]], ); @@ -572,7 +572,7 @@ fn tpp_with_argument_yields_error() { x unsupported argument in instruction: TPP ,---- 1 | TPP(0.5) Z0 - : ^^^^^^^^^^^ + : ^^^ `---- "#]], ); @@ -808,7 +808,7 @@ fn ccx_gate_with_argument_yields_error() { x unsupported argument in instruction: CCX ,---- 1 | CCX(0.5) 0 1 2 - : ^^^^^^^^^^^^^^ + : ^^^ `---- "#]], ); @@ -830,6 +830,58 @@ fn ccx_gate_with_negated_target_yields_error() { ); } +#[test] +fn ccx_gate_with_repeated_qubit_yields_error() { + check( + "CCX 0 0 1", + &expect![[r#" + Qdk.Stim.Compiler.RepeatedQubit + + x qubit 0 is repeated in instruction: CCX + ,---- + 1 | CCX 0 0 1 + : ^ + `---- + "#]], + ); + check( + "CCX 0 1 0", + &expect![[r#" + Qdk.Stim.Compiler.RepeatedQubit + + x qubit 0 is repeated in instruction: CCX + ,---- + 1 | CCX 0 1 0 + : ^ + `---- + "#]], + ); + check( + "CCX 0 1 1", + &expect![[r#" + Qdk.Stim.Compiler.RepeatedQubit + + x qubit 1 is repeated in instruction: CCX + ,---- + 1 | CCX 0 1 1 + : ^ + `---- + "#]], + ); + check( + "CCX 0 0 0", + &expect![[r#" + Qdk.Stim.Compiler.RepeatedQubit + + x qubit 0 is repeated in instruction: CCX + ,---- + 1 | CCX 0 0 0 + : ^ + `---- + "#]], + ); +} + #[test] fn ccz_gate_with_measurement_record_target_yields_error() { let source = indoc! {" @@ -1059,7 +1111,7 @@ fn r_x_with_two_arguments_yields_error() { x too many arguments for instruction R_X; expected 1, found 2 ,---- 1 | R_X(0.25, 0.5) 0 - : ^^^^^^^^^^^^^^^^ + : ^^^ `---- "#]], ); @@ -1224,7 +1276,7 @@ fn u3_with_one_argument_yields_error() { x too few arguments for instruction U3; expected 3, found 1 ,---- 1 | U3(0.1) 0 - : ^^^^^^^^^ + : ^^^ `---- "#]], ); @@ -1240,7 +1292,7 @@ fn u3_with_two_arguments_yields_error() { x too few arguments for instruction U3; expected 3, found 2 ,---- 1 | U3(0.1, 0.2) 0 - : ^^^^^^^^^^^^^^ + : ^^^^^^^^ `---- "#]], ); @@ -1256,7 +1308,7 @@ fn u3_with_four_arguments_yields_error() { x too many arguments for instruction U3; expected 3, found 4 ,---- 1 | U3(0.1, 0.2, 0.3, 0.4) 0 - : ^^^^^^^^^^^^^^^^^^^^^^^^ + : ^^^ `---- "#]], ); @@ -1473,7 +1525,7 @@ fn r_xx_with_two_arguments_yields_error() { x too many arguments for instruction R_XX; expected 1, found 2 ,---- 1 | R_XX(0.25, 0.5) 0 1 - : ^^^^^^^^^^^^^^^^^^^ + : ^^^ `---- "#]], ); @@ -1710,7 +1762,7 @@ fn r_pauli_with_two_arguments_yields_error() { x too many arguments for instruction R_PAULI; expected 1, found 2 ,---- 1 | R_PAULI(0.25, 0.5) X0 - : ^^^^^^^^^^^^^^^^^^^^^ + : ^^^ `---- "#]], ); diff --git a/source/compiler/stim_compiler/src/qir/tests/pair_measurements.rs b/source/compiler/stim_compiler/src/qir/tests/pair_measurements.rs index a4ccc5d63ce..f7fa5404d62 100644 --- a/source/compiler/stim_compiler/src/qir/tests/pair_measurements.rs +++ b/source/compiler/stim_compiler/src/qir/tests/pair_measurements.rs @@ -147,26 +147,26 @@ fn mxx_with_invalid_readout_noise_yields_error() { check( "MXX(1.1) 0 1", &expect![[r#" - Qdk.Stim.Compiler.InvalidProbability + Qdk.Stim.Compiler.InvalidProbability - x probability for MXX must be between 0 and 1; found 1.1 - ,---- - 1 | MXX(1.1) 0 1 - : ^^^^^^^^^^^^ - `---- - "#]], + x probability for MXX must be between 0 and 1; found 1.1 + ,---- + 1 | MXX(1.1) 0 1 + : ^^^ + `---- + "#]], ); check( "MXX(-0.1) 0 1", &expect![[r#" - Qdk.Stim.Compiler.InvalidProbability + Qdk.Stim.Compiler.InvalidProbability - x probability for MXX must be between 0 and 1; found -0.1 - ,---- - 1 | MXX(-0.1) 0 1 - : ^^^^^^^^^^^^^ - `---- - "#]], + x probability for MXX must be between 0 and 1; found -0.1 + ,---- + 1 | MXX(-0.1) 0 1 + : ^^^^ + `---- + "#]], ); } @@ -186,6 +186,22 @@ fn mxx_with_readout_noise_in_radians_yields_error() { ); } +#[test] +fn mxx_measurement_with_repeated_qubit_yields_error() { + check( + "MXX 0 0", + &expect![[r#" + Qdk.Stim.Compiler.RepeatedQubit + + x qubit 0 is repeated in instruction: MXX + ,---- + 1 | MXX 0 0 + : ^ + `---- + "#]], + ); +} + #[test] fn myy_measurement_yields_correct_qir() { let source = "MYY 0 1"; diff --git a/source/compiler/stim_compiler/src/qir/tests/peek_loss.rs b/source/compiler/stim_compiler/src/qir/tests/peek_loss.rs index 9576353cf60..70298cd52ac 100644 --- a/source/compiler/stim_compiler/src/qir/tests/peek_loss.rs +++ b/source/compiler/stim_compiler/src/qir/tests/peek_loss.rs @@ -129,26 +129,26 @@ fn peek_loss_with_invalid_readout_noise_yields_error() { check( "PEEK_LOSS(1.1) 0", &expect![[r#" - Qdk.Stim.Compiler.InvalidProbability + Qdk.Stim.Compiler.InvalidProbability - x probability for PEEK_LOSS must be between 0 and 1; found 1.1 - ,---- - 1 | PEEK_LOSS(1.1) 0 - : ^^^^^^^^^^^^^^^^ - `---- - "#]], + x probability for PEEK_LOSS must be between 0 and 1; found 1.1 + ,---- + 1 | PEEK_LOSS(1.1) 0 + : ^^^ + `---- + "#]], ); check( "PEEK_LOSS(-0.1) 0", &expect![[r#" - Qdk.Stim.Compiler.InvalidProbability + Qdk.Stim.Compiler.InvalidProbability - x probability for PEEK_LOSS must be between 0 and 1; found -0.1 - ,---- - 1 | PEEK_LOSS(-0.1) 0 - : ^^^^^^^^^^^^^^^^^ - `---- - "#]], + x probability for PEEK_LOSS must be between 0 and 1; found -0.1 + ,---- + 1 | PEEK_LOSS(-0.1) 0 + : ^^^^ + `---- + "#]], ); } diff --git a/source/compiler/stim_compiler/src/qir/tests/repeat.rs b/source/compiler/stim_compiler/src/qir/tests/repeat.rs index 55c8ce794de..86cb58f4867 100644 --- a/source/compiler/stim_compiler/src/qir/tests/repeat.rs +++ b/source/compiler/stim_compiler/src/qir/tests/repeat.rs @@ -83,9 +83,9 @@ fn repeat_with_args_yields_error() { Qdk.Stim.Compiler.UnsupportedArgument x unsupported argument in instruction: REPEAT - ,-[1:1] + ,-[1:8] 1 | REPEAT(0.1) 3 { - : ^^^^^^^^^^^^^ + : ^^^ 2 | X 0 `---- "#]], diff --git a/source/compiler/stim_compiler/src/qir/tests/select_block.rs b/source/compiler/stim_compiler/src/qir/tests/select_block.rs index 072b87e3a71..78a412f363b 100644 --- a/source/compiler/stim_compiler/src/qir/tests/select_block.rs +++ b/source/compiler/stim_compiler/src/qir/tests/select_block.rs @@ -375,9 +375,9 @@ fn select_block_with_args_yields_error() { Qdk.Stim.Compiler.UnsupportedArgument x unsupported argument in instruction: SELECT - ,-[1:1] + ,-[1:8] 1 | SELECT(0.5) { - : ^^^^^^^^^^^ + : ^^^ 2 | M 0 `---- "#]], @@ -1332,16 +1332,16 @@ fn require_with_args_yields_error() { check( source, &expect![[r#" - Qdk.Stim.Compiler.UnsupportedArgument + Qdk.Stim.Compiler.UnsupportedArgument - x unsupported argument in instruction: REQUIRE - ,-[3:3] - 2 | M 0 - 3 | REQUIRE(0.5) rec[-1] - : ^^^^^^^^^^^^^^^^^^^^ - 4 | } - `---- - "#]], + x unsupported argument in instruction: REQUIRE + ,-[3:11] + 2 | M 0 + 3 | REQUIRE(0.5) rec[-1] + : ^^^ + 4 | } + `---- + "#]], ); } @@ -1769,16 +1769,16 @@ fn notleaked_with_args_yields_error() { check( source, &expect![[r#" - Qdk.Stim.Compiler.UnsupportedArgument + Qdk.Stim.Compiler.UnsupportedArgument - x unsupported argument in instruction: NOTLEAKED - ,-[3:3] - 2 | M 0 - 3 | NOTLEAKED(0.5) rec[-1] - : ^^^^^^^^^^^^^^^^^^^^^^ - 4 | } - `---- - "#]], + x unsupported argument in instruction: NOTLEAKED + ,-[3:13] + 2 | M 0 + 3 | NOTLEAKED(0.5) rec[-1] + : ^^^ + 4 | } + `---- + "#]], ); } diff --git a/source/compiler/stim_compiler/src/qir/tests/single_qubit_gates.rs b/source/compiler/stim_compiler/src/qir/tests/single_qubit_gates.rs index be487ea1fea..1bcf1bf83af 100644 --- a/source/compiler/stim_compiler/src/qir/tests/single_qubit_gates.rs +++ b/source/compiler/stim_compiler/src/qir/tests/single_qubit_gates.rs @@ -50,7 +50,7 @@ fn i_gate_with_args_yields_error() { x unsupported argument in instruction: I ,---- 1 | I(0.1) 0 - : ^^^^^^^^ + : ^^^ `---- "#]], ); diff --git a/source/compiler/stim_compiler/src/qir/tests/two_qubit_gates.rs b/source/compiler/stim_compiler/src/qir/tests/two_qubit_gates.rs index c05a5adc085..7d5bae16b86 100644 --- a/source/compiler/stim_compiler/src/qir/tests/two_qubit_gates.rs +++ b/source/compiler/stim_compiler/src/qir/tests/two_qubit_gates.rs @@ -41,6 +41,22 @@ fn cx_gate_yields_expected_qir() { ); } +#[test] +fn cx_gate_with_repeated_qubit_yields_error() { + check( + "CX 0 0", + &expect![[r#" + Qdk.Stim.Compiler.RepeatedQubit + + x qubit 0 is repeated in instruction: CX + ,---- + 1 | CX 0 0 + : ^ + `---- + "#]], + ); +} + #[test] fn cnot_gate_yields_expected_qir() { let source = "CNOT 0 1"; @@ -441,14 +457,14 @@ fn ii_gate_with_args_yields_error() { check( source, &expect![[r#" - Qdk.Stim.Compiler.UnsupportedArgument - - x unsupported argument in instruction: II - ,---- - 1 | II(0.01) 0 1 - : ^^^^^^^^^^^^ - `---- - "#]], + Qdk.Stim.Compiler.UnsupportedArgument + + x unsupported argument in instruction: II + ,---- + 1 | II(0.01) 0 1 + : ^^^^ + `---- + "#]], ); } @@ -496,6 +512,22 @@ fn iswap_gate_yields_expected_qir() { ); } +#[test] +fn iswap_gate_with_repeated_qubit_yields_error() { + check( + "ISWAP 0 0", + &expect![[r#" + Qdk.Stim.Compiler.RepeatedQubit + + x qubit 0 is repeated in instruction: ISWAP + ,---- + 1 | ISWAP 0 0 + : ^ + `---- + "#]], + ); +} + #[test] fn iswap_dag_gate_yields_expected_qir() { let source = "ISWAP_DAG 0 1";