microsoft/qdk
Publicmirrored fromhttps://github.com/microsoft/qdkAvailable
library/src/tests.rs
266lines · modecode
| 1 | // Copyright (c) Microsoft Corporation. |
| 2 | // Licensed under the MIT License. |
| 3 | |
| 4 | mod arithmetic; |
| 5 | mod arrays; |
| 6 | mod canon; |
| 7 | mod convert; |
| 8 | mod core; |
| 9 | mod diagnostics; |
| 10 | mod intrinsic; |
| 11 | mod logical; |
| 12 | mod math; |
| 13 | mod measurement; |
| 14 | mod openqasm; |
| 15 | mod state_preparation; |
| 16 | mod table_lookup; |
| 17 | |
| 18 | use indoc::indoc; |
| 19 | use qsc::{ |
| 20 | Backend, LanguageFeatures, PackageType, SourceMap, SparseSim, |
| 21 | interpret::{self, GenericReceiver, Interpreter, Value}, |
| 22 | target::Profile, |
| 23 | }; |
| 24 | |
| 25 | /// # Panics |
| 26 | /// |
| 27 | /// Will panic if compilation fails or the result is not the same as expected. |
| 28 | /// NOTE: Floating point numbers in tuples are compared taking precision into |
| 29 | /// account so that results of calculations can also be compared. |
| 30 | pub fn test_expression(expr: &str, expected: &Value) -> String { |
| 31 | test_expression_with_lib(expr, "", expected) |
| 32 | } |
| 33 | |
| 34 | pub fn test_expression_fails(expr: &str) -> String { |
| 35 | test_expression_fails_with_lib_and_profile_and_sim( |
| 36 | expr, |
| 37 | "", |
| 38 | Profile::Unrestricted, |
| 39 | &mut SparseSim::default(), |
| 40 | ) |
| 41 | } |
| 42 | |
| 43 | pub fn test_expression_with_lib(expr: &str, lib: &str, expected: &Value) -> String { |
| 44 | test_expression_with_lib_and_profile(expr, lib, Profile::Unrestricted, expected) |
| 45 | } |
| 46 | |
| 47 | pub fn test_expression_with_lib_and_profile( |
| 48 | expr: &str, |
| 49 | lib: &str, |
| 50 | profile: Profile, |
| 51 | expected: &Value, |
| 52 | ) -> String { |
| 53 | let mut sim = SparseSim::default(); |
| 54 | test_expression_with_lib_and_profile_and_sim(expr, lib, profile, &mut sim, expected) |
| 55 | } |
| 56 | |
| 57 | pub fn test_expression_with_lib_and_profile_and_sim( |
| 58 | expr: &str, |
| 59 | lib: &str, |
| 60 | profile: Profile, |
| 61 | sim: &mut impl Backend, |
| 62 | expected: &Value, |
| 63 | ) -> String { |
| 64 | let mut stdout = vec![]; |
| 65 | let mut out = GenericReceiver::new(&mut stdout); |
| 66 | |
| 67 | let sources = SourceMap::new([("test".into(), lib.into())], Some(expr.into())); |
| 68 | |
| 69 | let (std_id, store) = qsc::compile::package_store_with_stdlib(profile.into()); |
| 70 | |
| 71 | let mut interpreter = Interpreter::new( |
| 72 | sources, |
| 73 | PackageType::Exe, |
| 74 | profile.into(), |
| 75 | LanguageFeatures::default(), |
| 76 | store, |
| 77 | &[(std_id, None)], |
| 78 | ) |
| 79 | .expect("test should compile"); |
| 80 | |
| 81 | let result = interpreter |
| 82 | .eval_entry_with_sim(sim, &mut out) |
| 83 | .expect("test should run successfully"); |
| 84 | |
| 85 | match (&expected, result) { |
| 86 | (&Value::Tuple(tup1, _), Value::Tuple(tup2, _)) if tup1.len() == tup2.len() => { |
| 87 | // If both values are tuples of the same length, we crack them open and compare elements |
| 88 | for (value1, value2) in tup1.iter().zip(tup2.iter()) { |
| 89 | if let (Value::Double(double1), Value::Double(double2)) = (value1, value2) { |
| 90 | // If both elements are doubles, we use approximate comparison |
| 91 | assert_doubles_almost_equal(*double1, *double2); |
| 92 | } else { |
| 93 | assert_eq!(value1, value2); |
| 94 | } |
| 95 | } |
| 96 | } |
| 97 | (&Value::Double(double1), Value::Double(double2)) => { |
| 98 | assert_doubles_almost_equal(*double1, double2); |
| 99 | } |
| 100 | (&expected, result) => assert_eq!(expected, &result), |
| 101 | } |
| 102 | |
| 103 | String::from_utf8(stdout).expect("stdout should be valid utf8") |
| 104 | } |
| 105 | |
| 106 | pub fn test_expression_fails_with_lib_and_profile_and_sim( |
| 107 | expr: &str, |
| 108 | lib: &str, |
| 109 | profile: Profile, |
| 110 | sim: &mut impl Backend, |
| 111 | ) -> String { |
| 112 | let mut stdout = vec![]; |
| 113 | let mut out = GenericReceiver::new(&mut stdout); |
| 114 | |
| 115 | let sources = SourceMap::new([("test".into(), lib.into())], Some(expr.into())); |
| 116 | |
| 117 | let (std_id, store) = qsc::compile::package_store_with_stdlib(profile.into()); |
| 118 | |
| 119 | let mut interpreter = Interpreter::new( |
| 120 | sources, |
| 121 | PackageType::Exe, |
| 122 | profile.into(), |
| 123 | LanguageFeatures::default(), |
| 124 | store, |
| 125 | &[(std_id, None)], |
| 126 | ) |
| 127 | .expect("test should compile"); |
| 128 | |
| 129 | let result = interpreter |
| 130 | .eval_entry_with_sim(sim, &mut out) |
| 131 | .expect_err("test should run successfully"); |
| 132 | |
| 133 | assert!( |
| 134 | result.len() == 1, |
| 135 | "Expected a single error, got {:?}", |
| 136 | result.len() |
| 137 | ); |
| 138 | let interpret::Error::Eval(err) = &result[0] else { |
| 139 | panic!("Expected an Eval error, got {:?}", result[0]); |
| 140 | }; |
| 141 | |
| 142 | err.error().error().to_string() |
| 143 | } |
| 144 | |
| 145 | /// # Panics |
| 146 | /// |
| 147 | /// Will panic if f64 values are significantly different. |
| 148 | fn assert_doubles_almost_equal(val1: f64, val2: f64) { |
| 149 | let val1_abs = val1.abs(); |
| 150 | let val2_abs = val2.abs(); |
| 151 | if val1_abs < f64::MIN_POSITIVE && val2_abs < f64::MIN_POSITIVE { |
| 152 | // Note, that f64::MIN_POSITIVE is not the smallest representable positive number. |
| 153 | return; |
| 154 | } |
| 155 | assert!( |
| 156 | ((val1 - val2).abs() / (val1_abs + val2_abs)) < 1e-15, |
| 157 | "Significant difference between expected and actual values: val1={val1}, val2={val2}." |
| 158 | ); |
| 159 | } |
| 160 | |
| 161 | // |
| 162 | // Core namespace |
| 163 | // |
| 164 | #[test] |
| 165 | fn check_repeated() { |
| 166 | test_expression("Repeated(Zero, 0)", &Value::Array(vec![].into())); |
| 167 | test_expression( |
| 168 | "Repeated(One, 1)", |
| 169 | &Value::Array(vec![Value::RESULT_ONE].into()), |
| 170 | ); |
| 171 | test_expression( |
| 172 | "Repeated(1, 2)", |
| 173 | &Value::Array(vec![Value::Int(1), Value::Int(1)].into()), |
| 174 | ); |
| 175 | test_expression( |
| 176 | "Repeated(true, 3)", |
| 177 | &Value::Array(vec![Value::Bool(true), Value::Bool(true), Value::Bool(true)].into()), |
| 178 | ); |
| 179 | } |
| 180 | |
| 181 | #[test] |
| 182 | fn check_exp_with_cnot() { |
| 183 | // This decomposition only holds if the magnitude of the angle used in Exp is correct and if the |
| 184 | // sign convention between Rx, Rz, and Exp is consistent. |
| 185 | test_expression( |
| 186 | indoc! {r#"{ |
| 187 | import Std.Diagnostics.*; |
| 188 | import Std.Math.*; |
| 189 | |
| 190 | use (aux, control, target) = (Qubit(), Qubit(), Qubit()); |
| 191 | within { |
| 192 | H(aux); |
| 193 | CNOT(aux, control); |
| 194 | CNOT(aux, target); |
| 195 | } |
| 196 | apply { |
| 197 | let theta = PI() / 4.0; |
| 198 | Rx(-2.0 * theta, target); |
| 199 | Rz(-2.0 * theta, control); |
| 200 | Adjoint Exp([PauliZ, PauliX], theta, [control, target]); |
| 201 | |
| 202 | Adjoint CNOT(control, target); |
| 203 | } |
| 204 | |
| 205 | CheckAllZero([aux, control, target]) |
| 206 | }"#}, |
| 207 | &Value::Bool(true), |
| 208 | ); |
| 209 | } |
| 210 | |
| 211 | #[test] |
| 212 | fn check_exp_with_swap() { |
| 213 | // This decomposition only holds if the magnitude of the angle used in Exp is correct. |
| 214 | test_expression( |
| 215 | indoc! {r#"{ |
| 216 | import Std.Diagnostics.*; |
| 217 | import Std.Math.*; |
| 218 | |
| 219 | use (aux, qs) = (Qubit(), Qubit[2]); |
| 220 | within { |
| 221 | H(aux); |
| 222 | CNOT(aux, qs[0]); |
| 223 | CNOT(aux, qs[1]); |
| 224 | } |
| 225 | apply { |
| 226 | let theta = PI() / 4.0; |
| 227 | Exp([PauliX, PauliX], theta, qs); |
| 228 | Exp([PauliY, PauliY], theta, qs); |
| 229 | Exp([PauliZ, PauliZ], theta, qs); |
| 230 | |
| 231 | Adjoint SWAP(qs[0], qs[1]); |
| 232 | } |
| 233 | |
| 234 | CheckAllZero([aux] + qs) |
| 235 | }"#}, |
| 236 | &Value::Bool(true), |
| 237 | ); |
| 238 | } |
| 239 | |
| 240 | #[test] |
| 241 | fn check_base_profile_measure_resets_aux_qubits() { |
| 242 | test_expression_with_lib_and_profile( |
| 243 | indoc! {"{ |
| 244 | use q = Qubit(); |
| 245 | X(q); |
| 246 | let result = M(q); |
| 247 | Reset(q); |
| 248 | result |
| 249 | }"}, |
| 250 | "", |
| 251 | Profile::Base, |
| 252 | &Value::RESULT_ONE, |
| 253 | ); |
| 254 | } |
| 255 | |
| 256 | // just tests a single case of the stdlib reexports for the modern api, |
| 257 | // to ensure that reexporting functionality doesn't break |
| 258 | #[test] |
| 259 | fn stdlib_reexport_single_case() { |
| 260 | test_expression( |
| 261 | r#" { |
| 262 | import Std.Arrays.Count; |
| 263 | }"#, |
| 264 | &Value::Tuple(vec![].into(), None), |
| 265 | ); |
| 266 | } |
| 267 | |