microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
f9ff1026ee0d8ead8c9892e7ca362a90fb72042a

Branches

Tags

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

Clone

HTTPS

Download ZIP

compiler/qsc_eval/src/backend.rs

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