microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
dbwy/random_seed

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/compiler/qsc_eval/src/backend.rs

1073lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use crate::debug::Frame;
5use crate::val::{self, Value};
6use crate::{noise::PauliNoise, val::unwrap_tuple};
7use ndarray::Array2;
8use num_bigint::BigUint;
9use num_complex::Complex;
10use num_traits::Zero;
11use qdk_simulators::QuantumSim;
12use rand::{Rng, RngCore};
13use rand::{SeedableRng, rngs::StdRng};
14
15#[cfg(test)]
16mod noise_tests;
17
18/// The trait that must be implemented by a quantum backend, whose functions will be invoked when
19/// quantum intrinsics are called.
20pub trait Backend {
21 fn ccx(&mut self, _ctl0: usize, _ctl1: usize, _q: usize) {
22 unimplemented!("ccx gate");
23 }
24 fn cx(&mut self, _ctl: usize, _q: usize) {
25 unimplemented!("cx gate");
26 }
27 fn cy(&mut self, _ctl: usize, _q: usize) {
28 unimplemented!("cy gate");
29 }
30 fn cz(&mut self, _ctl: usize, _q: usize) {
31 unimplemented!("cz gate");
32 }
33 fn h(&mut self, _q: usize) {
34 unimplemented!("h gate");
35 }
36 fn m(&mut self, _q: usize) -> val::Result {
37 unimplemented!("m operation");
38 }
39 fn mresetz(&mut self, _q: usize) -> val::Result {
40 unimplemented!("mresetz operation");
41 }
42 fn reset(&mut self, _q: usize) {
43 unimplemented!("reset gate");
44 }
45 fn rx(&mut self, _theta: f64, _q: usize) {
46 unimplemented!("rx gate");
47 }
48 fn rxx(&mut self, _theta: f64, _q0: usize, _q1: usize) {
49 unimplemented!("rxx gate");
50 }
51 fn ry(&mut self, _theta: f64, _q: usize) {
52 unimplemented!("ry gate");
53 }
54 fn ryy(&mut self, _theta: f64, _q0: usize, _q1: usize) {
55 unimplemented!("ryy gate");
56 }
57 fn rz(&mut self, _theta: f64, _q: usize) {
58 unimplemented!("rz gate");
59 }
60 fn rzz(&mut self, _theta: f64, _q0: usize, _q1: usize) {
61 unimplemented!("rzz gate");
62 }
63 fn sadj(&mut self, _q: usize) {
64 unimplemented!("sadj gate");
65 }
66 fn s(&mut self, _q: usize) {
67 unimplemented!("s gate");
68 }
69 fn sx(&mut self, _q: usize) {
70 unimplemented!("sx gate");
71 }
72 fn swap(&mut self, _q0: usize, _q1: usize) {
73 unimplemented!("swap gate");
74 }
75 fn tadj(&mut self, _q: usize) {
76 unimplemented!("tadj gate");
77 }
78 fn t(&mut self, _q: usize) {
79 unimplemented!("t gate");
80 }
81 fn x(&mut self, _q: usize) {
82 unimplemented!("x gate");
83 }
84 fn y(&mut self, _q: usize) {
85 unimplemented!("y gate");
86 }
87 fn z(&mut self, _q: usize) {
88 unimplemented!("z gate");
89 }
90 fn qubit_allocate(&mut self) -> usize {
91 unimplemented!("qubit_allocate operation");
92 }
93 /// `false` indicates that the qubit was in a non-zero state before the release,
94 /// but should have been in the zero state.
95 /// `true` otherwise. This includes the case when the qubit was in
96 /// a non-zero state during a noisy simulation, which is allowed.
97 fn qubit_release(&mut self, _q: usize) -> bool {
98 unimplemented!("qubit_release operation");
99 }
100 fn qubit_swap_id(&mut self, _q0: usize, _q1: usize) {
101 unimplemented!("qubit_swap_id operation");
102 }
103 fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
104 unimplemented!("capture_quantum_state operation");
105 }
106 fn qubit_is_zero(&mut self, _q: usize) -> bool {
107 unimplemented!("qubit_is_zero operation");
108 }
109 /// Executes custom intrinsic specified by `_name`.
110 /// Returns None if this intrinsic is unknown.
111 /// Otherwise returns Some(Result), with the Result from intrinsic.
112 fn custom_intrinsic(&mut self, _name: &str, _arg: Value) -> Option<Result<Value, String>> {
113 None
114 }
115 fn set_seed(&mut self, _seed: Option<u64>) {}
116}
117
118/// Trait receiving trace events for quantum execution. Each method records
119/// an operation along with the current call stack when stack/source location
120/// tracing is enabled. If stack tracing is disabled, the stack parameter
121/// will be ignored.
122pub trait Tracer {
123 fn qubit_allocate(&mut self, stack: &[Frame], q: usize);
124 fn qubit_release(&mut self, stack: &[Frame], q: usize);
125 fn qubit_swap_id(&mut self, stack: &[Frame], q0: usize, q1: usize);
126 fn gate(
127 &mut self,
128 stack: &[Frame],
129 name: &str,
130 is_adjoint: bool,
131 targets: &[usize],
132 controls: &[usize],
133 theta: Option<f64>,
134 );
135 fn measure(&mut self, stack: &[Frame], name: &str, q: usize, r: &val::Result);
136 fn reset(&mut self, stack: &[Frame], q: usize);
137 fn custom_intrinsic(&mut self, stack: &[Frame], name: &str, arg: Value);
138 fn is_stack_tracing_enabled(&self) -> bool;
139}
140
141/// Backend wrapper that forwards execution to a concrete `Backend` while
142/// optionally recording operations (qubit allocation/release, gates, measurements)
143/// via a `Tracer`. When constructed with `no_backend`, it uses a fallback
144/// allocator and emits trace events without performing real simulation.
145pub struct TracingBackend<'a, B: Backend> {
146 backend: OptionalBackend<'a, B>,
147 tracer: Option<&'a mut dyn Tracer>,
148}
149
150impl<'a, B: Backend> TracingBackend<'a, B> {
151 pub fn new(backend: &'a mut B, tracer: Option<&'a mut impl Tracer>) -> Self {
152 Self {
153 backend: OptionalBackend::Some(backend),
154 tracer: tracer.map(|t| t as &mut dyn Tracer),
155 }
156 }
157
158 pub fn no_tracer(backend: &'a mut B) -> Self {
159 Self {
160 backend: OptionalBackend::Some(backend),
161 tracer: None,
162 }
163 }
164
165 pub fn no_backend(tracer: &'a mut dyn Tracer) -> Self {
166 Self {
167 backend: OptionalBackend::None(SequentialAllocator::default()),
168 tracer: Some(tracer),
169 }
170 }
171
172 #[must_use]
173 pub fn is_stacks_enabled(&self) -> bool {
174 if let Some(tracer) = &self.tracer {
175 tracer.is_stack_tracing_enabled()
176 } else {
177 false
178 }
179 }
180
181 pub fn ccx(&mut self, ctl0: usize, ctl1: usize, q: usize, stack: &[Frame]) {
182 if let OptionalBackend::Some(backend) = &mut self.backend {
183 backend.ccx(ctl0, ctl1, q);
184 }
185 if let Some(tracer) = &mut self.tracer {
186 tracer.gate(stack, "X", false, &[q], &[ctl0, ctl1], None);
187 }
188 }
189
190 pub fn cx(&mut self, ctl: usize, q: usize, stack: &[Frame]) {
191 if let OptionalBackend::Some(backend) = &mut self.backend {
192 backend.cx(ctl, q);
193 }
194 if let Some(tracer) = &mut self.tracer {
195 tracer.gate(stack, "X", false, &[q], &[ctl], None);
196 }
197 }
198
199 pub fn cy(&mut self, ctl: usize, q: usize, stack: &[Frame]) {
200 if let OptionalBackend::Some(backend) = &mut self.backend {
201 backend.cy(ctl, q);
202 }
203 if let Some(tracer) = &mut self.tracer {
204 tracer.gate(stack, "Y", false, &[q], &[ctl], None);
205 }
206 }
207
208 pub fn cz(&mut self, ctl: usize, q: usize, stack: &[Frame]) {
209 if let OptionalBackend::Some(backend) = &mut self.backend {
210 backend.cz(ctl, q);
211 }
212 if let Some(tracer) = &mut self.tracer {
213 tracer.gate(stack, "Z", false, &[q], &[ctl], None);
214 }
215 }
216
217 pub fn h(&mut self, q: usize, stack: &[Frame]) {
218 if let OptionalBackend::Some(backend) = &mut self.backend {
219 backend.h(q);
220 }
221 if let Some(tracer) = &mut self.tracer {
222 tracer.gate(stack, "H", false, &[q], &[], None);
223 }
224 }
225
226 pub fn m(&mut self, q: usize, stack: &[Frame]) -> val::Result {
227 let r = match &mut self.backend {
228 OptionalBackend::Some(backend) => backend.m(q),
229 OptionalBackend::None(fallback) => fallback.result_allocate(),
230 };
231 if let Some(tracer) = &mut self.tracer {
232 tracer.measure(stack, "M", q, &r);
233 }
234 r
235 }
236
237 pub fn mresetz(&mut self, q: usize, stack: &[Frame]) -> val::Result {
238 let r = match &mut self.backend {
239 OptionalBackend::Some(backend) => backend.mresetz(q),
240 OptionalBackend::None(fallback) => fallback.result_allocate(),
241 };
242 if let Some(tracer) = &mut self.tracer {
243 tracer.measure(stack, "MResetZ", q, &r);
244 }
245 r
246 }
247
248 pub fn reset(&mut self, q: usize, stack: &[Frame]) {
249 if let Some(tracer) = &mut self.tracer {
250 tracer.reset(stack, q);
251 }
252 if let OptionalBackend::Some(backend) = &mut self.backend {
253 backend.reset(q);
254 }
255 }
256
257 pub fn rx(&mut self, theta: f64, q: usize, stack: &[Frame]) {
258 if let Some(tracer) = &mut self.tracer {
259 tracer.gate(stack, "Rx", false, &[q], &[], Some(theta));
260 }
261 if let OptionalBackend::Some(backend) = &mut self.backend {
262 backend.rx(theta, q);
263 }
264 }
265
266 pub fn rxx(&mut self, theta: f64, q0: usize, q1: usize, stack: &[Frame]) {
267 if let Some(tracer) = &mut self.tracer {
268 tracer.gate(stack, "Rxx", false, &[q0, q1], &[], Some(theta));
269 }
270 if let OptionalBackend::Some(backend) = &mut self.backend {
271 backend.rxx(theta, q0, q1);
272 }
273 }
274
275 pub fn ry(&mut self, theta: f64, q: usize, stack: &[Frame]) {
276 if let Some(tracer) = &mut self.tracer {
277 tracer.gate(stack, "Ry", false, &[q], &[], Some(theta));
278 }
279 if let OptionalBackend::Some(backend) = &mut self.backend {
280 backend.ry(theta, q);
281 }
282 }
283
284 pub fn ryy(&mut self, theta: f64, q0: usize, q1: usize, stack: &[Frame]) {
285 if let Some(tracer) = &mut self.tracer {
286 tracer.gate(stack, "Ryy", false, &[q0, q1], &[], Some(theta));
287 }
288 if let OptionalBackend::Some(backend) = &mut self.backend {
289 backend.ryy(theta, q0, q1);
290 }
291 }
292
293 pub fn rz(&mut self, theta: f64, q: usize, stack: &[Frame]) {
294 if let Some(tracer) = &mut self.tracer {
295 tracer.gate(stack, "Rz", false, &[q], &[], Some(theta));
296 }
297 if let OptionalBackend::Some(backend) = &mut self.backend {
298 backend.rz(theta, q);
299 }
300 }
301
302 pub fn rzz(&mut self, theta: f64, q0: usize, q1: usize, stack: &[Frame]) {
303 if let Some(tracer) = &mut self.tracer {
304 tracer.gate(stack, "Rzz", false, &[q0, q1], &[], Some(theta));
305 }
306 if let OptionalBackend::Some(backend) = &mut self.backend {
307 backend.rzz(theta, q0, q1);
308 }
309 }
310
311 pub fn sadj(&mut self, q: usize, stack: &[Frame]) {
312 if let Some(tracer) = &mut self.tracer {
313 tracer.gate(stack, "S", true, &[q], &[], None);
314 }
315 if let OptionalBackend::Some(backend) = &mut self.backend {
316 backend.sadj(q);
317 }
318 }
319
320 pub fn s(&mut self, q: usize, stack: &[Frame]) {
321 if let Some(tracer) = &mut self.tracer {
322 tracer.gate(stack, "S", false, &[q], &[], None);
323 }
324 if let OptionalBackend::Some(backend) = &mut self.backend {
325 backend.s(q);
326 }
327 }
328
329 pub fn sx(&mut self, q: usize, stack: &[Frame]) {
330 if let Some(tracer) = &mut self.tracer {
331 tracer.gate(stack, "SX", false, &[q], &[], None);
332 }
333 if let OptionalBackend::Some(backend) = &mut self.backend {
334 backend.sx(q);
335 }
336 }
337
338 pub fn swap(&mut self, q0: usize, q1: usize, stack: &[Frame]) {
339 if let Some(tracer) = &mut self.tracer {
340 tracer.gate(stack, "SWAP", false, &[q0, q1], &[], None);
341 }
342 if let OptionalBackend::Some(backend) = &mut self.backend {
343 backend.swap(q0, q1);
344 }
345 }
346
347 pub fn tadj(&mut self, q: usize, stack: &[Frame]) {
348 if let Some(tracer) = &mut self.tracer {
349 tracer.gate(stack, "T", true, &[q], &[], None);
350 }
351 if let OptionalBackend::Some(backend) = &mut self.backend {
352 backend.tadj(q);
353 }
354 }
355
356 pub fn t(&mut self, q: usize, stack: &[Frame]) {
357 if let Some(tracer) = &mut self.tracer {
358 tracer.gate(stack, "T", false, &[q], &[], None);
359 }
360 if let OptionalBackend::Some(backend) = &mut self.backend {
361 backend.t(q);
362 }
363 }
364
365 pub fn x(&mut self, q: usize, stack: &[Frame]) {
366 if let Some(tracer) = &mut self.tracer {
367 tracer.gate(stack, "X", false, &[q], &[], None);
368 }
369 if let OptionalBackend::Some(backend) = &mut self.backend {
370 backend.x(q);
371 }
372 }
373
374 pub fn y(&mut self, q: usize, stack: &[Frame]) {
375 if let Some(tracer) = &mut self.tracer {
376 tracer.gate(stack, "Y", false, &[q], &[], None);
377 }
378 if let OptionalBackend::Some(backend) = &mut self.backend {
379 backend.y(q);
380 }
381 }
382
383 pub fn z(&mut self, q: usize, stack: &[Frame]) {
384 if let Some(tracer) = &mut self.tracer {
385 tracer.gate(stack, "Z", false, &[q], &[], None);
386 }
387 if let OptionalBackend::Some(backend) = &mut self.backend {
388 backend.z(q);
389 }
390 }
391
392 pub fn qubit_allocate(&mut self, stack: &[Frame]) -> usize {
393 let q = match &mut self.backend {
394 OptionalBackend::Some(backend) => backend.qubit_allocate(),
395 OptionalBackend::None(fallback) => fallback.qubit_allocate(),
396 };
397 if let Some(tracer) = &mut self.tracer {
398 tracer.qubit_allocate(stack, q);
399 }
400 q
401 }
402
403 pub fn qubit_release(&mut self, q: usize, stack: &[Frame]) -> bool {
404 let b = match &mut self.backend {
405 OptionalBackend::Some(backend) => backend.qubit_release(q),
406 OptionalBackend::None(fallback) => fallback.qubit_release(q),
407 };
408 if let Some(tracer) = &mut self.tracer {
409 tracer.qubit_release(stack, q);
410 }
411 b
412 }
413
414 pub fn qubit_swap_id(&mut self, q0: usize, q1: usize, stack: &[Frame]) {
415 if let OptionalBackend::Some(backend) = &mut self.backend {
416 backend.qubit_swap_id(q0, q1);
417 }
418 if let Some(tracer) = &mut self.tracer {
419 tracer.qubit_swap_id(stack, q0, q1);
420 }
421 }
422
423 pub fn capture_quantum_state(
424 &mut self,
425 ) -> (Vec<(num_bigint::BigUint, num_complex::Complex<f64>)>, usize) {
426 match &mut self.backend {
427 OptionalBackend::Some(backend) => backend.capture_quantum_state(),
428 OptionalBackend::None(_) => (Vec::new(), 0),
429 }
430 }
431
432 pub fn qubit_is_zero(&mut self, q: usize) -> bool {
433 match &mut self.backend {
434 OptionalBackend::Some(backend) => backend.qubit_is_zero(q),
435 OptionalBackend::None(_) => true,
436 }
437 }
438
439 pub fn custom_intrinsic(
440 &mut self,
441 name: &str,
442 arg: Value,
443 stack: &[Frame],
444 ) -> Option<Result<Value, String>> {
445 if let Some(tracer) = &mut self.tracer {
446 tracer.custom_intrinsic(stack, name, arg.clone());
447 }
448 match &mut self.backend {
449 OptionalBackend::Some(backend) => backend.custom_intrinsic(name, arg),
450 OptionalBackend::None(_) => {
451 match name {
452 // Special case this known intrinsic to match the simulator
453 // behavior, so that our samples will work
454 "BeginEstimateCaching" => Some(Ok(Value::Bool(true))),
455 _ => Some(Ok(Value::unit())),
456 }
457 }
458 }
459 }
460
461 pub fn set_seed(&mut self, seed: Option<u64>) {
462 if let OptionalBackend::Some(backend) = &mut self.backend {
463 backend.set_seed(seed);
464 }
465 }
466}
467
468enum OptionalBackend<'a, B: Backend> {
469 None(SequentialAllocator),
470 Some(&'a mut B),
471}
472
473#[derive(Default)]
474/// Fallback allocator used when there is no concrete backend (`OptionalBackend::None`).
475/// Provides monotonically increasing identifiers for qubits and measurement result
476/// values so program can run without a full simulator implementation.
477struct SequentialAllocator {
478 next_result_id: usize,
479 next_qubit_id: usize,
480}
481
482impl SequentialAllocator {
483 fn result_allocate(&mut self) -> val::Result {
484 let id = self.next_result_id;
485 self.next_result_id += 1;
486 id.into()
487 }
488 fn qubit_allocate(&mut self) -> usize {
489 let id = self.next_qubit_id;
490 self.next_qubit_id += 1;
491 id
492 }
493 fn qubit_release(&mut self, _q: usize) -> bool {
494 // This pattern only works when qubits (or sets of qubits)
495 // are released in reverse order to allocation.
496 self.next_qubit_id -= 1;
497 true
498 }
499}
500
501/// Default backend used when targeting sparse simulation.
502pub struct SparseSim {
503 /// Noiseless Sparse simulator to be used by this instance.
504 pub sim: QuantumSim,
505 /// Pauli noise that is applied after a gate or before a measurement is executed.
506 /// Service functions aren't subject to noise.
507 pub noise: PauliNoise,
508 /// Loss probability for the qubit, which is applied before a measurement.
509 pub loss: f64,
510 /// A bit vector that tracks which qubits were lost.
511 pub lost_qubits: BigUint,
512 /// Random number generator to sample Pauli noise.
513 /// Noise is not applied when rng is None.
514 pub rng: Option<StdRng>,
515}
516
517impl Default for SparseSim {
518 fn default() -> Self {
519 Self::new()
520 }
521}
522
523impl SparseSim {
524 #[must_use]
525 pub fn new() -> Self {
526 Self {
527 sim: QuantumSim::new(None),
528 noise: PauliNoise::default(),
529 loss: f64::zero(),
530 lost_qubits: BigUint::zero(),
531 rng: None,
532 }
533 }
534
535 #[must_use]
536 pub fn new_with_seed(seed: Option<u64>) -> Self {
537 Self {
538 sim: QuantumSim::new(seed.map(StdRng::seed_from_u64)),
539 noise: PauliNoise::default(),
540 loss: f64::zero(),
541 lost_qubits: BigUint::zero(),
542 rng: None,
543 }
544 }
545
546 #[must_use]
547 pub fn new_with_noise(noise: &PauliNoise) -> Self {
548 let mut sim = SparseSim::new();
549 sim.set_noise(noise);
550 sim
551 }
552
553 fn set_noise(&mut self, noise: &PauliNoise) {
554 self.noise = *noise;
555 if noise.is_noiseless() && self.loss.is_zero() {
556 self.rng = None;
557 } else {
558 self.rng = Some(StdRng::from_entropy());
559 }
560 }
561
562 pub fn set_loss(&mut self, loss: f64) {
563 self.loss = loss;
564 if loss.is_zero() && self.noise.is_noiseless() {
565 self.rng = None;
566 } else {
567 self.rng = Some(StdRng::from_entropy());
568 }
569 }
570
571 #[must_use]
572 fn is_noiseless(&self) -> bool {
573 self.rng.is_none()
574 }
575
576 fn apply_noise(&mut self, q: usize) {
577 if self.is_qubit_lost(q) {
578 // If the qubit is already lost, we don't apply noise.
579 return;
580 }
581 if let Some(rng) = &mut self.rng {
582 // First, check for loss.
583 let p = rng.gen_range(0.0..1.0);
584 if p < self.loss {
585 // The qubit is lost, so we reset it.
586 // It is not safe to release the qubit here, as that may
587 // interfere with later operations (gates or measurements)
588 // or even normal qubit release at end of scope.
589 if self.sim.measure(q) {
590 self.sim.x(q);
591 }
592 // Mark the qubit as lost.
593 self.lost_qubits.set_bit(q as u64, true);
594 return;
595 }
596
597 // Apply noise with a probability distribution defined in `self.noise`.
598 let p = rng.gen_range(0.0..1.0);
599 if p >= self.noise.distribution[2] {
600 // In the most common case we don't apply noise
601 } else if p < self.noise.distribution[0] {
602 self.sim.x(q);
603 } else if p < self.noise.distribution[1] {
604 self.sim.y(q);
605 } else {
606 self.sim.z(q);
607 }
608 }
609 // No noise applied if rng is None.
610 }
611
612 /// Checks if the qubit is lost.
613 fn is_qubit_lost(&self, q: usize) -> bool {
614 self.lost_qubits.bit(q as u64)
615 }
616}
617
618impl Backend for SparseSim {
619 fn ccx(&mut self, ctl0: usize, ctl1: usize, q: usize) {
620 match (
621 self.is_qubit_lost(ctl0),
622 self.is_qubit_lost(ctl1),
623 self.is_qubit_lost(q),
624 ) {
625 (true, true, _) | (_, _, true) => {
626 // If the target qubit is lost or both controls are lost, skip the operation.
627 }
628
629 // When only one control is lost, use the other to do a singly controlled X.
630 (true, false, false) => {
631 self.sim.mcx(&[ctl1], q);
632 }
633 (false, true, false) => {
634 self.sim.mcx(&[ctl0], q);
635 }
636
637 // No qubits lost, execute normally.
638 (false, false, false) => {
639 self.sim.mcx(&[ctl0, ctl1], q);
640 }
641 }
642 self.apply_noise(ctl0);
643 self.apply_noise(ctl1);
644 self.apply_noise(q);
645 }
646
647 fn cx(&mut self, ctl: usize, q: usize) {
648 if !self.is_qubit_lost(ctl) && !self.is_qubit_lost(q) {
649 self.sim.mcx(&[ctl], q);
650 }
651 self.apply_noise(ctl);
652 self.apply_noise(q);
653 }
654
655 fn cy(&mut self, ctl: usize, q: usize) {
656 if !self.is_qubit_lost(ctl) && !self.is_qubit_lost(q) {
657 self.sim.mcy(&[ctl], q);
658 }
659 self.apply_noise(ctl);
660 self.apply_noise(q);
661 }
662
663 fn cz(&mut self, ctl: usize, q: usize) {
664 if !self.is_qubit_lost(ctl) && !self.is_qubit_lost(q) {
665 self.sim.mcz(&[ctl], q);
666 }
667 self.apply_noise(ctl);
668 self.apply_noise(q);
669 }
670
671 fn h(&mut self, q: usize) {
672 if !self.is_qubit_lost(q) {
673 self.sim.h(q);
674 }
675 self.apply_noise(q);
676 }
677
678 fn m(&mut self, q: usize) -> val::Result {
679 self.apply_noise(q);
680 if self.is_qubit_lost(q) {
681 // If the qubit is lost, we cannot measure it.
682 // Mark it as no longer lost so it becomes usable again, since
683 // measurement will "reload" the qubit.
684 self.lost_qubits.set_bit(q as u64, false);
685 return val::Result::Loss;
686 }
687 val::Result::Val(self.sim.measure(q))
688 }
689
690 fn mresetz(&mut self, q: usize) -> val::Result {
691 self.apply_noise(q); // Applying noise before measurement
692 if self.is_qubit_lost(q) {
693 // If the qubit is lost, we cannot measure it.
694 // Mark it as no longer lost so it becomes usable again, since
695 // measurement will "reload" the qubit.
696 self.lost_qubits.set_bit(q as u64, false);
697 return val::Result::Loss;
698 }
699 let res = self.sim.measure(q);
700 if res {
701 self.sim.x(q);
702 }
703 self.apply_noise(q); // Applying noise after reset
704 val::Result::Val(res)
705 }
706
707 fn reset(&mut self, q: usize) {
708 self.mresetz(q);
709 // Noise applied in mresetz.
710 }
711
712 fn rx(&mut self, theta: f64, q: usize) {
713 if !self.is_qubit_lost(q) {
714 self.sim.rx(theta, q);
715 }
716 self.apply_noise(q);
717 }
718
719 fn rxx(&mut self, theta: f64, q0: usize, q1: usize) {
720 // If only one qubit is lost, we can apply a single qubit rotation.
721 // If both are lost, return without performing any operation.
722 match (self.is_qubit_lost(q0), self.is_qubit_lost(q1)) {
723 (true, false) => {
724 self.sim.rx(theta, q1);
725 }
726 (false, true) => {
727 self.sim.rx(theta, q0);
728 }
729 (true, true) => {}
730 (false, false) => {
731 self.sim.h(q0);
732 self.sim.h(q1);
733 self.sim.mcx(&[q1], q0);
734 self.sim.rz(theta, q0);
735 self.sim.mcx(&[q1], q0);
736 self.sim.h(q1);
737 self.sim.h(q0);
738 }
739 }
740 self.apply_noise(q0);
741 self.apply_noise(q1);
742 }
743
744 fn ry(&mut self, theta: f64, q: usize) {
745 if !self.is_qubit_lost(q) {
746 self.sim.ry(theta, q);
747 }
748 self.apply_noise(q);
749 }
750
751 fn ryy(&mut self, theta: f64, q0: usize, q1: usize) {
752 // If only one qubit is lost, we can apply a single qubit rotation.
753 // If both are lost, return without performing any operation.
754 match (self.is_qubit_lost(q0), self.is_qubit_lost(q1)) {
755 (true, false) => {
756 self.sim.ry(theta, q1);
757 }
758 (false, true) => {
759 self.sim.ry(theta, q0);
760 }
761 (true, true) => {}
762 (false, false) => {
763 self.sim.h(q0);
764 self.sim.s(q0);
765 self.sim.h(q0);
766 self.sim.h(q1);
767 self.sim.s(q1);
768 self.sim.h(q1);
769 self.sim.mcx(&[q1], q0);
770 self.sim.rz(theta, q0);
771 self.sim.mcx(&[q1], q0);
772 self.sim.h(q1);
773 self.sim.sadj(q1);
774 self.sim.h(q1);
775 self.sim.h(q0);
776 self.sim.sadj(q0);
777 self.sim.h(q0);
778 }
779 }
780 self.apply_noise(q0);
781 self.apply_noise(q1);
782 }
783
784 fn rz(&mut self, theta: f64, q: usize) {
785 if !self.is_qubit_lost(q) {
786 self.sim.rz(theta, q);
787 }
788 self.apply_noise(q);
789 }
790
791 fn rzz(&mut self, theta: f64, q0: usize, q1: usize) {
792 // If only one qubit is lost, we can apply a single qubit rotation.
793 // If both are lost, return without performing any operation.
794 match (self.is_qubit_lost(q0), self.is_qubit_lost(q1)) {
795 (true, false) => {
796 self.sim.rz(theta, q1);
797 }
798 (false, true) => {
799 self.sim.rz(theta, q0);
800 }
801 (true, true) => {}
802 (false, false) => {
803 self.sim.mcx(&[q1], q0);
804 self.sim.rz(theta, q0);
805 self.sim.mcx(&[q1], q0);
806 }
807 }
808 self.apply_noise(q0);
809 self.apply_noise(q1);
810 }
811
812 fn sadj(&mut self, q: usize) {
813 if !self.is_qubit_lost(q) {
814 self.sim.sadj(q);
815 }
816 self.apply_noise(q);
817 }
818
819 fn s(&mut self, q: usize) {
820 if !self.is_qubit_lost(q) {
821 self.sim.s(q);
822 }
823 self.apply_noise(q);
824 }
825
826 fn sx(&mut self, q: usize) {
827 if !self.is_qubit_lost(q) {
828 self.sim.h(q);
829 self.sim.s(q);
830 self.sim.h(q);
831 }
832 self.apply_noise(q);
833 }
834
835 fn swap(&mut self, q0: usize, q1: usize) {
836 if !self.is_qubit_lost(q0) && !self.is_qubit_lost(q1) {
837 self.sim.swap_qubit_ids(q0, q1);
838 }
839 self.apply_noise(q0);
840 self.apply_noise(q1);
841 }
842
843 fn tadj(&mut self, q: usize) {
844 if !self.is_qubit_lost(q) {
845 self.sim.tadj(q);
846 }
847 self.apply_noise(q);
848 }
849
850 fn t(&mut self, q: usize) {
851 if !self.is_qubit_lost(q) {
852 self.sim.t(q);
853 }
854 self.apply_noise(q);
855 }
856
857 fn x(&mut self, q: usize) {
858 if !self.is_qubit_lost(q) {
859 self.sim.x(q);
860 }
861 self.apply_noise(q);
862 }
863
864 fn y(&mut self, q: usize) {
865 if !self.is_qubit_lost(q) {
866 self.sim.y(q);
867 }
868 self.apply_noise(q);
869 }
870
871 fn z(&mut self, q: usize) {
872 if !self.is_qubit_lost(q) {
873 self.sim.z(q);
874 }
875 self.apply_noise(q);
876 }
877
878 fn qubit_allocate(&mut self) -> usize {
879 // Fresh qubit start in ground state even with noise.
880 self.sim.allocate()
881 }
882
883 fn qubit_release(&mut self, q: usize) -> bool {
884 if self.is_noiseless() {
885 let was_zero = self.sim.qubit_is_zero(q);
886 self.sim.release(q);
887 was_zero
888 } else {
889 self.sim.release(q);
890 true
891 }
892 }
893
894 fn qubit_swap_id(&mut self, q0: usize, q1: usize) {
895 // This is a service function rather than a gate so it doesn't incur noise.
896 self.sim.swap_qubit_ids(q0, q1);
897 // We must also swap any loss bits for the qubits.
898 let (q0_lost, q1_lost) = (
899 self.lost_qubits.bit(q0 as u64),
900 self.lost_qubits.bit(q1 as u64),
901 );
902 if q0_lost != q1_lost {
903 // If the loss state is different, we need to swap them.
904 self.lost_qubits.set_bit(q0 as u64, q1_lost);
905 self.lost_qubits.set_bit(q1 as u64, q0_lost);
906 }
907 }
908
909 fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
910 let (state, count) = self.sim.get_state();
911 // Because the simulator returns the state indices with opposite endianness from the
912 // expected one, we need to reverse the bit order of the indices.
913 let mut new_state = state
914 .into_iter()
915 .map(|(idx, val)| {
916 let mut new_idx = BigUint::default();
917 for i in 0..(count as u64) {
918 if idx.bit((count as u64) - 1 - i) {
919 new_idx.set_bit(i, true);
920 }
921 }
922 (new_idx, val)
923 })
924 .collect::<Vec<_>>();
925 new_state.sort_unstable_by(|a, b| a.0.cmp(&b.0));
926 (new_state, count)
927 }
928
929 fn qubit_is_zero(&mut self, q: usize) -> bool {
930 // This is a service function rather than a measurement so it doesn't incur noise.
931 self.sim.qubit_is_zero(q)
932 }
933
934 fn custom_intrinsic(&mut self, name: &str, arg: Value) -> Option<Result<Value, String>> {
935 // These intrinsics aren't subject to noise.
936 match name {
937 "GlobalPhase" => {
938 // Apply a global phase to the simulation by doing an Rz to a fresh qubit.
939 // The controls list may be empty, in which case the phase is applied unconditionally.
940 let [ctls_val, theta] = &*arg.unwrap_tuple() else {
941 panic!("tuple arity for GlobalPhase intrinsic should be 2");
942 };
943 let ctls = ctls_val
944 .clone()
945 .unwrap_array()
946 .iter()
947 .map(|q| q.clone().unwrap_qubit().deref().0)
948 .collect::<Vec<_>>();
949 if ctls.iter().all(|&q| !self.is_qubit_lost(q)) {
950 let q = self.sim.allocate();
951 // The new qubit is by-definition in the |0⟩ state, so by reversing the sign of the
952 // angle we can apply the phase to the entire state without increasing its size in memory.
953 self.sim
954 .mcrz(&ctls, -2.0 * theta.clone().unwrap_double(), q);
955 self.sim.release(q);
956 }
957 Some(Ok(Value::unit()))
958 }
959 "BeginEstimateCaching" => Some(Ok(Value::Bool(true))),
960 "EndEstimateCaching"
961 | "AccountForEstimatesInternal"
962 | "BeginRepeatEstimatesInternal"
963 | "EndRepeatEstimatesInternal"
964 | "EnableMemoryComputeArchitecture" => Some(Ok(Value::unit())),
965 "ConfigurePauliNoise" => {
966 let [xv, yv, zv] = &*arg.unwrap_tuple() else {
967 panic!("tuple arity for ConfigurePauliNoise intrinsic should be 3");
968 };
969 let px = xv.get_double();
970 let py = yv.get_double();
971 let pz = zv.get_double();
972 match PauliNoise::from_probabilities(px, py, pz) {
973 Ok(noise) => {
974 self.set_noise(&noise);
975 Some(Ok(Value::unit()))
976 }
977 Err(message) => Some(Err(message)),
978 }
979 }
980 "ConfigureQubitLoss" => {
981 let loss = arg.unwrap_double();
982 if (0.0..=1.0).contains(&loss) {
983 self.set_loss(loss);
984 Some(Ok(Value::unit()))
985 } else {
986 Some(Err(
987 "loss probability must be in between 0.0 and 1.0".to_string()
988 ))
989 }
990 }
991 "ApplyIdleNoise" => {
992 let q = arg.unwrap_qubit().deref().0;
993 self.apply_noise(q);
994 Some(Ok(Value::unit()))
995 }
996 "Apply" => {
997 let [matrix, qubits] = unwrap_tuple(arg);
998 let qubits = qubits
999 .unwrap_array()
1000 .iter()
1001 .filter_map(|q| q.clone().unwrap_qubit().try_deref().map(|q| q.0))
1002 .collect::<Vec<_>>();
1003 let matrix = unwrap_matrix_as_array2(matrix, &qubits);
1004
1005 if qubits.iter().all(|&q| !self.is_qubit_lost(q)) {
1006 // Confirm the matrix is unitary by checking if multiplying it by its adjoint gives the identity matrix (up to numerical precision).
1007 let adj = matrix.t().map(Complex::<f64>::conj);
1008 if (matrix.dot(&adj) - Array2::<Complex<f64>>::eye(1 << qubits.len()))
1009 .map(|x| x.norm())
1010 .sum()
1011 > 1e-9
1012 {
1013 return Some(Err("matrix is not unitary".to_string()));
1014 }
1015
1016 self.sim.apply(&matrix, &qubits, None);
1017 }
1018
1019 Some(Ok(Value::unit()))
1020 }
1021 "PostSelectZ" => {
1022 let [result, qubit] = unwrap_tuple(arg);
1023 let id = qubit.unwrap_qubit().deref().0;
1024 let Value::Result(val::Result::Val(val)) = result else {
1025 panic!("first argument to PostSelectZ should be a measurement result",);
1026 };
1027 let prob = self.sim.force_collapse(val, id);
1028 if prob.is_zero() {
1029 return Some(Err(
1030 "post-selection condition has zero probability".to_string()
1031 ));
1032 }
1033 Some(Ok(Value::unit()))
1034 }
1035 _ => None,
1036 }
1037 }
1038
1039 fn set_seed(&mut self, seed: Option<u64>) {
1040 if let Some(seed) = seed {
1041 if !self.is_noiseless() {
1042 self.rng = Some(StdRng::seed_from_u64(seed));
1043 }
1044 self.sim.set_rng_seed(seed);
1045 } else {
1046 if !self.is_noiseless() {
1047 self.rng = Some(StdRng::from_entropy());
1048 }
1049 self.sim.set_rng_seed(rand::thread_rng().next_u64());
1050 }
1051 }
1052}
1053
1054fn unwrap_matrix_as_array2(matrix: Value, qubits: &[usize]) -> Array2<Complex<f64>> {
1055 let matrix: Vec<Vec<Complex<f64>>> = matrix
1056 .unwrap_array()
1057 .iter()
1058 .map(|row| {
1059 row.clone()
1060 .unwrap_array()
1061 .iter()
1062 .map(|elem| {
1063 let [re, im] = unwrap_tuple(elem.clone());
1064 Complex::<f64>::new(re.unwrap_double(), im.unwrap_double())
1065 })
1066 .collect::<Vec<_>>()
1067 })
1068 .collect::<Vec<_>>();
1069
1070 Array2::from_shape_fn((1 << qubits.len(), 1 << qubits.len()), |(i, j)| {
1071 matrix[i][j]
1072 })
1073}
1074