microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.29.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/compiler/qsc_eval/src/backend.rs

1488lines · modecode

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