microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
iadavis/openqasm-extern-compilation

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/compiler/qsc_eval/src/backend.rs

854lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use crate::val::{self, Value};
5use crate::{noise::PauliNoise, val::unwrap_tuple};
6use ndarray::Array2;
7use num_bigint::BigUint;
8use num_complex::Complex;
9use num_traits::Zero;
10use quantum_sparse_sim::QuantumSim;
11use rand::{Rng, RngCore, SeedableRng, rngs::StdRng};
12
13#[cfg(test)]
14mod noise_tests;
15
16/// The trait that must be implemented by a quantum backend, whose functions will be invoked when
17/// quantum intrinsics are called.
18pub trait Backend {
19 type ResultType;
20
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) -> Self::ResultType {
37 unimplemented!("m operation");
38 }
39 fn mresetz(&mut self, _q: usize) -> Self::ResultType {
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/// Default backend used when targeting sparse simulation.
119pub struct SparseSim {
120 /// Noiseless Sparse simulator to be used by this instance.
121 pub sim: QuantumSim,
122 /// Pauli noise that is applied after a gate or before a measurement is executed.
123 /// Service functions aren't subject to noise.
124 pub noise: PauliNoise,
125 /// Loss probability for the qubit, which is applied before a measurement.
126 pub loss: f64,
127 /// A bit vector that tracks which qubits were lost.
128 pub lost_qubits: BigUint,
129 /// Random number generator to sample Pauli noise.
130 /// Noise is not applied when rng is None.
131 pub rng: Option<StdRng>,
132}
133
134impl Default for SparseSim {
135 fn default() -> Self {
136 Self::new()
137 }
138}
139
140impl SparseSim {
141 #[must_use]
142 pub fn new() -> Self {
143 Self {
144 sim: QuantumSim::new(None),
145 noise: PauliNoise::default(),
146 loss: f64::zero(),
147 lost_qubits: BigUint::zero(),
148 rng: None,
149 }
150 }
151
152 #[must_use]
153 pub fn new_with_noise(noise: &PauliNoise) -> Self {
154 let mut sim = SparseSim::new();
155 sim.set_noise(noise);
156 sim
157 }
158
159 fn set_noise(&mut self, noise: &PauliNoise) {
160 self.noise = *noise;
161 if noise.is_noiseless() && self.loss.is_zero() {
162 self.rng = None;
163 } else {
164 self.rng = Some(StdRng::from_entropy());
165 }
166 }
167
168 pub fn set_loss(&mut self, loss: f64) {
169 self.loss = loss;
170 if loss.is_zero() && self.noise.is_noiseless() {
171 self.rng = None;
172 } else {
173 self.rng = Some(StdRng::from_entropy());
174 }
175 }
176
177 #[must_use]
178 fn is_noiseless(&self) -> bool {
179 self.rng.is_none()
180 }
181
182 fn apply_noise(&mut self, q: usize) {
183 if self.is_qubit_lost(q) {
184 // If the qubit is already lost, we don't apply noise.
185 return;
186 }
187 if let Some(rng) = &mut self.rng {
188 // First, check for loss.
189 let p = rng.gen_range(0.0..1.0);
190 if p < self.loss {
191 // The qubit is lost, so we reset it.
192 // It is not safe to release the qubit here, as that may
193 // interfere with later operations (gates or measurements)
194 // or even normal qubit release at end of scope.
195 if self.sim.measure(q) {
196 self.sim.x(q);
197 }
198 // Mark the qubit as lost.
199 self.lost_qubits.set_bit(q as u64, true);
200 return;
201 }
202
203 // Apply noise with a probability distribution defined in `self.noise`.
204 let p = rng.gen_range(0.0..1.0);
205 if p >= self.noise.distribution[2] {
206 // In the most common case we don't apply noise
207 } else if p < self.noise.distribution[0] {
208 self.sim.x(q);
209 } else if p < self.noise.distribution[1] {
210 self.sim.y(q);
211 } else {
212 self.sim.z(q);
213 }
214 }
215 // No noise applied if rng is None.
216 }
217
218 /// Checks if the qubit is lost.
219 fn is_qubit_lost(&self, q: usize) -> bool {
220 self.lost_qubits.bit(q as u64)
221 }
222}
223
224impl Backend for SparseSim {
225 type ResultType = val::Result;
226
227 fn ccx(&mut self, ctl0: usize, ctl1: usize, q: usize) {
228 match (
229 self.is_qubit_lost(ctl0),
230 self.is_qubit_lost(ctl1),
231 self.is_qubit_lost(q),
232 ) {
233 (true, true, _) | (_, _, true) => {
234 // If the target qubit is lost or both controls are lost, skip the operation.
235 }
236
237 // When only one control is lost, use the other to do a singly controlled X.
238 (true, false, false) => {
239 self.sim.mcx(&[ctl1], q);
240 }
241 (false, true, false) => {
242 self.sim.mcx(&[ctl0], q);
243 }
244
245 // No qubits lost, execute normally.
246 (false, false, false) => {
247 self.sim.mcx(&[ctl0, ctl1], q);
248 }
249 }
250 self.apply_noise(ctl0);
251 self.apply_noise(ctl1);
252 self.apply_noise(q);
253 }
254
255 fn cx(&mut self, ctl: usize, q: usize) {
256 if !self.is_qubit_lost(ctl) && !self.is_qubit_lost(q) {
257 self.sim.mcx(&[ctl], q);
258 }
259 self.apply_noise(ctl);
260 self.apply_noise(q);
261 }
262
263 fn cy(&mut self, ctl: usize, q: usize) {
264 if !self.is_qubit_lost(ctl) && !self.is_qubit_lost(q) {
265 self.sim.mcy(&[ctl], q);
266 }
267 self.apply_noise(ctl);
268 self.apply_noise(q);
269 }
270
271 fn cz(&mut self, ctl: usize, q: usize) {
272 if !self.is_qubit_lost(ctl) && !self.is_qubit_lost(q) {
273 self.sim.mcz(&[ctl], q);
274 }
275 self.apply_noise(ctl);
276 self.apply_noise(q);
277 }
278
279 fn h(&mut self, q: usize) {
280 if !self.is_qubit_lost(q) {
281 self.sim.h(q);
282 }
283 self.apply_noise(q);
284 }
285
286 fn m(&mut self, q: usize) -> Self::ResultType {
287 self.apply_noise(q);
288 if self.is_qubit_lost(q) {
289 // If the qubit is lost, we cannot measure it.
290 // Mark it as no longer lost so it becomes usable again, since
291 // measurement will "reload" the qubit.
292 self.lost_qubits.set_bit(q as u64, false);
293 return val::Result::Loss;
294 }
295 val::Result::Val(self.sim.measure(q))
296 }
297
298 fn mresetz(&mut self, q: usize) -> Self::ResultType {
299 self.apply_noise(q); // Applying noise before measurement
300 if self.is_qubit_lost(q) {
301 // If the qubit is lost, we cannot measure it.
302 // Mark it as no longer lost so it becomes usable again, since
303 // measurement will "reload" the qubit.
304 self.lost_qubits.set_bit(q as u64, false);
305 return val::Result::Loss;
306 }
307 let res = self.sim.measure(q);
308 if res {
309 self.sim.x(q);
310 }
311 self.apply_noise(q); // Applying noise after reset
312 val::Result::Val(res)
313 }
314
315 fn reset(&mut self, q: usize) {
316 self.mresetz(q);
317 // Noise applied in mresetz.
318 }
319
320 fn rx(&mut self, theta: f64, q: usize) {
321 if !self.is_qubit_lost(q) {
322 self.sim.rx(theta, q);
323 }
324 self.apply_noise(q);
325 }
326
327 fn rxx(&mut self, theta: f64, q0: usize, q1: usize) {
328 // If only one qubit is lost, we can apply a single qubit rotation.
329 // If both are lost, return without performing any operation.
330 match (self.is_qubit_lost(q0), self.is_qubit_lost(q1)) {
331 (true, false) => {
332 self.sim.rx(theta, q1);
333 }
334 (false, true) => {
335 self.sim.rx(theta, q0);
336 }
337 (true, true) => {}
338 (false, false) => {
339 self.sim.h(q0);
340 self.sim.h(q1);
341 self.sim.mcx(&[q1], q0);
342 self.sim.rz(theta, q0);
343 self.sim.mcx(&[q1], q0);
344 self.sim.h(q1);
345 self.sim.h(q0);
346 }
347 }
348 self.apply_noise(q0);
349 self.apply_noise(q1);
350 }
351
352 fn ry(&mut self, theta: f64, q: usize) {
353 if !self.is_qubit_lost(q) {
354 self.sim.ry(theta, q);
355 }
356 self.apply_noise(q);
357 }
358
359 fn ryy(&mut self, theta: f64, q0: usize, q1: usize) {
360 // If only one qubit is lost, we can apply a single qubit rotation.
361 // If both are lost, return without performing any operation.
362 match (self.is_qubit_lost(q0), self.is_qubit_lost(q1)) {
363 (true, false) => {
364 self.sim.ry(theta, q1);
365 }
366 (false, true) => {
367 self.sim.ry(theta, q0);
368 }
369 (true, true) => {}
370 (false, false) => {
371 self.sim.h(q0);
372 self.sim.s(q0);
373 self.sim.h(q0);
374 self.sim.h(q1);
375 self.sim.s(q1);
376 self.sim.h(q1);
377 self.sim.mcx(&[q1], q0);
378 self.sim.rz(theta, q0);
379 self.sim.mcx(&[q1], q0);
380 self.sim.h(q1);
381 self.sim.sadj(q1);
382 self.sim.h(q1);
383 self.sim.h(q0);
384 self.sim.sadj(q0);
385 self.sim.h(q0);
386 }
387 }
388 self.apply_noise(q0);
389 self.apply_noise(q1);
390 }
391
392 fn rz(&mut self, theta: f64, q: usize) {
393 if !self.is_qubit_lost(q) {
394 self.sim.rz(theta, q);
395 }
396 self.apply_noise(q);
397 }
398
399 fn rzz(&mut self, theta: f64, q0: usize, q1: usize) {
400 // If only one qubit is lost, we can apply a single qubit rotation.
401 // If both are lost, return without performing any operation.
402 match (self.is_qubit_lost(q0), self.is_qubit_lost(q1)) {
403 (true, false) => {
404 self.sim.rz(theta, q1);
405 }
406 (false, true) => {
407 self.sim.rz(theta, q0);
408 }
409 (true, true) => {}
410 (false, false) => {
411 self.sim.mcx(&[q1], q0);
412 self.sim.rz(theta, q0);
413 self.sim.mcx(&[q1], q0);
414 }
415 }
416 self.apply_noise(q0);
417 self.apply_noise(q1);
418 }
419
420 fn sadj(&mut self, q: usize) {
421 if !self.is_qubit_lost(q) {
422 self.sim.sadj(q);
423 }
424 self.apply_noise(q);
425 }
426
427 fn s(&mut self, q: usize) {
428 if !self.is_qubit_lost(q) {
429 self.sim.s(q);
430 }
431 self.apply_noise(q);
432 }
433
434 fn sx(&mut self, q: usize) {
435 if !self.is_qubit_lost(q) {
436 self.sim.h(q);
437 self.sim.s(q);
438 self.sim.h(q);
439 }
440 self.apply_noise(q);
441 }
442
443 fn swap(&mut self, q0: usize, q1: usize) {
444 if !self.is_qubit_lost(q0) && !self.is_qubit_lost(q1) {
445 self.sim.swap_qubit_ids(q0, q1);
446 }
447 self.apply_noise(q0);
448 self.apply_noise(q1);
449 }
450
451 fn tadj(&mut self, q: usize) {
452 if !self.is_qubit_lost(q) {
453 self.sim.tadj(q);
454 }
455 self.apply_noise(q);
456 }
457
458 fn t(&mut self, q: usize) {
459 if !self.is_qubit_lost(q) {
460 self.sim.t(q);
461 }
462 self.apply_noise(q);
463 }
464
465 fn x(&mut self, q: usize) {
466 if !self.is_qubit_lost(q) {
467 self.sim.x(q);
468 }
469 self.apply_noise(q);
470 }
471
472 fn y(&mut self, q: usize) {
473 if !self.is_qubit_lost(q) {
474 self.sim.y(q);
475 }
476 self.apply_noise(q);
477 }
478
479 fn z(&mut self, q: usize) {
480 if !self.is_qubit_lost(q) {
481 self.sim.z(q);
482 }
483 self.apply_noise(q);
484 }
485
486 fn qubit_allocate(&mut self) -> usize {
487 // Fresh qubit start in ground state even with noise.
488 self.sim.allocate()
489 }
490
491 fn qubit_release(&mut self, q: usize) -> bool {
492 if self.is_noiseless() {
493 let was_zero = self.sim.qubit_is_zero(q);
494 self.sim.release(q);
495 was_zero
496 } else {
497 self.sim.release(q);
498 true
499 }
500 }
501
502 fn qubit_swap_id(&mut self, q0: usize, q1: usize) {
503 // This is a service function rather than a gate so it doesn't incur noise.
504 self.sim.swap_qubit_ids(q0, q1);
505 // We must also swap any loss bits for the qubits.
506 let (q0_lost, q1_lost) = (
507 self.lost_qubits.bit(q0 as u64),
508 self.lost_qubits.bit(q1 as u64),
509 );
510 if q0_lost != q1_lost {
511 // If the loss state is different, we need to swap them.
512 self.lost_qubits.set_bit(q0 as u64, q1_lost);
513 self.lost_qubits.set_bit(q1 as u64, q0_lost);
514 }
515 }
516
517 fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
518 let (state, count) = self.sim.get_state();
519 // Because the simulator returns the state indices with opposite endianness from the
520 // expected one, we need to reverse the bit order of the indices.
521 let mut new_state = state
522 .into_iter()
523 .map(|(idx, val)| {
524 let mut new_idx = BigUint::default();
525 for i in 0..(count as u64) {
526 if idx.bit((count as u64) - 1 - i) {
527 new_idx.set_bit(i, true);
528 }
529 }
530 (new_idx, val)
531 })
532 .collect::<Vec<_>>();
533 new_state.sort_unstable_by(|a, b| a.0.cmp(&b.0));
534 (new_state, count)
535 }
536
537 fn qubit_is_zero(&mut self, q: usize) -> bool {
538 // This is a service function rather than a measurement so it doesn't incur noise.
539 self.sim.qubit_is_zero(q)
540 }
541
542 fn custom_intrinsic(&mut self, name: &str, arg: Value) -> Option<Result<Value, String>> {
543 // These intrinsics aren't subject to noise.
544 match name {
545 "GlobalPhase" => {
546 // Apply a global phase to the simulation by doing an Rz to a fresh qubit.
547 // The controls list may be empty, in which case the phase is applied unconditionally.
548 let [ctls_val, theta] = &*arg.unwrap_tuple() else {
549 panic!("tuple arity for GlobalPhase intrinsic should be 2");
550 };
551 let ctls = ctls_val
552 .clone()
553 .unwrap_array()
554 .iter()
555 .map(|q| q.clone().unwrap_qubit().deref().0)
556 .collect::<Vec<_>>();
557 if ctls.iter().all(|&q| !self.is_qubit_lost(q)) {
558 let q = self.sim.allocate();
559 // The new qubit is by-definition in the |0⟩ state, so by reversing the sign of the
560 // angle we can apply the phase to the entire state without increasing its size in memory.
561 self.sim
562 .mcrz(&ctls, -2.0 * theta.clone().unwrap_double(), q);
563 self.sim.release(q);
564 }
565 Some(Ok(Value::unit()))
566 }
567 "BeginEstimateCaching" => Some(Ok(Value::Bool(true))),
568 "EndEstimateCaching"
569 | "AccountForEstimatesInternal"
570 | "BeginRepeatEstimatesInternal"
571 | "EndRepeatEstimatesInternal" => Some(Ok(Value::unit())),
572 "ConfigurePauliNoise" => {
573 let [xv, yv, zv] = &*arg.unwrap_tuple() else {
574 panic!("tuple arity for ConfigurePauliNoise intrinsic should be 3");
575 };
576 let px = xv.get_double();
577 let py = yv.get_double();
578 let pz = zv.get_double();
579 match PauliNoise::from_probabilities(px, py, pz) {
580 Ok(noise) => {
581 self.set_noise(&noise);
582 Some(Ok(Value::unit()))
583 }
584 Err(message) => Some(Err(message)),
585 }
586 }
587 "ConfigureQubitLoss" => {
588 let loss = arg.unwrap_double();
589 if (0.0..=1.0).contains(&loss) {
590 self.set_loss(loss);
591 Some(Ok(Value::unit()))
592 } else {
593 Some(Err(
594 "loss probability must be in between 0.0 and 1.0".to_string()
595 ))
596 }
597 }
598 "ApplyIdleNoise" => {
599 let q = arg.unwrap_qubit().deref().0;
600 self.apply_noise(q);
601 Some(Ok(Value::unit()))
602 }
603 "Apply" => {
604 let [matrix, qubits] = unwrap_tuple(arg);
605 let qubits = qubits
606 .unwrap_array()
607 .iter()
608 .filter_map(|q| q.clone().unwrap_qubit().try_deref().map(|q| q.0))
609 .collect::<Vec<_>>();
610 let matrix = unwrap_matrix_as_array2(matrix, &qubits);
611
612 if qubits.iter().all(|&q| !self.is_qubit_lost(q)) {
613 // Confirm the matrix is unitary by checking if multiplying it by its adjoint gives the identity matrix (up to numerical precision).
614 let adj = matrix.t().map(Complex::<f64>::conj);
615 if (matrix.dot(&adj) - Array2::<Complex<f64>>::eye(1 << qubits.len()))
616 .map(|x| x.norm())
617 .sum()
618 > 1e-9
619 {
620 return Some(Err("matrix is not unitary".to_string()));
621 }
622
623 self.sim.apply(&matrix, &qubits, None);
624 }
625
626 Some(Ok(Value::unit()))
627 }
628 _ => None,
629 }
630 }
631
632 fn set_seed(&mut self, seed: Option<u64>) {
633 if let Some(seed) = seed {
634 if !self.is_noiseless() {
635 self.rng = Some(StdRng::seed_from_u64(seed));
636 }
637 self.sim.set_rng_seed(seed);
638 } else {
639 if !self.is_noiseless() {
640 self.rng = Some(StdRng::from_entropy());
641 }
642 self.sim.set_rng_seed(rand::thread_rng().next_u64());
643 }
644 }
645}
646
647fn unwrap_matrix_as_array2(matrix: Value, qubits: &[usize]) -> Array2<Complex<f64>> {
648 let matrix: Vec<Vec<Complex<f64>>> = matrix
649 .unwrap_array()
650 .iter()
651 .map(|row| {
652 row.clone()
653 .unwrap_array()
654 .iter()
655 .map(|elem| {
656 let [re, im] = unwrap_tuple(elem.clone());
657 Complex::<f64>::new(re.unwrap_double(), im.unwrap_double())
658 })
659 .collect::<Vec<_>>()
660 })
661 .collect::<Vec<_>>();
662
663 Array2::from_shape_fn((1 << qubits.len(), 1 << qubits.len()), |(i, j)| {
664 matrix[i][j]
665 })
666}
667
668/// Simple struct that chains two backends together so that the chained
669/// backend is called before the main backend.
670/// For any intrinsics that return a value,
671/// the value returned by the chained backend is ignored.
672/// The value returned by the main backend is returned.
673pub struct Chain<T1, T2> {
674 pub main: T1,
675 pub chained: T2,
676}
677
678impl<T1, T2> Chain<T1, T2>
679where
680 T1: Backend,
681 T2: Backend,
682{
683 pub fn new(primary: T1, chained: T2) -> Chain<T1, T2> {
684 Chain {
685 main: primary,
686 chained,
687 }
688 }
689}
690
691impl<T1, T2> Backend for Chain<T1, T2>
692where
693 T1: Backend,
694 T2: Backend,
695{
696 type ResultType = T1::ResultType;
697
698 fn ccx(&mut self, ctl0: usize, ctl1: usize, q: usize) {
699 self.chained.ccx(ctl0, ctl1, q);
700 self.main.ccx(ctl0, ctl1, q);
701 }
702
703 fn cx(&mut self, ctl: usize, q: usize) {
704 self.chained.cx(ctl, q);
705 self.main.cx(ctl, q);
706 }
707
708 fn cy(&mut self, ctl: usize, q: usize) {
709 self.chained.cy(ctl, q);
710 self.main.cy(ctl, q);
711 }
712
713 fn cz(&mut self, ctl: usize, q: usize) {
714 self.chained.cz(ctl, q);
715 self.main.cz(ctl, q);
716 }
717
718 fn h(&mut self, q: usize) {
719 self.chained.h(q);
720 self.main.h(q);
721 }
722
723 fn m(&mut self, q: usize) -> Self::ResultType {
724 let _ = self.chained.m(q);
725 self.main.m(q)
726 }
727
728 fn mresetz(&mut self, q: usize) -> Self::ResultType {
729 let _ = self.chained.mresetz(q);
730 self.main.mresetz(q)
731 }
732
733 fn reset(&mut self, q: usize) {
734 self.chained.reset(q);
735 self.main.reset(q);
736 }
737
738 fn rx(&mut self, theta: f64, q: usize) {
739 self.chained.rx(theta, q);
740 self.main.rx(theta, q);
741 }
742
743 fn rxx(&mut self, theta: f64, q0: usize, q1: usize) {
744 self.chained.rxx(theta, q0, q1);
745 self.main.rxx(theta, q0, q1);
746 }
747
748 fn ry(&mut self, theta: f64, q: usize) {
749 self.chained.ry(theta, q);
750 self.main.ry(theta, q);
751 }
752
753 fn ryy(&mut self, theta: f64, q0: usize, q1: usize) {
754 self.chained.ryy(theta, q0, q1);
755 self.main.ryy(theta, q0, q1);
756 }
757
758 fn rz(&mut self, theta: f64, q: usize) {
759 self.chained.rz(theta, q);
760 self.main.rz(theta, q);
761 }
762
763 fn rzz(&mut self, theta: f64, q0: usize, q1: usize) {
764 self.chained.rzz(theta, q0, q1);
765 self.main.rzz(theta, q0, q1);
766 }
767
768 fn sadj(&mut self, q: usize) {
769 self.chained.sadj(q);
770 self.main.sadj(q);
771 }
772
773 fn s(&mut self, q: usize) {
774 self.chained.s(q);
775 self.main.s(q);
776 }
777
778 fn sx(&mut self, q: usize) {
779 self.chained.sx(q);
780 self.main.sx(q);
781 }
782
783 fn swap(&mut self, q0: usize, q1: usize) {
784 self.chained.swap(q0, q1);
785 self.main.swap(q0, q1);
786 }
787
788 fn tadj(&mut self, q: usize) {
789 self.chained.tadj(q);
790 self.main.tadj(q);
791 }
792
793 fn t(&mut self, q: usize) {
794 self.chained.t(q);
795 self.main.t(q);
796 }
797
798 fn x(&mut self, q: usize) {
799 self.chained.x(q);
800 self.main.x(q);
801 }
802
803 fn y(&mut self, q: usize) {
804 self.chained.y(q);
805 self.main.y(q);
806 }
807
808 fn z(&mut self, q: usize) {
809 self.chained.z(q);
810 self.main.z(q);
811 }
812
813 fn qubit_allocate(&mut self) -> usize {
814 // Warning: we use the qubit id allocated by the
815 // main backend, even for later calls into the chained
816 // backend. This is not an issue today, but could
817 // become an issue if the qubit ids differ between
818 // the two backends.
819 let _ = self.chained.qubit_allocate();
820 self.main.qubit_allocate()
821 }
822
823 fn qubit_release(&mut self, q: usize) -> bool {
824 let _ = self.chained.qubit_release(q);
825 self.main.qubit_release(q)
826 }
827
828 fn qubit_swap_id(&mut self, q0: usize, q1: usize) {
829 self.chained.qubit_swap_id(q0, q1);
830 self.main.qubit_swap_id(q0, q1);
831 }
832
833 fn capture_quantum_state(
834 &mut self,
835 ) -> (Vec<(num_bigint::BigUint, num_complex::Complex<f64>)>, usize) {
836 let _ = self.chained.capture_quantum_state();
837 self.main.capture_quantum_state()
838 }
839
840 fn qubit_is_zero(&mut self, q: usize) -> bool {
841 let _ = self.chained.qubit_is_zero(q);
842 self.main.qubit_is_zero(q)
843 }
844
845 fn custom_intrinsic(&mut self, name: &str, arg: Value) -> Option<Result<Value, String>> {
846 let _ = self.chained.custom_intrinsic(name, arg.clone());
847 self.main.custom_intrinsic(name, arg)
848 }
849
850 fn set_seed(&mut self, seed: Option<u64>) {
851 self.chained.set_seed(seed);
852 self.main.set_seed(seed);
853 }
854}