microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
alex/second-api-refactor

Branches

Tags

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

Clone

HTTPS

Download ZIP

compiler/qsc_circuit/src/builder.rs

456lines · modecode

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