microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
alex/bounds

Branches

Tags

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

Clone

HTTPS

Download ZIP

compiler/qsc_eval/src/backend.rs

596lines · modecode

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