microsoft/qdk
Publicmirrored from https://github.com/microsoft/qdkAvailable
compiler/qsc_eval/src/intrinsic.rs
408lines · modecode
| 1 | // Copyright (c) Microsoft Corporation. |
| 2 | // Licensed under the MIT License. |
| 3 | |
| 4 | mod utils; |
| 5 | |
| 6 | #[cfg(test)] |
| 7 | mod tests; |
| 8 | |
| 9 | use crate::{ |
| 10 | backend::Backend, |
| 11 | error::PackageSpan, |
| 12 | output::Receiver, |
| 13 | val::{self, unwrap_tuple, Value}, |
| 14 | Error, Rc, |
| 15 | }; |
| 16 | use num_bigint::BigInt; |
| 17 | use rand::{rngs::StdRng, Rng}; |
| 18 | use rustc_hash::{FxHashMap, FxHashSet}; |
| 19 | use std::convert::TryFrom; |
| 20 | |
| 21 | #[allow(clippy::too_many_lines)] |
| 22 | pub(crate) fn call( |
| 23 | name: &str, |
| 24 | name_span: PackageSpan, |
| 25 | arg: Value, |
| 26 | arg_span: PackageSpan, |
| 27 | sim: &mut dyn Backend<ResultType = impl Into<val::Result>>, |
| 28 | rng: &mut StdRng, |
| 29 | out: &mut dyn Receiver, |
| 30 | ) -> Result<Value, Error> { |
| 31 | match name { |
| 32 | "Length" => match arg.unwrap_array().len().try_into() { |
| 33 | Ok(len) => Ok(Value::Int(len)), |
| 34 | Err(_) => Err(Error::ArrayTooLarge(arg_span)), |
| 35 | }, |
| 36 | #[allow(clippy::cast_precision_loss)] |
| 37 | "IntAsDouble" => Ok(Value::Double(arg.unwrap_int() as f64)), |
| 38 | "IntAsBigInt" => Ok(Value::BigInt(BigInt::from(arg.unwrap_int()))), |
| 39 | "DoubleAsStringWithPrecision" => { |
| 40 | let [input, prec_val] = unwrap_tuple(arg); |
| 41 | let prec_int = prec_val.unwrap_int(); |
| 42 | if prec_int < 0 { |
| 43 | Err(Error::InvalidNegativeInt(prec_int, arg_span)) |
| 44 | } else { |
| 45 | let precision = usize::try_from(prec_int).expect("integer value"); |
| 46 | let is_zero = if precision == 0 { "." } else { "" }; |
| 47 | Ok(Value::String(Rc::from(format!( |
| 48 | "{:.*}{}", |
| 49 | precision, |
| 50 | input.unwrap_double(), |
| 51 | is_zero |
| 52 | )))) |
| 53 | } |
| 54 | } |
| 55 | "DumpMachine" => { |
| 56 | let (state, qubit_count) = sim.capture_quantum_state(); |
| 57 | match out.state(state, qubit_count) { |
| 58 | Ok(()) => Ok(Value::unit()), |
| 59 | Err(_) => Err(Error::OutputFail(name_span)), |
| 60 | } |
| 61 | } |
| 62 | "DumpRegister" => { |
| 63 | let qubits = arg.unwrap_array(); |
| 64 | let qubits_len = qubits.len(); |
| 65 | let qubits = qubits |
| 66 | .iter() |
| 67 | .filter_map(|q| q.clone().unwrap_qubit().try_deref().map(|q| q.0)) |
| 68 | .collect::<Vec<_>>(); |
| 69 | if qubits.len() != qubits_len { |
| 70 | return Err(Error::QubitUsedAfterRelease(arg_span)); |
| 71 | } |
| 72 | if qubits.len() != qubits.iter().collect::<FxHashSet<_>>().len() { |
| 73 | return Err(Error::QubitUniqueness(arg_span)); |
| 74 | } |
| 75 | let (state, qubit_count) = sim.capture_quantum_state(); |
| 76 | let state = utils::split_state(&qubits, &state, qubit_count) |
| 77 | .map_err(|()| Error::QubitsNotSeparable(arg_span))?; |
| 78 | match out.state(state, qubits.len()) { |
| 79 | Ok(()) => Ok(Value::unit()), |
| 80 | Err(_) => Err(Error::OutputFail(name_span)), |
| 81 | } |
| 82 | } |
| 83 | "DumpMatrix" => { |
| 84 | let qubits = arg.unwrap_array(); |
| 85 | let qubits_len = qubits.len(); |
| 86 | let qubits = qubits |
| 87 | .iter() |
| 88 | .filter_map(|q| q.clone().unwrap_qubit().try_deref().map(|q| q.0)) |
| 89 | .collect::<Vec<_>>(); |
| 90 | if qubits.len() != qubits_len { |
| 91 | return Err(Error::QubitUsedAfterRelease(arg_span)); |
| 92 | } |
| 93 | if qubits.len() != qubits.iter().collect::<FxHashSet<_>>().len() { |
| 94 | return Err(Error::QubitUniqueness(arg_span)); |
| 95 | } |
| 96 | let (state, qubit_count) = sim.capture_quantum_state(); |
| 97 | let state = utils::split_state(&qubits, &state, qubit_count) |
| 98 | .map_err(|()| Error::QubitsNotSeparable(arg_span))?; |
| 99 | let matrix = utils::state_to_matrix(state, qubits.len() / 2); |
| 100 | match out.matrix(matrix) { |
| 101 | Ok(()) => Ok(Value::unit()), |
| 102 | Err(_) => Err(Error::OutputFail(name_span)), |
| 103 | } |
| 104 | } |
| 105 | "PermuteLabels" => qubit_relabel(arg, arg_span, |q0, q1| sim.qubit_swap_id(q0, q1)), |
| 106 | "Message" => match out.message(&arg.unwrap_string()) { |
| 107 | Ok(()) => Ok(Value::unit()), |
| 108 | Err(_) => Err(Error::OutputFail(name_span)), |
| 109 | }, |
| 110 | "CheckZero" => Ok(Value::Bool( |
| 111 | sim.qubit_is_zero( |
| 112 | arg.unwrap_qubit() |
| 113 | .try_deref() |
| 114 | .ok_or(Error::QubitUsedAfterRelease(arg_span))? |
| 115 | .0, |
| 116 | ), |
| 117 | )), |
| 118 | "ArcCos" => Ok(Value::Double(arg.unwrap_double().acos())), |
| 119 | "ArcSin" => Ok(Value::Double(arg.unwrap_double().asin())), |
| 120 | "ArcTan" => Ok(Value::Double(arg.unwrap_double().atan())), |
| 121 | "ArcTan2" => { |
| 122 | let [x, y] = unwrap_tuple(arg); |
| 123 | Ok(Value::Double(x.unwrap_double().atan2(y.unwrap_double()))) |
| 124 | } |
| 125 | "Cos" => Ok(Value::Double(arg.unwrap_double().cos())), |
| 126 | "Cosh" => Ok(Value::Double(arg.unwrap_double().cosh())), |
| 127 | "Sin" => Ok(Value::Double(arg.unwrap_double().sin())), |
| 128 | "Sinh" => Ok(Value::Double(arg.unwrap_double().sinh())), |
| 129 | "Tan" => Ok(Value::Double(arg.unwrap_double().tan())), |
| 130 | "Tanh" => Ok(Value::Double(arg.unwrap_double().tanh())), |
| 131 | "Sqrt" => Ok(Value::Double(arg.unwrap_double().sqrt())), |
| 132 | "Log" => Ok(Value::Double(arg.unwrap_double().ln())), |
| 133 | "DrawRandomInt" => { |
| 134 | let [lo, hi] = unwrap_tuple(arg); |
| 135 | let lo = lo.unwrap_int(); |
| 136 | let hi = hi.unwrap_int(); |
| 137 | if lo > hi { |
| 138 | Err(Error::EmptyRange(arg_span)) |
| 139 | } else { |
| 140 | Ok(Value::Int(rng.gen_range(lo..=hi))) |
| 141 | } |
| 142 | } |
| 143 | "DrawRandomDouble" => { |
| 144 | let [lo, hi] = unwrap_tuple(arg); |
| 145 | let lo = lo.unwrap_double(); |
| 146 | let hi = hi.unwrap_double(); |
| 147 | if lo > hi { |
| 148 | Err(Error::EmptyRange(arg_span)) |
| 149 | } else { |
| 150 | Ok(Value::Double(rng.gen_range(lo..=hi))) |
| 151 | } |
| 152 | } |
| 153 | "DrawRandomBool" => { |
| 154 | let p = arg.unwrap_double(); |
| 155 | Ok(Value::Bool(rng.gen_bool(p))) |
| 156 | } |
| 157 | #[allow(clippy::cast_possible_truncation)] |
| 158 | "Truncate" => Ok(Value::Int(arg.unwrap_double() as i64)), |
| 159 | "__quantum__qis__ccx__body" => { |
| 160 | three_qubit_gate(|ctl0, ctl1, q| sim.ccx(ctl0, ctl1, q), arg, arg_span) |
| 161 | } |
| 162 | "__quantum__qis__cx__body" => two_qubit_gate(|ctl, q| sim.cx(ctl, q), arg, arg_span), |
| 163 | "__quantum__qis__cy__body" => two_qubit_gate(|ctl, q| sim.cy(ctl, q), arg, arg_span), |
| 164 | "__quantum__qis__cz__body" => two_qubit_gate(|ctl, q| sim.cz(ctl, q), arg, arg_span), |
| 165 | "__quantum__qis__rx__body" => { |
| 166 | one_qubit_rotation(|theta, q| sim.rx(theta, q), arg, arg_span) |
| 167 | } |
| 168 | "__quantum__qis__rxx__body" => { |
| 169 | two_qubit_rotation(|theta, q0, q1| sim.rxx(theta, q0, q1), arg, arg_span) |
| 170 | } |
| 171 | "__quantum__qis__ry__body" => { |
| 172 | one_qubit_rotation(|theta, q| sim.ry(theta, q), arg, arg_span) |
| 173 | } |
| 174 | "__quantum__qis__ryy__body" => { |
| 175 | two_qubit_rotation(|theta, q0, q1| sim.ryy(theta, q0, q1), arg, arg_span) |
| 176 | } |
| 177 | "__quantum__qis__rz__body" => { |
| 178 | one_qubit_rotation(|theta, q| sim.rz(theta, q), arg, arg_span) |
| 179 | } |
| 180 | "__quantum__qis__rzz__body" => { |
| 181 | two_qubit_rotation(|theta, q0, q1| sim.rzz(theta, q0, q1), arg, arg_span) |
| 182 | } |
| 183 | "__quantum__qis__h__body" => one_qubit_gate(|q| sim.h(q), arg, arg_span), |
| 184 | "__quantum__qis__s__body" => one_qubit_gate(|q| sim.s(q), arg, arg_span), |
| 185 | "__quantum__qis__s__adj" => one_qubit_gate(|q| sim.sadj(q), arg, arg_span), |
| 186 | "__quantum__qis__t__body" => one_qubit_gate(|q| sim.t(q), arg, arg_span), |
| 187 | "__quantum__qis__t__adj" => one_qubit_gate(|q| sim.tadj(q), arg, arg_span), |
| 188 | "__quantum__qis__x__body" => one_qubit_gate(|q| sim.x(q), arg, arg_span), |
| 189 | "__quantum__qis__y__body" => one_qubit_gate(|q| sim.y(q), arg, arg_span), |
| 190 | "__quantum__qis__z__body" => one_qubit_gate(|q| sim.z(q), arg, arg_span), |
| 191 | "__quantum__qis__swap__body" => two_qubit_gate(|q0, q1| sim.swap(q0, q1), arg, arg_span), |
| 192 | "__quantum__qis__reset__body" => one_qubit_gate(|q| sim.reset(q), arg, arg_span), |
| 193 | "__quantum__qis__m__body" => Ok(Value::Result( |
| 194 | sim.m(arg |
| 195 | .unwrap_qubit() |
| 196 | .try_deref() |
| 197 | .ok_or(Error::QubitUsedAfterRelease(arg_span))? |
| 198 | .0) |
| 199 | .into(), |
| 200 | )), |
| 201 | "__quantum__qis__mresetz__body" => Ok(Value::Result( |
| 202 | sim.mresetz( |
| 203 | arg.unwrap_qubit() |
| 204 | .try_deref() |
| 205 | .ok_or(Error::QubitUsedAfterRelease(arg_span))? |
| 206 | .0, |
| 207 | ) |
| 208 | .into(), |
| 209 | )), |
| 210 | _ => { |
| 211 | let qubits = arg.qubits(); |
| 212 | let qubits_len = qubits.len(); |
| 213 | let qubits = qubits |
| 214 | .iter() |
| 215 | .filter_map(|q| q.try_deref().map(|q| q.0)) |
| 216 | .collect::<Vec<_>>(); |
| 217 | if qubits.len() != qubits_len { |
| 218 | return Err(Error::QubitUsedAfterRelease(arg_span)); |
| 219 | } |
| 220 | if let Some(result) = sim.custom_intrinsic(name, arg) { |
| 221 | match result { |
| 222 | Ok(value) => Ok(value), |
| 223 | Err(message) => Err(Error::IntrinsicFail(name.to_string(), message, name_span)), |
| 224 | } |
| 225 | } else { |
| 226 | Err(Error::UnknownIntrinsic(name.to_string(), name_span)) |
| 227 | } |
| 228 | } |
| 229 | } |
| 230 | } |
| 231 | |
| 232 | fn one_qubit_gate( |
| 233 | mut gate: impl FnMut(usize), |
| 234 | arg: Value, |
| 235 | arg_span: PackageSpan, |
| 236 | ) -> Result<Value, Error> { |
| 237 | gate( |
| 238 | arg.unwrap_qubit() |
| 239 | .try_deref() |
| 240 | .ok_or(Error::QubitUsedAfterRelease(arg_span))? |
| 241 | .0, |
| 242 | ); |
| 243 | Ok(Value::unit()) |
| 244 | } |
| 245 | |
| 246 | fn two_qubit_gate( |
| 247 | mut gate: impl FnMut(usize, usize), |
| 248 | arg: Value, |
| 249 | arg_span: PackageSpan, |
| 250 | ) -> Result<Value, Error> { |
| 251 | let [x, y] = unwrap_tuple(arg); |
| 252 | if x == y { |
| 253 | Err(Error::QubitUniqueness(arg_span)) |
| 254 | } else { |
| 255 | gate( |
| 256 | x.unwrap_qubit() |
| 257 | .try_deref() |
| 258 | .ok_or(Error::QubitUsedAfterRelease(arg_span))? |
| 259 | .0, |
| 260 | y.unwrap_qubit() |
| 261 | .try_deref() |
| 262 | .ok_or(Error::QubitUsedAfterRelease(arg_span))? |
| 263 | .0, |
| 264 | ); |
| 265 | Ok(Value::unit()) |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | fn one_qubit_rotation( |
| 270 | mut gate: impl FnMut(f64, usize), |
| 271 | arg: Value, |
| 272 | arg_span: PackageSpan, |
| 273 | ) -> Result<Value, Error> { |
| 274 | let [x, y] = unwrap_tuple(arg); |
| 275 | let angle = x.unwrap_double(); |
| 276 | if angle.is_nan() || angle.is_infinite() { |
| 277 | Err(Error::InvalidRotationAngle(angle, arg_span)) |
| 278 | } else { |
| 279 | gate( |
| 280 | angle, |
| 281 | y.unwrap_qubit() |
| 282 | .try_deref() |
| 283 | .ok_or(Error::QubitUsedAfterRelease(arg_span))? |
| 284 | .0, |
| 285 | ); |
| 286 | Ok(Value::unit()) |
| 287 | } |
| 288 | } |
| 289 | |
| 290 | fn three_qubit_gate( |
| 291 | mut gate: impl FnMut(usize, usize, usize), |
| 292 | arg: Value, |
| 293 | arg_span: PackageSpan, |
| 294 | ) -> Result<Value, Error> { |
| 295 | let [x, y, z] = unwrap_tuple(arg); |
| 296 | if x == y || y == z || x == z { |
| 297 | Err(Error::QubitUniqueness(arg_span)) |
| 298 | } else { |
| 299 | gate( |
| 300 | x.unwrap_qubit() |
| 301 | .try_deref() |
| 302 | .ok_or(Error::QubitUsedAfterRelease(arg_span))? |
| 303 | .0, |
| 304 | y.unwrap_qubit() |
| 305 | .try_deref() |
| 306 | .ok_or(Error::QubitUsedAfterRelease(arg_span))? |
| 307 | .0, |
| 308 | z.unwrap_qubit() |
| 309 | .try_deref() |
| 310 | .ok_or(Error::QubitUsedAfterRelease(arg_span))? |
| 311 | .0, |
| 312 | ); |
| 313 | Ok(Value::unit()) |
| 314 | } |
| 315 | } |
| 316 | |
| 317 | fn two_qubit_rotation( |
| 318 | mut gate: impl FnMut(f64, usize, usize), |
| 319 | arg: Value, |
| 320 | arg_span: PackageSpan, |
| 321 | ) -> Result<Value, Error> { |
| 322 | let [x, y, z] = unwrap_tuple(arg); |
| 323 | let angle = x.unwrap_double(); |
| 324 | if y == z { |
| 325 | Err(Error::QubitUniqueness(arg_span)) |
| 326 | } else if angle.is_nan() || angle.is_infinite() { |
| 327 | Err(Error::InvalidRotationAngle(angle, arg_span)) |
| 328 | } else { |
| 329 | gate( |
| 330 | angle, |
| 331 | y.unwrap_qubit() |
| 332 | .try_deref() |
| 333 | .ok_or(Error::QubitUsedAfterRelease(arg_span))? |
| 334 | .0, |
| 335 | z.unwrap_qubit() |
| 336 | .try_deref() |
| 337 | .ok_or(Error::QubitUsedAfterRelease(arg_span))? |
| 338 | .0, |
| 339 | ); |
| 340 | Ok(Value::unit()) |
| 341 | } |
| 342 | } |
| 343 | |
| 344 | /// Performs relabeling of qubits from the a given left array to the corresponding right array. |
| 345 | /// The function will swap qubits with the given function to match the new relabeling, returning an error |
| 346 | /// if the qubits are not unique or if the relabeling is not a valid permutation. |
| 347 | pub fn qubit_relabel( |
| 348 | arg: Value, |
| 349 | arg_span: PackageSpan, |
| 350 | mut swap: impl FnMut(usize, usize), |
| 351 | ) -> Result<Value, Error> { |
| 352 | let [left, right] = unwrap_tuple(arg); |
| 353 | let left = left.unwrap_array(); |
| 354 | let left_len = left.len(); |
| 355 | let left = left |
| 356 | .iter() |
| 357 | .filter_map(|q| q.clone().unwrap_qubit().try_deref().map(|q| q.0)) |
| 358 | .collect::<Vec<_>>(); |
| 359 | if left.len() != left_len { |
| 360 | return Err(Error::QubitUsedAfterRelease(arg_span)); |
| 361 | } |
| 362 | let right = right.unwrap_array(); |
| 363 | let right_len = right.len(); |
| 364 | let right = right |
| 365 | .iter() |
| 366 | .filter_map(|q| q.clone().unwrap_qubit().try_deref().map(|q| q.0)) |
| 367 | .collect::<Vec<_>>(); |
| 368 | if right.len() != right_len { |
| 369 | return Err(Error::QubitUsedAfterRelease(arg_span)); |
| 370 | } |
| 371 | let left_set = left.iter().collect::<FxHashSet<_>>(); |
| 372 | let right_set = right.iter().collect::<FxHashSet<_>>(); |
| 373 | if left.len() != left_set.len() || right.len() != right_set.len() { |
| 374 | return Err(Error::QubitUniqueness(arg_span)); |
| 375 | } |
| 376 | if left_set != right_set { |
| 377 | return Err(Error::RelabelingMismatch(arg_span)); |
| 378 | } |
| 379 | |
| 380 | // Start with a mapping of each qubit to itself. |
| 381 | let mut mappings: FxHashMap<usize, usize> = |
| 382 | left.iter().copied().zip(left.iter().copied()).collect(); |
| 383 | for (l, r) in left.into_iter().zip(right.into_iter()) { |
| 384 | // Trivial case where the qubit is already mapped to itself in the relabel, which can be short circuited. |
| 385 | if l == r { |
| 386 | continue; |
| 387 | } |
| 388 | |
| 389 | // Check what each label currently maps to. |
| 390 | let mapped_l = *mappings.get(&l).expect("mapped qubit should be present"); |
| 391 | let mapped_r = *mappings.get(&r).expect("mapped qubit should be present"); |
| 392 | |
| 393 | // We only need to swap if the label is not pointing to the correct qubit. |
| 394 | if mapped_l != r && mapped_r != l { |
| 395 | // Do a reverse lookup to find which label is currently mapped to the desired right qubit. |
| 396 | // This tells us which label to use in the swap, which we will use in the update of the mappings too. |
| 397 | let label_r = *mappings |
| 398 | .keys() |
| 399 | .find(|k| mappings[*k] == r) |
| 400 | .expect("mapped qubit should be present as both key and value"); |
| 401 | swap(l, label_r); |
| 402 | mappings.insert(label_r, mapped_l); |
| 403 | mappings.insert(l, mapped_r); |
| 404 | } |
| 405 | } |
| 406 | |
| 407 | Ok(Value::unit()) |
| 408 | } |
| 409 | |