microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
billti/sim

Branches

Tags

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

Clone

HTTPS

Download ZIP

compiler/qsc_eval/src/backend.rs

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