microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/fix-wasm-logging-issue

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/compiler/qsc_eval/src/backend.rs

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