microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/fix-2145

Branches

Tags

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

Clone

HTTPS

Download ZIP

compiler/qsc_circuit/src/builder.rs

517lines · 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 sx(&mut self, q: usize) {
133 let q = self.map(q);
134 self.push_gate(gate("SX", [q]));
135 }
136
137 fn swap(&mut self, q0: usize, q1: usize) {
138 let q0 = self.map(q0);
139 let q1 = self.map(q1);
140 self.push_gate(gate("SWAP", [q0, q1]));
141 }
142
143 fn tadj(&mut self, q: usize) {
144 let q = self.map(q);
145 self.push_gate(adjoint_gate("T", [q]));
146 }
147
148 fn t(&mut self, q: usize) {
149 let q = self.map(q);
150 self.push_gate(gate("T", [q]));
151 }
152
153 fn x(&mut self, q: usize) {
154 let q = self.map(q);
155 self.push_gate(gate("X", [q]));
156 }
157
158 fn y(&mut self, q: usize) {
159 let q = self.map(q);
160 self.push_gate(gate("Y", [q]));
161 }
162
163 fn z(&mut self, q: usize) {
164 let q = self.map(q);
165 self.push_gate(gate("Z", [q]));
166 }
167
168 fn qubit_allocate(&mut self) -> usize {
169 self.remapper.qubit_allocate()
170 }
171
172 fn qubit_release(&mut self, q: usize) -> bool {
173 self.remapper.qubit_release(q);
174 true
175 }
176
177 fn qubit_swap_id(&mut self, q0: usize, q1: usize) {
178 self.remapper.swap(q0, q1);
179 }
180
181 fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
182 (Vec::new(), 0)
183 }
184
185 fn qubit_is_zero(&mut self, _q: usize) -> bool {
186 // We don't simulate quantum execution here. So we don't know if the qubit
187 // is zero or not. Returning true avoids potential panics.
188 true
189 }
190
191 fn custom_intrinsic(&mut self, name: &str, arg: Value) -> Option<Result<Value, String>> {
192 // The qubit arguments are treated as the targets for custom gates.
193 // Any remaining arguments will be kept in the display_args field
194 // to be shown as part of the gate label when the circuit is rendered.
195 let (qubit_args, classical_args) = self.split_qubit_args(arg);
196
197 self.push_gate(custom_gate(
198 name,
199 &qubit_args,
200 if classical_args.is_empty() {
201 vec![]
202 } else {
203 vec![classical_args]
204 },
205 ));
206
207 match name {
208 // Special case this known intrinsic to match the simulator
209 // behavior, so that our samples will work
210 "BeginEstimateCaching" => Some(Ok(Value::Bool(true))),
211 _ => Some(Ok(Value::unit())),
212 }
213 }
214}
215
216impl Builder {
217 #[must_use]
218 pub fn new(config: Config) -> Self {
219 Builder {
220 max_ops_exceeded: false,
221 operations: vec![],
222 config,
223 remapper: Remapper::default(),
224 }
225 }
226
227 #[must_use]
228 pub fn snapshot(&self) -> Circuit {
229 let operations = self.operations.clone();
230 self.finish_circuit(operations)
231 }
232
233 #[must_use]
234 pub fn finish(mut self) -> Circuit {
235 let operations = take(&mut self.operations);
236 self.finish_circuit(operations)
237 }
238
239 fn map(&mut self, qubit: usize) -> WireId {
240 self.remapper.map(qubit)
241 }
242
243 fn push_gate(&mut self, gate: Operation) {
244 if self.max_ops_exceeded || self.operations.len() >= self.config.max_operations {
245 // Stop adding gates and leave the circuit as is
246 self.max_ops_exceeded = true;
247 return;
248 }
249 self.operations.push(gate);
250 }
251
252 fn num_measurements_for_qubit(&self, qubit: WireId) -> usize {
253 self.remapper
254 .qubit_measurement_counts
255 .get(qubit)
256 .copied()
257 .unwrap_or_default()
258 }
259
260 fn finish_circuit(&self, operations: Vec<Operation>) -> Circuit {
261 let mut qubits = vec![];
262
263 // add qubit declarations
264 for i in 0..self.remapper.num_qubits() {
265 let num_measurements = self.num_measurements_for_qubit(WireId(i));
266 qubits.push(crate::circuit::Qubit {
267 id: i,
268 num_results: num_measurements,
269 });
270 }
271
272 Circuit {
273 component_grid: operation_list_to_grid(operations, qubits.len()),
274 qubits,
275 }
276 }
277
278 /// Splits the qubit arguments from classical arguments so that the qubits
279 /// can be treated as the targets for custom gates.
280 /// The classical arguments get formatted into a comma-separated list.
281 fn split_qubit_args(&mut self, arg: Value) -> (Vec<WireId>, String) {
282 let arg = if let Value::Tuple(vals) = arg {
283 vals
284 } else {
285 // Single arguments are not passed as tuples, wrap in an array
286 Rc::new([arg])
287 };
288 let mut qubits = vec![];
289 let mut classical_args = String::new();
290 self.push_vals(&arg, &mut qubits, &mut classical_args);
291 (qubits, classical_args)
292 }
293
294 /// Pushes all qubit values into `qubits`, and formats all classical values into `classical_args`.
295 fn push_val(&mut self, arg: &Value, qubits: &mut Vec<WireId>, classical_args: &mut String) {
296 match arg {
297 Value::Array(vals) => {
298 self.push_list::<'[', ']'>(vals, qubits, classical_args);
299 }
300 Value::Tuple(vals) => {
301 self.push_list::<'(', ')'>(vals, qubits, classical_args);
302 }
303 Value::Qubit(q) => {
304 qubits.push(self.map(q.deref().0));
305 }
306 v => {
307 let _ = write!(classical_args, "{v}");
308 }
309 }
310 qubits.sort_unstable_by_key(|q| q.0);
311 qubits.dedup_by_key(|q| q.0);
312 }
313
314 /// Pushes all qubit values into `qubits`, and formats all
315 /// classical values into `classical_args` as a list.
316 fn push_list<const OPEN: char, const CLOSE: char>(
317 &mut self,
318 vals: &[Value],
319 qubits: &mut Vec<WireId>,
320 classical_args: &mut String,
321 ) {
322 classical_args.push(OPEN);
323 let start = classical_args.len();
324 self.push_vals(vals, qubits, classical_args);
325 if classical_args.len() > start {
326 classical_args.push(CLOSE);
327 } else {
328 classical_args.pop();
329 }
330 }
331
332 /// Pushes all qubit values into `qubits`, and formats all
333 /// classical values into `classical_args` as comma-separated values.
334 fn push_vals(&mut self, vals: &[Value], qubits: &mut Vec<WireId>, classical_args: &mut String) {
335 let mut any = false;
336 for v in vals {
337 let start = classical_args.len();
338 self.push_val(v, qubits, classical_args);
339 if classical_args.len() > start {
340 any = true;
341 classical_args.push_str(", ");
342 }
343 }
344 if any {
345 // remove trailing comma
346 classical_args.pop();
347 classical_args.pop();
348 }
349 }
350}
351
352/// Provides support for qubit id allocation, measurement and
353/// reset operations for Base Profile targets.
354///
355/// Since qubit reuse is disallowed, a mapping is maintained
356/// from allocated qubit ids to hardware qubit ids. Each time
357/// a qubit is reset, it is remapped to a fresh hardware qubit.
358///
359/// Note that even though qubit reset & reuse is disallowed,
360/// qubit ids are still reused for new allocations.
361/// Measurements are tracked and deferred.
362#[derive(Default)]
363struct Remapper {
364 next_meas_id: usize,
365 next_qubit_id: usize,
366 next_qubit_wire_id: WireId,
367 qubit_map: IndexMap<usize, WireId>,
368 qubit_measurement_counts: IndexMap<WireId, usize>,
369}
370
371impl Remapper {
372 fn map(&mut self, qubit: usize) -> WireId {
373 if let Some(mapped) = self.qubit_map.get(qubit) {
374 *mapped
375 } else {
376 let mapped = self.next_qubit_wire_id;
377 self.next_qubit_wire_id.0 += 1;
378 self.qubit_map.insert(qubit, mapped);
379 mapped
380 }
381 }
382
383 fn m(&mut self, q: usize) -> usize {
384 let mapped_q = self.map(q);
385 let id = self.get_meas_id();
386 match self.qubit_measurement_counts.get_mut(mapped_q) {
387 Some(count) => *count += 1,
388 None => {
389 self.qubit_measurement_counts.insert(mapped_q, 1);
390 }
391 }
392 id
393 }
394
395 fn qubit_allocate(&mut self) -> usize {
396 let id = self.next_qubit_id;
397 self.next_qubit_id += 1;
398 let _ = self.map(id);
399 id
400 }
401
402 fn qubit_release(&mut self, _q: usize) {
403 self.next_qubit_id -= 1;
404 }
405
406 fn swap(&mut self, q0: usize, q1: usize) {
407 let q0_mapped = self.map(q0);
408 let q1_mapped = self.map(q1);
409 self.qubit_map.insert(q0, q1_mapped);
410 self.qubit_map.insert(q1, q0_mapped);
411 }
412
413 #[must_use]
414 fn num_qubits(&self) -> usize {
415 self.next_qubit_wire_id.0
416 }
417
418 #[must_use]
419 fn get_meas_id(&mut self) -> usize {
420 let id = self.next_meas_id;
421 self.next_meas_id += 1;
422 id
423 }
424}
425
426#[derive(Copy, Clone, Default)]
427struct WireId(pub usize);
428
429impl From<usize> for WireId {
430 fn from(id: usize) -> Self {
431 WireId(id)
432 }
433}
434
435impl From<WireId> for usize {
436 fn from(id: WireId) -> Self {
437 id.0
438 }
439}
440
441fn gate<const N: usize>(name: &str, targets: [WireId; N]) -> Operation {
442 Operation::Unitary(Unitary {
443 gate: name.into(),
444 args: vec![],
445 is_adjoint: false,
446 controls: vec![],
447 targets: targets.iter().map(|q| Register::quantum(q.0)).collect(),
448 children: vec![],
449 })
450}
451
452fn adjoint_gate<const N: usize>(name: &str, targets: [WireId; N]) -> Operation {
453 Operation::Unitary(Unitary {
454 gate: name.into(),
455 args: vec![],
456 is_adjoint: true,
457 controls: vec![],
458 targets: targets.iter().map(|q| Register::quantum(q.0)).collect(),
459 children: vec![],
460 })
461}
462
463fn controlled_gate<const M: usize, const N: usize>(
464 name: &str,
465 controls: [WireId; M],
466 targets: [WireId; N],
467) -> Operation {
468 Operation::Unitary(Unitary {
469 gate: name.into(),
470 args: vec![],
471 is_adjoint: false,
472 controls: controls.iter().map(|q| Register::quantum(q.0)).collect(),
473 targets: targets.iter().map(|q| Register::quantum(q.0)).collect(),
474 children: vec![],
475 })
476}
477
478fn measurement_gate(qubit: usize, result: usize) -> Operation {
479 Operation::Measurement(Measurement {
480 gate: "Measure".into(),
481 args: vec![],
482 qubits: vec![Register::quantum(qubit)],
483 results: vec![Register::classical(qubit, result)],
484 children: vec![],
485 })
486}
487
488fn ket_gate<const N: usize>(name: &str, targets: [WireId; N]) -> Operation {
489 Operation::Ket(Ket {
490 gate: name.into(),
491 args: vec![],
492 targets: targets.iter().map(|q| Register::quantum(q.0)).collect(),
493 children: vec![],
494 })
495}
496
497fn rotation_gate<const N: usize>(name: &str, theta: f64, targets: [WireId; N]) -> Operation {
498 Operation::Unitary(Unitary {
499 gate: name.into(),
500 args: vec![format!("{theta:.4}")],
501 is_adjoint: false,
502 controls: vec![],
503 targets: targets.iter().map(|q| Register::quantum(q.0)).collect(),
504 children: vec![],
505 })
506}
507
508fn custom_gate(name: &str, targets: &[WireId], args: Vec<String>) -> Operation {
509 Operation::Unitary(Unitary {
510 gate: name.into(),
511 args,
512 is_adjoint: false,
513 controls: vec![],
514 targets: targets.iter().map(|q| Register::quantum(q.0)).collect(),
515 children: vec![],
516 })
517}
518