microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/fix-2145

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

compiler/qsc_eval/src/backend.rs

682lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use crate::val::Value;
5use crate::{noise::PauliNoise, val::unwrap_tuple};
6use ndarray::Array2;
7use num_bigint::BigUint;
8use num_complex::Complex;
9use quantum_sparse_sim::QuantumSim;
10use rand::{rngs::StdRng, Rng, RngCore, SeedableRng};
11
12#[cfg(test)]
13mod noise_tests;
14
15/// The trait that must be implemented by a quantum backend, whose functions will be invoked when
16/// quantum intrinsics are called.
17pub trait Backend {
18 type ResultType;
19
20 fn ccx(&mut self, _ctl0: usize, _ctl1: usize, _q: usize) {
21 unimplemented!("ccx gate");
22 }
23 fn cx(&mut self, _ctl: usize, _q: usize) {
24 unimplemented!("cx gate");
25 }
26 fn cy(&mut self, _ctl: usize, _q: usize) {
27 unimplemented!("cy gate");
28 }
29 fn cz(&mut self, _ctl: usize, _q: usize) {
30 unimplemented!("cz gate");
31 }
32 fn h(&mut self, _q: usize) {
33 unimplemented!("h gate");
34 }
35 fn m(&mut self, _q: usize) -> Self::ResultType {
36 unimplemented!("m operation");
37 }
38 fn mresetz(&mut self, _q: usize) -> Self::ResultType {
39 unimplemented!("mresetz operation");
40 }
41 fn reset(&mut self, _q: usize) {
42 unimplemented!("reset gate");
43 }
44 fn rx(&mut self, _theta: f64, _q: usize) {
45 unimplemented!("rx gate");
46 }
47 fn rxx(&mut self, _theta: f64, _q0: usize, _q1: usize) {
48 unimplemented!("rxx gate");
49 }
50 fn ry(&mut self, _theta: f64, _q: usize) {
51 unimplemented!("ry gate");
52 }
53 fn ryy(&mut self, _theta: f64, _q0: usize, _q1: usize) {
54 unimplemented!("ryy gate");
55 }
56 fn rz(&mut self, _theta: f64, _q: usize) {
57 unimplemented!("rz gate");
58 }
59 fn rzz(&mut self, _theta: f64, _q0: usize, _q1: usize) {
60 unimplemented!("rzz gate");
61 }
62 fn sadj(&mut self, _q: usize) {
63 unimplemented!("sadj gate");
64 }
65 fn s(&mut self, _q: usize) {
66 unimplemented!("s gate");
67 }
68 fn sx(&mut self, _q: usize) {
69 unimplemented!("sx gate");
70 }
71 fn swap(&mut self, _q0: usize, _q1: usize) {
72 unimplemented!("swap gate");
73 }
74 fn tadj(&mut self, _q: usize) {
75 unimplemented!("tadj gate");
76 }
77 fn t(&mut self, _q: usize) {
78 unimplemented!("t gate");
79 }
80 fn x(&mut self, _q: usize) {
81 unimplemented!("x gate");
82 }
83 fn y(&mut self, _q: usize) {
84 unimplemented!("y gate");
85 }
86 fn z(&mut self, _q: usize) {
87 unimplemented!("z gate");
88 }
89 fn qubit_allocate(&mut self) -> usize {
90 unimplemented!("qubit_allocate operation");
91 }
92 /// `false` indicates that the qubit was in a non-zero state before the release,
93 /// but should have been in the zero state.
94 /// `true` otherwise. This includes the case when the qubit was in
95 /// a non-zero state during a noisy simulation, which is allowed.
96 fn qubit_release(&mut self, _q: usize) -> bool {
97 unimplemented!("qubit_release operation");
98 }
99 fn qubit_swap_id(&mut self, _q0: usize, _q1: usize) {
100 unimplemented!("qubit_swap_id operation");
101 }
102 fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
103 unimplemented!("capture_quantum_state operation");
104 }
105 fn qubit_is_zero(&mut self, _q: usize) -> bool {
106 unimplemented!("qubit_is_zero operation");
107 }
108 /// Executes custom intrinsic specified by `_name`.
109 /// Returns None if this intrinsic is unknown.
110 /// Otherwise returns Some(Result), with the Result from intrinsic.
111 fn custom_intrinsic(&mut self, _name: &str, _arg: Value) -> Option<Result<Value, String>> {
112 None
113 }
114 fn set_seed(&mut self, _seed: Option<u64>) {}
115}
116
117/// Default backend used when targeting sparse simulation.
118pub struct SparseSim {
119 /// Noiseless Sparse simulator to be used by this instance.
120 pub sim: QuantumSim,
121 /// Pauli noise that is applied after a gate or before a measurement is executed.
122 /// Service functions aren't subject to noise.
123 pub noise: PauliNoise,
124 /// Random number generator to sample Pauli noise.
125 /// Noise is not applied when rng is None.
126 pub rng: Option<StdRng>,
127}
128
129impl Default for SparseSim {
130 fn default() -> Self {
131 Self::new()
132 }
133}
134
135impl SparseSim {
136 #[must_use]
137 pub fn new() -> Self {
138 Self {
139 sim: QuantumSim::new(None),
140 noise: PauliNoise::default(),
141 rng: None,
142 }
143 }
144
145 #[must_use]
146 pub fn new_with_noise(noise: &PauliNoise) -> Self {
147 let mut sim = SparseSim::new();
148 sim.set_noise(noise);
149 sim
150 }
151
152 fn set_noise(&mut self, noise: &PauliNoise) {
153 self.noise = *noise;
154 if noise.is_noiseless() {
155 self.rng = None;
156 } else {
157 self.rng = Some(StdRng::from_entropy());
158 }
159 }
160
161 #[must_use]
162 fn is_noiseless(&self) -> bool {
163 self.rng.is_none()
164 }
165
166 fn apply_noise(&mut self, q: usize) {
167 if let Some(rng) = &mut self.rng {
168 let p = rng.gen_range(0.0..1.0);
169 if p >= self.noise.distribution[2] {
170 // In the most common case we don't apply noise
171 } else if p < self.noise.distribution[0] {
172 self.sim.x(q);
173 } else if p < self.noise.distribution[1] {
174 self.sim.y(q);
175 } else {
176 self.sim.z(q);
177 }
178 }
179 // No noise applied if rng is None.
180 }
181}
182
183impl Backend for SparseSim {
184 type ResultType = bool;
185
186 fn ccx(&mut self, ctl0: usize, ctl1: usize, q: usize) {
187 self.sim.mcx(&[ctl0, ctl1], q);
188 self.apply_noise(ctl0);
189 self.apply_noise(ctl1);
190 self.apply_noise(q);
191 }
192
193 fn cx(&mut self, ctl: usize, q: usize) {
194 self.sim.mcx(&[ctl], q);
195 self.apply_noise(ctl);
196 self.apply_noise(q);
197 }
198
199 fn cy(&mut self, ctl: usize, q: usize) {
200 self.sim.mcy(&[ctl], q);
201 self.apply_noise(ctl);
202 self.apply_noise(q);
203 }
204
205 fn cz(&mut self, ctl: usize, q: usize) {
206 self.sim.mcz(&[ctl], q);
207 self.apply_noise(ctl);
208 self.apply_noise(q);
209 }
210
211 fn h(&mut self, q: usize) {
212 self.sim.h(q);
213 self.apply_noise(q);
214 }
215
216 fn m(&mut self, q: usize) -> Self::ResultType {
217 self.apply_noise(q);
218 self.sim.measure(q)
219 }
220
221 fn mresetz(&mut self, q: usize) -> Self::ResultType {
222 self.apply_noise(q); // Applying noise before measurement
223 let res = self.sim.measure(q);
224 if res {
225 self.sim.x(q);
226 }
227 self.apply_noise(q); // Applying noise after reset
228 res
229 }
230
231 fn reset(&mut self, q: usize) {
232 self.mresetz(q);
233 // Noise applied in mresetz.
234 }
235
236 fn rx(&mut self, theta: f64, q: usize) {
237 self.sim.rx(theta, q);
238 self.apply_noise(q);
239 }
240
241 fn rxx(&mut self, theta: f64, q0: usize, q1: usize) {
242 self.sim.h(q0);
243 self.sim.h(q1);
244 self.sim.mcx(&[q1], q0);
245 self.sim.rz(theta, q0);
246 self.sim.mcx(&[q1], q0);
247 self.sim.h(q1);
248 self.sim.h(q0);
249 self.apply_noise(q0);
250 self.apply_noise(q1);
251 }
252
253 fn ry(&mut self, theta: f64, q: usize) {
254 self.sim.ry(theta, q);
255 self.apply_noise(q);
256 }
257
258 fn ryy(&mut self, theta: f64, q0: usize, q1: usize) {
259 self.sim.h(q0);
260 self.sim.s(q0);
261 self.sim.h(q0);
262 self.sim.h(q1);
263 self.sim.s(q1);
264 self.sim.h(q1);
265 self.sim.mcx(&[q1], q0);
266 self.sim.rz(theta, q0);
267 self.sim.mcx(&[q1], q0);
268 self.sim.h(q1);
269 self.sim.sadj(q1);
270 self.sim.h(q1);
271 self.sim.h(q0);
272 self.sim.sadj(q0);
273 self.sim.h(q0);
274 self.apply_noise(q0);
275 self.apply_noise(q1);
276 }
277
278 fn rz(&mut self, theta: f64, q: usize) {
279 self.sim.rz(theta, q);
280 self.apply_noise(q);
281 }
282
283 fn rzz(&mut self, theta: f64, q0: usize, q1: usize) {
284 self.sim.mcx(&[q1], q0);
285 self.sim.rz(theta, q0);
286 self.sim.mcx(&[q1], q0);
287 self.apply_noise(q0);
288 self.apply_noise(q1);
289 }
290
291 fn sadj(&mut self, q: usize) {
292 self.sim.sadj(q);
293 self.apply_noise(q);
294 }
295
296 fn s(&mut self, q: usize) {
297 self.sim.s(q);
298 self.apply_noise(q);
299 }
300
301 fn sx(&mut self, q: usize) {
302 self.sim.h(q);
303 self.sim.s(q);
304 self.sim.h(q);
305 self.apply_noise(q);
306 }
307
308 fn swap(&mut self, q0: usize, q1: usize) {
309 self.sim.swap_qubit_ids(q0, q1);
310 self.apply_noise(q0);
311 self.apply_noise(q1);
312 }
313
314 fn tadj(&mut self, q: usize) {
315 self.sim.tadj(q);
316 self.apply_noise(q);
317 }
318
319 fn t(&mut self, q: usize) {
320 self.sim.t(q);
321 self.apply_noise(q);
322 }
323
324 fn x(&mut self, q: usize) {
325 self.sim.x(q);
326 self.apply_noise(q);
327 }
328
329 fn y(&mut self, q: usize) {
330 self.sim.y(q);
331 self.apply_noise(q);
332 }
333
334 fn z(&mut self, q: usize) {
335 self.sim.z(q);
336 self.apply_noise(q);
337 }
338
339 fn qubit_allocate(&mut self) -> usize {
340 // Fresh qubit start in ground state even with noise.
341 self.sim.allocate()
342 }
343
344 fn qubit_release(&mut self, q: usize) -> bool {
345 if self.is_noiseless() {
346 let was_zero = self.sim.qubit_is_zero(q);
347 self.sim.release(q);
348 was_zero
349 } else {
350 self.sim.release(q);
351 true
352 }
353 }
354
355 fn qubit_swap_id(&mut self, q0: usize, q1: usize) {
356 // This is a service function rather than a gate so it doesn't incur noise.
357 self.sim.swap_qubit_ids(q0, q1);
358 }
359
360 fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
361 let (state, count) = self.sim.get_state();
362 // Because the simulator returns the state indices with opposite endianness from the
363 // expected one, we need to reverse the bit order of the indices.
364 let mut new_state = state
365 .into_iter()
366 .map(|(idx, val)| {
367 let mut new_idx = BigUint::default();
368 for i in 0..(count as u64) {
369 if idx.bit((count as u64) - 1 - i) {
370 new_idx.set_bit(i, true);
371 }
372 }
373 (new_idx, val)
374 })
375 .collect::<Vec<_>>();
376 new_state.sort_unstable_by(|a, b| a.0.cmp(&b.0));
377 (new_state, count)
378 }
379
380 fn qubit_is_zero(&mut self, q: usize) -> bool {
381 // This is a service function rather than a measurement so it doesn't incur noise.
382 self.sim.qubit_is_zero(q)
383 }
384
385 fn custom_intrinsic(&mut self, name: &str, arg: Value) -> Option<Result<Value, String>> {
386 // These intrinsics aren't subject to noise.
387 match name {
388 "GlobalPhase" => {
389 // Apply a global phase to the simulation by doing an Rz to a fresh qubit.
390 // The controls list may be empty, in which case the phase is applied unconditionally.
391 let [ctls_val, theta] = &*arg.unwrap_tuple() else {
392 panic!("tuple arity for GlobalPhase intrinsic should be 2");
393 };
394 let ctls = ctls_val
395 .clone()
396 .unwrap_array()
397 .iter()
398 .map(|q| q.clone().unwrap_qubit().deref().0)
399 .collect::<Vec<_>>();
400 let q = self.sim.allocate();
401 // The new qubit is by-definition in the |0⟩ state, so by reversing the sign of the
402 // angle we can apply the phase to the entire state without increasing its size in memory.
403 self.sim
404 .mcrz(&ctls, -2.0 * theta.clone().unwrap_double(), q);
405 self.sim.release(q);
406 Some(Ok(Value::unit()))
407 }
408 "BeginEstimateCaching" => Some(Ok(Value::Bool(true))),
409 "EndEstimateCaching"
410 | "AccountForEstimatesInternal"
411 | "BeginRepeatEstimatesInternal"
412 | "EndRepeatEstimatesInternal" => Some(Ok(Value::unit())),
413 "ConfigurePauliNoise" => {
414 let [xv, yv, zv] = &*arg.unwrap_tuple() else {
415 panic!("tuple arity for ConfigurePauliNoise intrinsic should be 3");
416 };
417 let px = xv.get_double();
418 let py = yv.get_double();
419 let pz = zv.get_double();
420 match PauliNoise::from_probabilities(px, py, pz) {
421 Ok(noise) => {
422 self.set_noise(&noise);
423 Some(Ok(Value::unit()))
424 }
425 Err(message) => Some(Err(message)),
426 }
427 }
428 "ApplyIdleNoise" => {
429 let q = arg.unwrap_qubit().deref().0;
430 self.apply_noise(q);
431 Some(Ok(Value::unit()))
432 }
433 "Apply" => {
434 let [matrix, qubits] = unwrap_tuple(arg);
435 let qubits = qubits
436 .unwrap_array()
437 .iter()
438 .filter_map(|q| q.clone().unwrap_qubit().try_deref().map(|q| q.0))
439 .collect::<Vec<_>>();
440 let matrix = unwrap_matrix_as_array2(matrix, &qubits);
441
442 // Confirm the matrix is unitary by checking if multiplying it by its adjoint gives the identity matrix (up to numerical precision).
443 let adj = matrix.t().map(Complex::<f64>::conj);
444 if (matrix.dot(&adj) - Array2::<Complex<f64>>::eye(1 << qubits.len()))
445 .map(|x| x.norm())
446 .sum()
447 > 1e-9
448 {
449 return Some(Err("matrix is not unitary".to_string()));
450 }
451
452 self.sim.apply(&matrix, &qubits, None);
453
454 Some(Ok(Value::unit()))
455 }
456 _ => None,
457 }
458 }
459
460 fn set_seed(&mut self, seed: Option<u64>) {
461 if let Some(seed) = seed {
462 if !self.is_noiseless() {
463 self.rng = Some(StdRng::seed_from_u64(seed));
464 }
465 self.sim.set_rng_seed(seed);
466 } else {
467 if !self.is_noiseless() {
468 self.rng = Some(StdRng::from_entropy());
469 }
470 self.sim.set_rng_seed(rand::thread_rng().next_u64());
471 }
472 }
473}
474
475fn unwrap_matrix_as_array2(matrix: Value, qubits: &[usize]) -> Array2<Complex<f64>> {
476 let matrix: Vec<Vec<Complex<f64>>> = matrix
477 .unwrap_array()
478 .iter()
479 .map(|row| {
480 row.clone()
481 .unwrap_array()
482 .iter()
483 .map(|elem| {
484 let [re, im] = unwrap_tuple(elem.clone());
485 Complex::<f64>::new(re.unwrap_double(), im.unwrap_double())
486 })
487 .collect::<Vec<_>>()
488 })
489 .collect::<Vec<_>>();
490
491 Array2::from_shape_fn((1 << qubits.len(), 1 << qubits.len()), |(i, j)| {
492 matrix[i][j]
493 })
494}
495
496/// Simple struct that chains two backends together so that the chained
497/// backend is called before the main backend.
498/// For any intrinsics that return a value,
499/// the value returned by the chained backend is ignored.
500/// The value returned by the main backend is returned.
501pub struct Chain<T1, T2> {
502 pub main: T1,
503 pub chained: T2,
504}
505
506impl<T1, T2> Chain<T1, T2>
507where
508 T1: Backend,
509 T2: Backend,
510{
511 pub fn new(primary: T1, chained: T2) -> Chain<T1, T2> {
512 Chain {
513 main: primary,
514 chained,
515 }
516 }
517}
518
519impl<T1, T2> Backend for Chain<T1, T2>
520where
521 T1: Backend,
522 T2: Backend,
523{
524 type ResultType = T1::ResultType;
525
526 fn ccx(&mut self, ctl0: usize, ctl1: usize, q: usize) {
527 self.chained.ccx(ctl0, ctl1, q);
528 self.main.ccx(ctl0, ctl1, q);
529 }
530
531 fn cx(&mut self, ctl: usize, q: usize) {
532 self.chained.cx(ctl, q);
533 self.main.cx(ctl, q);
534 }
535
536 fn cy(&mut self, ctl: usize, q: usize) {
537 self.chained.cy(ctl, q);
538 self.main.cy(ctl, q);
539 }
540
541 fn cz(&mut self, ctl: usize, q: usize) {
542 self.chained.cz(ctl, q);
543 self.main.cz(ctl, q);
544 }
545
546 fn h(&mut self, q: usize) {
547 self.chained.h(q);
548 self.main.h(q);
549 }
550
551 fn m(&mut self, q: usize) -> Self::ResultType {
552 let _ = self.chained.m(q);
553 self.main.m(q)
554 }
555
556 fn mresetz(&mut self, q: usize) -> Self::ResultType {
557 let _ = self.chained.mresetz(q);
558 self.main.mresetz(q)
559 }
560
561 fn reset(&mut self, q: usize) {
562 self.chained.reset(q);
563 self.main.reset(q);
564 }
565
566 fn rx(&mut self, theta: f64, q: usize) {
567 self.chained.rx(theta, q);
568 self.main.rx(theta, q);
569 }
570
571 fn rxx(&mut self, theta: f64, q0: usize, q1: usize) {
572 self.chained.rxx(theta, q0, q1);
573 self.main.rxx(theta, q0, q1);
574 }
575
576 fn ry(&mut self, theta: f64, q: usize) {
577 self.chained.ry(theta, q);
578 self.main.ry(theta, q);
579 }
580
581 fn ryy(&mut self, theta: f64, q0: usize, q1: usize) {
582 self.chained.ryy(theta, q0, q1);
583 self.main.ryy(theta, q0, q1);
584 }
585
586 fn rz(&mut self, theta: f64, q: usize) {
587 self.chained.rz(theta, q);
588 self.main.rz(theta, q);
589 }
590
591 fn rzz(&mut self, theta: f64, q0: usize, q1: usize) {
592 self.chained.rzz(theta, q0, q1);
593 self.main.rzz(theta, q0, q1);
594 }
595
596 fn sadj(&mut self, q: usize) {
597 self.chained.sadj(q);
598 self.main.sadj(q);
599 }
600
601 fn s(&mut self, q: usize) {
602 self.chained.s(q);
603 self.main.s(q);
604 }
605
606 fn sx(&mut self, q: usize) {
607 self.chained.sx(q);
608 self.main.sx(q);
609 }
610
611 fn swap(&mut self, q0: usize, q1: usize) {
612 self.chained.swap(q0, q1);
613 self.main.swap(q0, q1);
614 }
615
616 fn tadj(&mut self, q: usize) {
617 self.chained.tadj(q);
618 self.main.tadj(q);
619 }
620
621 fn t(&mut self, q: usize) {
622 self.chained.t(q);
623 self.main.t(q);
624 }
625
626 fn x(&mut self, q: usize) {
627 self.chained.x(q);
628 self.main.x(q);
629 }
630
631 fn y(&mut self, q: usize) {
632 self.chained.y(q);
633 self.main.y(q);
634 }
635
636 fn z(&mut self, q: usize) {
637 self.chained.z(q);
638 self.main.z(q);
639 }
640
641 fn qubit_allocate(&mut self) -> usize {
642 // Warning: we use the qubit id allocated by the
643 // main backend, even for later calls into the chained
644 // backend. This is not an issue today, but could
645 // become an issue if the qubit ids differ between
646 // the two backends.
647 let _ = self.chained.qubit_allocate();
648 self.main.qubit_allocate()
649 }
650
651 fn qubit_release(&mut self, q: usize) -> bool {
652 let _ = self.chained.qubit_release(q);
653 self.main.qubit_release(q)
654 }
655
656 fn qubit_swap_id(&mut self, q0: usize, q1: usize) {
657 self.chained.qubit_swap_id(q0, q1);
658 self.main.qubit_swap_id(q0, q1);
659 }
660
661 fn capture_quantum_state(
662 &mut self,
663 ) -> (Vec<(num_bigint::BigUint, num_complex::Complex<f64>)>, usize) {
664 let _ = self.chained.capture_quantum_state();
665 self.main.capture_quantum_state()
666 }
667
668 fn qubit_is_zero(&mut self, q: usize) -> bool {
669 let _ = self.chained.qubit_is_zero(q);
670 self.main.qubit_is_zero(q)
671 }
672
673 fn custom_intrinsic(&mut self, name: &str, arg: Value) -> Option<Result<Value, String>> {
674 let _ = self.chained.custom_intrinsic(name, arg.clone());
675 self.main.custom_intrinsic(name, arg)
676 }
677
678 fn set_seed(&mut self, seed: Option<u64>) {
679 self.chained.set_seed(seed);
680 self.main.set_seed(seed);
681 }
682}
683