microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
cesarzc/ssa-panic

Branches

Tags

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

Clone

HTTPS

Download ZIP

compiler/qsc_eval/src/backend.rs

482lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use num_bigint::BigUint;
5use num_complex::Complex;
6use quantum_sparse_sim::QuantumSim;
7use rand::RngCore;
8
9use crate::val::Value;
10
11/// The trait that must be implemented by a quantum backend, whose functions will be invoked when
12/// quantum intrinsics are called.
13pub trait Backend {
14 type ResultType;
15
16 fn ccx(&mut self, _ctl0: usize, _ctl1: usize, _q: usize) {
17 unimplemented!("ccx gate");
18 }
19 fn cx(&mut self, _ctl: usize, _q: usize) {
20 unimplemented!("cx gate");
21 }
22 fn cy(&mut self, _ctl: usize, _q: usize) {
23 unimplemented!("cy gate");
24 }
25 fn cz(&mut self, _ctl: usize, _q: usize) {
26 unimplemented!("cz gate");
27 }
28 fn h(&mut self, _q: usize) {
29 unimplemented!("h gate");
30 }
31 fn m(&mut self, _q: usize) -> Self::ResultType {
32 unimplemented!("m operation");
33 }
34 fn mresetz(&mut self, _q: usize) -> Self::ResultType {
35 unimplemented!("mresetz operation");
36 }
37 fn reset(&mut self, _q: usize) {
38 unimplemented!("reset gate");
39 }
40 fn rx(&mut self, _theta: f64, _q: usize) {
41 unimplemented!("rx gate");
42 }
43 fn rxx(&mut self, _theta: f64, _q0: usize, _q1: usize) {
44 unimplemented!("rxx gate");
45 }
46 fn ry(&mut self, _theta: f64, _q: usize) {
47 unimplemented!("ry gate");
48 }
49 fn ryy(&mut self, _theta: f64, _q0: usize, _q1: usize) {
50 unimplemented!("ryy gate");
51 }
52 fn rz(&mut self, _theta: f64, _q: usize) {
53 unimplemented!("rz gate");
54 }
55 fn rzz(&mut self, _theta: f64, _q0: usize, _q1: usize) {
56 unimplemented!("rzz gate");
57 }
58 fn sadj(&mut self, _q: usize) {
59 unimplemented!("sadj gate");
60 }
61 fn s(&mut self, _q: usize) {
62 unimplemented!("s gate");
63 }
64 fn swap(&mut self, _q0: usize, _q1: usize) {
65 unimplemented!("swap gate");
66 }
67 fn tadj(&mut self, _q: usize) {
68 unimplemented!("tadj gate");
69 }
70 fn t(&mut self, _q: usize) {
71 unimplemented!("t gate");
72 }
73 fn x(&mut self, _q: usize) {
74 unimplemented!("x gate");
75 }
76 fn y(&mut self, _q: usize) {
77 unimplemented!("y gate");
78 }
79 fn z(&mut self, _q: usize) {
80 unimplemented!("z gate");
81 }
82 fn qubit_allocate(&mut self) -> usize {
83 unimplemented!("qubit_allocate operation");
84 }
85 fn qubit_release(&mut self, _q: usize) {
86 unimplemented!("qubit_release operation");
87 }
88 fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
89 unimplemented!("capture_quantum_state operation");
90 }
91 fn qubit_is_zero(&mut self, _q: usize) -> bool {
92 unimplemented!("qubit_is_zero operation");
93 }
94
95 fn custom_intrinsic(&mut self, _name: &str, _arg: Value) -> Option<Result<Value, String>> {
96 None
97 }
98
99 fn set_seed(&mut self, _seed: Option<u64>) {}
100}
101
102/// Default backend used when targeting sparse simulation.
103pub struct SparseSim {
104 pub sim: QuantumSim,
105}
106
107impl Default for SparseSim {
108 fn default() -> Self {
109 Self::new()
110 }
111}
112
113impl SparseSim {
114 #[must_use]
115 pub fn new() -> Self {
116 Self {
117 sim: QuantumSim::new(),
118 }
119 }
120}
121
122impl Backend for SparseSim {
123 type ResultType = bool;
124
125 fn ccx(&mut self, ctl0: usize, ctl1: usize, q: usize) {
126 self.sim.mcx(&[ctl0, ctl1], q);
127 }
128
129 fn cx(&mut self, ctl: usize, q: usize) {
130 self.sim.mcx(&[ctl], q);
131 }
132
133 fn cy(&mut self, ctl: usize, q: usize) {
134 self.sim.mcy(&[ctl], q);
135 }
136
137 fn cz(&mut self, ctl: usize, q: usize) {
138 self.sim.mcz(&[ctl], q);
139 }
140
141 fn h(&mut self, q: usize) {
142 self.sim.h(q);
143 }
144
145 fn m(&mut self, q: usize) -> Self::ResultType {
146 self.sim.measure(q)
147 }
148
149 fn mresetz(&mut self, q: usize) -> Self::ResultType {
150 let res = self.sim.measure(q);
151 if res {
152 self.sim.x(q);
153 }
154 res
155 }
156
157 fn reset(&mut self, q: usize) {
158 self.mresetz(q);
159 }
160
161 fn rx(&mut self, theta: f64, q: usize) {
162 self.sim.rx(theta, q);
163 }
164
165 fn rxx(&mut self, theta: f64, q0: usize, q1: usize) {
166 self.h(q0);
167 self.h(q1);
168 self.rzz(theta, q0, q1);
169 self.h(q1);
170 self.h(q0);
171 }
172
173 fn ry(&mut self, theta: f64, q: usize) {
174 self.sim.ry(theta, q);
175 }
176
177 fn ryy(&mut self, theta: f64, q0: usize, q1: usize) {
178 self.h(q0);
179 self.s(q0);
180 self.h(q0);
181 self.h(q1);
182 self.s(q1);
183 self.h(q1);
184 self.rzz(theta, q0, q1);
185 self.h(q1);
186 self.sadj(q1);
187 self.h(q1);
188 self.h(q0);
189 self.sadj(q0);
190 self.h(q0);
191 }
192
193 fn rz(&mut self, theta: f64, q: usize) {
194 self.sim.rz(theta, q);
195 }
196
197 fn rzz(&mut self, theta: f64, q0: usize, q1: usize) {
198 self.cx(q1, q0);
199 self.rz(theta, q0);
200 self.cx(q1, q0);
201 }
202
203 fn sadj(&mut self, q: usize) {
204 self.sim.sadj(q);
205 }
206
207 fn s(&mut self, q: usize) {
208 self.sim.s(q);
209 }
210
211 fn swap(&mut self, q0: usize, q1: usize) {
212 self.sim.swap_qubit_ids(q0, q1);
213 }
214
215 fn tadj(&mut self, q: usize) {
216 self.sim.tadj(q);
217 }
218
219 fn t(&mut self, q: usize) {
220 self.sim.t(q);
221 }
222
223 fn x(&mut self, q: usize) {
224 self.sim.x(q);
225 }
226
227 fn y(&mut self, q: usize) {
228 self.sim.y(q);
229 }
230
231 fn z(&mut self, q: usize) {
232 self.sim.z(q);
233 }
234
235 fn qubit_allocate(&mut self) -> usize {
236 self.sim.allocate()
237 }
238
239 fn qubit_release(&mut self, q: usize) {
240 self.sim.release(q);
241 }
242
243 fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
244 let (state, count) = self.sim.get_state();
245 // Because the simulator returns the state indices with opposite endianness from the
246 // expected one, we need to reverse the bit order of the indices.
247 let mut new_state = state
248 .into_iter()
249 .map(|(idx, val)| {
250 let mut new_idx = BigUint::default();
251 for i in 0..(count as u64) {
252 if idx.bit((count as u64) - 1 - i) {
253 new_idx.set_bit(i, true);
254 }
255 }
256 (new_idx, val)
257 })
258 .collect::<Vec<_>>();
259 new_state.sort_unstable_by(|a, b| a.0.cmp(&b.0));
260 (new_state, count)
261 }
262
263 fn qubit_is_zero(&mut self, q: usize) -> bool {
264 self.sim.qubit_is_zero(q)
265 }
266
267 fn custom_intrinsic(&mut self, name: &str, arg: Value) -> Option<Result<Value, String>> {
268 match name {
269 "GlobalPhase" => {
270 // Apply a global phase to the simulation by doing an Rz to a fresh qubit.
271 // The controls list may be empty, in which case the phase is applied unconditionally.
272 let [ctls_val, theta] = &*arg.unwrap_tuple() else {
273 panic!("tuple arity for GlobalPhase intrinsic should be 2");
274 };
275 let ctls = ctls_val
276 .clone()
277 .unwrap_array()
278 .iter()
279 .map(|q| q.clone().unwrap_qubit().0)
280 .collect::<Vec<_>>();
281 let q = self.sim.allocate();
282 // The new qubit is by-definition in the |0⟩ state, so by reversing the sign of the
283 // angle we can apply the phase to the entire state without increasing its size in memory.
284 self.sim
285 .mcrz(&ctls, -2.0 * theta.clone().unwrap_double(), q);
286 self.sim.release(q);
287 Some(Ok(Value::unit()))
288 }
289 "BeginEstimateCaching" => Some(Ok(Value::Bool(true))),
290 "EndEstimateCaching"
291 | "AccountForEstimatesInternal"
292 | "BeginRepeatEstimatesInternal"
293 | "EndRepeatEstimatesInternal" => Some(Ok(Value::unit())),
294 _ => None,
295 }
296 }
297
298 fn set_seed(&mut self, seed: Option<u64>) {
299 match seed {
300 Some(seed) => self.sim.set_rng_seed(seed),
301 None => self.sim.set_rng_seed(rand::thread_rng().next_u64()),
302 }
303 }
304}
305
306/// Simple struct that chains two backends together so that the chained
307/// backend is called before the main backend.
308/// For any intrinsics that return a value,
309/// the value returned by the chained backend is ignored.
310/// The value returned by the main backend is returned.
311pub struct Chain<T1, T2> {
312 pub main: T1,
313 pub chained: T2,
314}
315
316impl<T1, T2> Chain<T1, T2>
317where
318 T1: Backend,
319 T2: Backend,
320{
321 pub fn new(primary: T1, chained: T2) -> Chain<T1, T2> {
322 Chain {
323 main: primary,
324 chained,
325 }
326 }
327}
328
329impl<T1, T2> Backend for Chain<T1, T2>
330where
331 T1: Backend,
332 T2: Backend,
333{
334 type ResultType = T1::ResultType;
335
336 fn ccx(&mut self, ctl0: usize, ctl1: usize, q: usize) {
337 self.chained.ccx(ctl0, ctl1, q);
338 self.main.ccx(ctl0, ctl1, q);
339 }
340
341 fn cx(&mut self, ctl: usize, q: usize) {
342 self.chained.cx(ctl, q);
343 self.main.cx(ctl, q);
344 }
345
346 fn cy(&mut self, ctl: usize, q: usize) {
347 self.chained.cy(ctl, q);
348 self.main.cy(ctl, q);
349 }
350
351 fn cz(&mut self, ctl: usize, q: usize) {
352 self.chained.cz(ctl, q);
353 self.main.cz(ctl, q);
354 }
355
356 fn h(&mut self, q: usize) {
357 self.chained.h(q);
358 self.main.h(q);
359 }
360
361 fn m(&mut self, q: usize) -> Self::ResultType {
362 let _ = self.chained.m(q);
363 self.main.m(q)
364 }
365
366 fn mresetz(&mut self, q: usize) -> Self::ResultType {
367 let _ = self.chained.mresetz(q);
368 self.main.mresetz(q)
369 }
370
371 fn reset(&mut self, q: usize) {
372 self.chained.reset(q);
373 self.main.reset(q);
374 }
375
376 fn rx(&mut self, theta: f64, q: usize) {
377 self.chained.rx(theta, q);
378 self.main.rx(theta, q);
379 }
380
381 fn rxx(&mut self, theta: f64, q0: usize, q1: usize) {
382 self.chained.rxx(theta, q0, q1);
383 self.main.rxx(theta, q0, q1);
384 }
385
386 fn ry(&mut self, theta: f64, q: usize) {
387 self.chained.ry(theta, q);
388 self.main.ry(theta, q);
389 }
390
391 fn ryy(&mut self, theta: f64, q0: usize, q1: usize) {
392 self.chained.ryy(theta, q0, q1);
393 self.main.ryy(theta, q0, q1);
394 }
395
396 fn rz(&mut self, theta: f64, q: usize) {
397 self.chained.rz(theta, q);
398 self.main.rz(theta, q);
399 }
400
401 fn rzz(&mut self, theta: f64, q0: usize, q1: usize) {
402 self.chained.rzz(theta, q0, q1);
403 self.main.rzz(theta, q0, q1);
404 }
405
406 fn sadj(&mut self, q: usize) {
407 self.chained.sadj(q);
408 self.main.sadj(q);
409 }
410
411 fn s(&mut self, q: usize) {
412 self.chained.s(q);
413 self.main.s(q);
414 }
415
416 fn swap(&mut self, q0: usize, q1: usize) {
417 self.chained.swap(q0, q1);
418 self.main.swap(q0, q1);
419 }
420
421 fn tadj(&mut self, q: usize) {
422 self.chained.tadj(q);
423 self.main.tadj(q);
424 }
425
426 fn t(&mut self, q: usize) {
427 self.chained.t(q);
428 self.main.t(q);
429 }
430
431 fn x(&mut self, q: usize) {
432 self.chained.x(q);
433 self.main.x(q);
434 }
435
436 fn y(&mut self, q: usize) {
437 self.chained.y(q);
438 self.main.y(q);
439 }
440
441 fn z(&mut self, q: usize) {
442 self.chained.z(q);
443 self.main.z(q);
444 }
445
446 fn qubit_allocate(&mut self) -> usize {
447 // Warning: we use the qubit id allocated by the
448 // main backend, even for later calls into the chained
449 // backend. This is not an issue today, but could
450 // become an issue if the qubit ids differ between
451 // the two backends.
452 let _ = self.chained.qubit_allocate();
453 self.main.qubit_allocate()
454 }
455
456 fn qubit_release(&mut self, q: usize) {
457 self.chained.qubit_release(q);
458 self.main.qubit_release(q);
459 }
460
461 fn capture_quantum_state(
462 &mut self,
463 ) -> (Vec<(num_bigint::BigUint, num_complex::Complex<f64>)>, usize) {
464 let _ = self.chained.capture_quantum_state();
465 self.main.capture_quantum_state()
466 }
467
468 fn qubit_is_zero(&mut self, q: usize) -> bool {
469 let _ = self.chained.qubit_is_zero(q);
470 self.main.qubit_is_zero(q)
471 }
472
473 fn custom_intrinsic(&mut self, name: &str, arg: Value) -> Option<Result<Value, String>> {
474 let _ = self.chained.custom_intrinsic(name, arg.clone());
475 self.main.custom_intrinsic(name, arg)
476 }
477
478 fn set_seed(&mut self, seed: Option<u64>) {
479 self.chained.set_seed(seed);
480 self.main.set_seed(seed);
481 }
482}
483