microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.16.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

compiler/qsc_circuit/src/builder.rs

512lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#[cfg(test)]
5mod tests;
6
7use crate::{
8 circuit::{operation_list_to_grid, Circuit, Ket, Measurement, Operation, Register, Unitary},
9 Config,
10};
11use num_bigint::BigUint;
12use num_complex::Complex;
13use qsc_data_structures::index_map::IndexMap;
14use qsc_eval::{backend::Backend, val::Value};
15use std::{fmt::Write, mem::take, rc::Rc};
16
17/// Backend implementation that builds a circuit representation.
18pub struct Builder {
19 max_ops_exceeded: bool,
20 operations: Vec<Operation>,
21 config: Config,
22 remapper: Remapper,
23}
24
25impl Backend for Builder {
26 type ResultType = usize;
27
28 fn ccx(&mut self, ctl0: usize, ctl1: usize, q: usize) {
29 let ctl0 = self.map(ctl0);
30 let ctl1 = self.map(ctl1);
31 let q = self.map(q);
32 self.push_gate(controlled_gate("X", [ctl0, ctl1], [q]));
33 }
34
35 fn cx(&mut self, ctl: usize, q: usize) {
36 let ctl = self.map(ctl);
37 let q = self.map(q);
38 self.push_gate(controlled_gate("X", [ctl], [q]));
39 }
40
41 fn cy(&mut self, ctl: usize, q: usize) {
42 let ctl = self.map(ctl);
43 let q = self.map(q);
44 self.push_gate(controlled_gate("Y", [ctl], [q]));
45 }
46
47 fn cz(&mut self, ctl: usize, q: usize) {
48 let ctl = self.map(ctl);
49 let q = self.map(q);
50 self.push_gate(controlled_gate("Z", [ctl], [q]));
51 }
52
53 fn h(&mut self, q: usize) {
54 let q = self.map(q);
55 self.push_gate(gate("H", [q]));
56 }
57
58 fn m(&mut self, q: usize) -> Self::ResultType {
59 let mapped_q = self.map(q);
60 // In the Circuit schema, result id is per-qubit
61 let res_id = self.num_measurements_for_qubit(mapped_q);
62 let id = self.remapper.m(q);
63
64 self.push_gate(measurement_gate(mapped_q.0, res_id));
65 id
66 }
67
68 fn mresetz(&mut self, q: usize) -> Self::ResultType {
69 let mapped_q = self.map(q);
70 // In the Circuit schema, result id is per-qubit
71 let res_id = self.num_measurements_for_qubit(mapped_q);
72 // We don't actually need the Remapper since we're not
73 // remapping any qubits, but it's handy for keeping track of measurements
74 let id = self.remapper.m(q);
75
76 // Ideally MResetZ would be atomic but we don't currently have
77 // a way to visually represent that. So decompose it into
78 // a measurement and a reset gate.
79 self.push_gate(measurement_gate(mapped_q.0, res_id));
80 self.push_gate(ket_gate("0", [mapped_q]));
81 id
82 }
83
84 fn reset(&mut self, q: usize) {
85 let mapped_q = self.map(q);
86 self.push_gate(ket_gate("0", [mapped_q]));
87 }
88
89 fn rx(&mut self, theta: f64, q: usize) {
90 let q = self.map(q);
91 self.push_gate(rotation_gate("Rx", theta, [q]));
92 }
93
94 fn rxx(&mut self, theta: f64, q0: usize, q1: usize) {
95 let q0 = self.map(q0);
96 let q1 = self.map(q1);
97 self.push_gate(rotation_gate("Rxx", theta, [q0, q1]));
98 }
99
100 fn ry(&mut self, theta: f64, q: usize) {
101 let q = self.map(q);
102 self.push_gate(rotation_gate("Ry", theta, [q]));
103 }
104
105 fn ryy(&mut self, theta: f64, q0: usize, q1: usize) {
106 let q0 = self.map(q0);
107 let q1 = self.map(q1);
108 self.push_gate(rotation_gate("Ryy", theta, [q0, q1]));
109 }
110
111 fn rz(&mut self, theta: f64, q: usize) {
112 let q = self.map(q);
113 self.push_gate(rotation_gate("Rz", theta, [q]));
114 }
115
116 fn rzz(&mut self, theta: f64, q0: usize, q1: usize) {
117 let q0 = self.map(q0);
118 let q1 = self.map(q1);
119 self.push_gate(rotation_gate("Rzz", theta, [q0, q1]));
120 }
121
122 fn sadj(&mut self, q: usize) {
123 let q = self.map(q);
124 self.push_gate(adjoint_gate("S", [q]));
125 }
126
127 fn s(&mut self, q: usize) {
128 let q = self.map(q);
129 self.push_gate(gate("S", [q]));
130 }
131
132 fn swap(&mut self, q0: usize, q1: usize) {
133 let q0 = self.map(q0);
134 let q1 = self.map(q1);
135 self.push_gate(gate("SWAP", [q0, q1]));
136 }
137
138 fn tadj(&mut self, q: usize) {
139 let q = self.map(q);
140 self.push_gate(adjoint_gate("T", [q]));
141 }
142
143 fn t(&mut self, q: usize) {
144 let q = self.map(q);
145 self.push_gate(gate("T", [q]));
146 }
147
148 fn x(&mut self, q: usize) {
149 let q = self.map(q);
150 self.push_gate(gate("X", [q]));
151 }
152
153 fn y(&mut self, q: usize) {
154 let q = self.map(q);
155 self.push_gate(gate("Y", [q]));
156 }
157
158 fn z(&mut self, q: usize) {
159 let q = self.map(q);
160 self.push_gate(gate("Z", [q]));
161 }
162
163 fn qubit_allocate(&mut self) -> usize {
164 self.remapper.qubit_allocate()
165 }
166
167 fn qubit_release(&mut self, q: usize) -> bool {
168 self.remapper.qubit_release(q);
169 true
170 }
171
172 fn qubit_swap_id(&mut self, q0: usize, q1: usize) {
173 self.remapper.swap(q0, q1);
174 }
175
176 fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
177 (Vec::new(), 0)
178 }
179
180 fn qubit_is_zero(&mut self, _q: usize) -> bool {
181 // We don't simulate quantum execution here. So we don't know if the qubit
182 // is zero or not. Returning true avoids potential panics.
183 true
184 }
185
186 fn custom_intrinsic(&mut self, name: &str, arg: Value) -> Option<Result<Value, String>> {
187 // The qubit arguments are treated as the targets for custom gates.
188 // Any remaining arguments will be kept in the display_args field
189 // to be shown as part of the gate label when the circuit is rendered.
190 let (qubit_args, classical_args) = self.split_qubit_args(arg);
191
192 self.push_gate(custom_gate(
193 name,
194 &qubit_args,
195 if classical_args.is_empty() {
196 vec![]
197 } else {
198 vec![classical_args]
199 },
200 ));
201
202 match name {
203 // Special case this known intrinsic to match the simulator
204 // behavior, so that our samples will work
205 "BeginEstimateCaching" => Some(Ok(Value::Bool(true))),
206 _ => Some(Ok(Value::unit())),
207 }
208 }
209}
210
211impl Builder {
212 #[must_use]
213 pub fn new(config: Config) -> Self {
214 Builder {
215 max_ops_exceeded: false,
216 operations: vec![],
217 config,
218 remapper: Remapper::default(),
219 }
220 }
221
222 #[must_use]
223 pub fn snapshot(&self) -> Circuit {
224 let operations = self.operations.clone();
225 self.finish_circuit(operations)
226 }
227
228 #[must_use]
229 pub fn finish(mut self) -> Circuit {
230 let operations = take(&mut self.operations);
231 self.finish_circuit(operations)
232 }
233
234 fn map(&mut self, qubit: usize) -> WireId {
235 self.remapper.map(qubit)
236 }
237
238 fn push_gate(&mut self, gate: Operation) {
239 if self.max_ops_exceeded || self.operations.len() >= self.config.max_operations {
240 // Stop adding gates and leave the circuit as is
241 self.max_ops_exceeded = true;
242 return;
243 }
244 self.operations.push(gate);
245 }
246
247 fn num_measurements_for_qubit(&self, qubit: WireId) -> usize {
248 self.remapper
249 .qubit_measurement_counts
250 .get(qubit)
251 .copied()
252 .unwrap_or_default()
253 }
254
255 fn finish_circuit(&self, operations: Vec<Operation>) -> Circuit {
256 let mut qubits = vec![];
257
258 // add qubit declarations
259 for i in 0..self.remapper.num_qubits() {
260 let num_measurements = self.num_measurements_for_qubit(WireId(i));
261 qubits.push(crate::circuit::Qubit {
262 id: i,
263 num_results: num_measurements,
264 });
265 }
266
267 Circuit {
268 component_grid: operation_list_to_grid(operations, qubits.len()),
269 qubits,
270 }
271 }
272
273 /// Splits the qubit arguments from classical arguments so that the qubits
274 /// can be treated as the targets for custom gates.
275 /// The classical arguments get formatted into a comma-separated list.
276 fn split_qubit_args(&mut self, arg: Value) -> (Vec<WireId>, String) {
277 let arg = if let Value::Tuple(vals) = arg {
278 vals
279 } else {
280 // Single arguments are not passed as tuples, wrap in an array
281 Rc::new([arg])
282 };
283 let mut qubits = vec![];
284 let mut classical_args = String::new();
285 self.push_vals(&arg, &mut qubits, &mut classical_args);
286 (qubits, classical_args)
287 }
288
289 /// Pushes all qubit values into `qubits`, and formats all classical values into `classical_args`.
290 fn push_val(&mut self, arg: &Value, qubits: &mut Vec<WireId>, classical_args: &mut String) {
291 match arg {
292 Value::Array(vals) => {
293 self.push_list::<'[', ']'>(vals, qubits, classical_args);
294 }
295 Value::Tuple(vals) => {
296 self.push_list::<'(', ')'>(vals, qubits, classical_args);
297 }
298 Value::Qubit(q) => {
299 qubits.push(self.map(q.deref().0));
300 }
301 v => {
302 let _ = write!(classical_args, "{v}");
303 }
304 }
305 qubits.sort_unstable_by_key(|q| q.0);
306 qubits.dedup_by_key(|q| q.0);
307 }
308
309 /// Pushes all qubit values into `qubits`, and formats all
310 /// classical values into `classical_args` as a list.
311 fn push_list<const OPEN: char, const CLOSE: char>(
312 &mut self,
313 vals: &[Value],
314 qubits: &mut Vec<WireId>,
315 classical_args: &mut String,
316 ) {
317 classical_args.push(OPEN);
318 let start = classical_args.len();
319 self.push_vals(vals, qubits, classical_args);
320 if classical_args.len() > start {
321 classical_args.push(CLOSE);
322 } else {
323 classical_args.pop();
324 }
325 }
326
327 /// Pushes all qubit values into `qubits`, and formats all
328 /// classical values into `classical_args` as comma-separated values.
329 fn push_vals(&mut self, vals: &[Value], qubits: &mut Vec<WireId>, classical_args: &mut String) {
330 let mut any = false;
331 for v in vals {
332 let start = classical_args.len();
333 self.push_val(v, qubits, classical_args);
334 if classical_args.len() > start {
335 any = true;
336 classical_args.push_str(", ");
337 }
338 }
339 if any {
340 // remove trailing comma
341 classical_args.pop();
342 classical_args.pop();
343 }
344 }
345}
346
347/// Provides support for qubit id allocation, measurement and
348/// reset operations for Base Profile targets.
349///
350/// Since qubit reuse is disallowed, a mapping is maintained
351/// from allocated qubit ids to hardware qubit ids. Each time
352/// a qubit is reset, it is remapped to a fresh hardware qubit.
353///
354/// Note that even though qubit reset & reuse is disallowed,
355/// qubit ids are still reused for new allocations.
356/// Measurements are tracked and deferred.
357#[derive(Default)]
358struct Remapper {
359 next_meas_id: usize,
360 next_qubit_id: usize,
361 next_qubit_wire_id: WireId,
362 qubit_map: IndexMap<usize, WireId>,
363 qubit_measurement_counts: IndexMap<WireId, usize>,
364}
365
366impl Remapper {
367 fn map(&mut self, qubit: usize) -> WireId {
368 if let Some(mapped) = self.qubit_map.get(qubit) {
369 *mapped
370 } else {
371 let mapped = self.next_qubit_wire_id;
372 self.next_qubit_wire_id.0 += 1;
373 self.qubit_map.insert(qubit, mapped);
374 mapped
375 }
376 }
377
378 fn m(&mut self, q: usize) -> usize {
379 let mapped_q = self.map(q);
380 let id = self.get_meas_id();
381 match self.qubit_measurement_counts.get_mut(mapped_q) {
382 Some(count) => *count += 1,
383 None => {
384 self.qubit_measurement_counts.insert(mapped_q, 1);
385 }
386 }
387 id
388 }
389
390 fn qubit_allocate(&mut self) -> usize {
391 let id = self.next_qubit_id;
392 self.next_qubit_id += 1;
393 let _ = self.map(id);
394 id
395 }
396
397 fn qubit_release(&mut self, _q: usize) {
398 self.next_qubit_id -= 1;
399 }
400
401 fn swap(&mut self, q0: usize, q1: usize) {
402 let q0_mapped = self.map(q0);
403 let q1_mapped = self.map(q1);
404 self.qubit_map.insert(q0, q1_mapped);
405 self.qubit_map.insert(q1, q0_mapped);
406 }
407
408 #[must_use]
409 fn num_qubits(&self) -> usize {
410 self.next_qubit_wire_id.0
411 }
412
413 #[must_use]
414 fn get_meas_id(&mut self) -> usize {
415 let id = self.next_meas_id;
416 self.next_meas_id += 1;
417 id
418 }
419}
420
421#[derive(Copy, Clone, Default)]
422struct WireId(pub usize);
423
424impl From<usize> for WireId {
425 fn from(id: usize) -> Self {
426 WireId(id)
427 }
428}
429
430impl From<WireId> for usize {
431 fn from(id: WireId) -> Self {
432 id.0
433 }
434}
435
436fn gate<const N: usize>(name: &str, targets: [WireId; N]) -> Operation {
437 Operation::Unitary(Unitary {
438 gate: name.into(),
439 args: vec![],
440 is_adjoint: false,
441 controls: vec![],
442 targets: targets.iter().map(|q| Register::quantum(q.0)).collect(),
443 children: vec![],
444 })
445}
446
447fn adjoint_gate<const N: usize>(name: &str, targets: [WireId; N]) -> Operation {
448 Operation::Unitary(Unitary {
449 gate: name.into(),
450 args: vec![],
451 is_adjoint: true,
452 controls: vec![],
453 targets: targets.iter().map(|q| Register::quantum(q.0)).collect(),
454 children: vec![],
455 })
456}
457
458fn controlled_gate<const M: usize, const N: usize>(
459 name: &str,
460 controls: [WireId; M],
461 targets: [WireId; N],
462) -> Operation {
463 Operation::Unitary(Unitary {
464 gate: name.into(),
465 args: vec![],
466 is_adjoint: false,
467 controls: controls.iter().map(|q| Register::quantum(q.0)).collect(),
468 targets: targets.iter().map(|q| Register::quantum(q.0)).collect(),
469 children: vec![],
470 })
471}
472
473fn measurement_gate(qubit: usize, result: usize) -> Operation {
474 Operation::Measurement(Measurement {
475 gate: "Measure".into(),
476 args: vec![],
477 qubits: vec![Register::quantum(qubit)],
478 results: vec![Register::classical(qubit, result)],
479 children: vec![],
480 })
481}
482
483fn ket_gate<const N: usize>(name: &str, targets: [WireId; N]) -> Operation {
484 Operation::Ket(Ket {
485 gate: name.into(),
486 args: vec![],
487 targets: targets.iter().map(|q| Register::quantum(q.0)).collect(),
488 children: vec![],
489 })
490}
491
492fn rotation_gate<const N: usize>(name: &str, theta: f64, targets: [WireId; N]) -> Operation {
493 Operation::Unitary(Unitary {
494 gate: name.into(),
495 args: vec![format!("{theta:.4}")],
496 is_adjoint: false,
497 controls: vec![],
498 targets: targets.iter().map(|q| Register::quantum(q.0)).collect(),
499 children: vec![],
500 })
501}
502
503fn custom_gate(name: &str, targets: &[WireId], args: Vec<String>) -> Operation {
504 Operation::Unitary(Unitary {
505 gate: name.into(),
506 args,
507 is_adjoint: false,
508 controls: vec![],
509 targets: targets.iter().map(|q| Register::quantum(q.0)).collect(),
510 children: vec![],
511 })
512}
513