microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
alex/bounds

Branches

Tags

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

Clone

HTTPS

Download ZIP

compiler/qsc_circuit/src/circuit.rs

346lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#[cfg(test)]
5mod tests;
6
7use rustc_hash::FxHashMap;
8use serde::Serialize;
9use std::{fmt::Display, fmt::Write, ops::Not, vec};
10
11/// Representation of a quantum circuit.
12/// Implementation of <https://github.com/microsoft/quantum-viz.js/wiki/API-schema-reference>
13#[derive(Clone, Serialize, Default, Debug, PartialEq)]
14pub struct Circuit {
15 pub operations: Vec<Operation>,
16 pub qubits: Vec<Qubit>,
17}
18
19#[derive(Clone, Serialize, Debug, PartialEq)]
20pub struct Operation {
21 #[allow(clippy::struct_field_names)]
22 pub gate: String,
23 #[serde(rename = "displayArgs")]
24 #[serde(skip_serializing_if = "Option::is_none")]
25 pub display_args: Option<String>,
26 #[serde(rename = "isControlled")]
27 #[serde(skip_serializing_if = "Not::not")]
28 pub is_controlled: bool,
29 #[serde(rename = "isAdjoint")]
30 #[serde(skip_serializing_if = "Not::not")]
31 pub is_adjoint: bool,
32 #[serde(rename = "isMeasurement")]
33 #[serde(skip_serializing_if = "Not::not")]
34 pub is_measurement: bool,
35 #[serde(skip_serializing_if = "Vec::is_empty")]
36 pub controls: Vec<Register>,
37 pub targets: Vec<Register>,
38 #[serde(skip_serializing_if = "Vec::is_empty")]
39 pub children: Vec<Operation>,
40}
41
42const QUANTUM_REGISTER: usize = 0;
43const CLASSICAL_REGISTER: usize = 1;
44
45#[derive(Serialize, Debug, Eq, Hash, PartialEq, Clone)]
46pub struct Register {
47 #[serde(rename = "qId")]
48 pub q_id: usize,
49 pub r#type: usize,
50 #[serde(rename = "cId")]
51 #[serde(skip_serializing_if = "Option::is_none")]
52 pub c_id: Option<usize>,
53}
54
55impl Register {
56 pub fn quantum(q_id: usize) -> Self {
57 Self {
58 q_id,
59 r#type: QUANTUM_REGISTER,
60 c_id: None,
61 }
62 }
63
64 pub fn classical(q_id: usize, c_id: usize) -> Self {
65 Self {
66 q_id,
67 r#type: CLASSICAL_REGISTER,
68 c_id: Some(c_id),
69 }
70 }
71}
72
73#[derive(PartialEq, Clone, Serialize, Debug)]
74pub struct Qubit {
75 pub id: usize,
76 #[serde(rename = "numChildren")]
77 pub num_children: usize,
78}
79
80#[derive(Clone, Debug, Copy, Default)]
81pub struct Config {
82 /// Perform Base Profile decompositions
83 pub base_profile: bool,
84}
85
86type ObjectsByColumn = FxHashMap<usize, String>;
87
88struct Row {
89 wire: Wire,
90 objects: ObjectsByColumn,
91 next_column: usize,
92}
93
94enum Wire {
95 Qubit { q_id: usize },
96 Classical { start_column: Option<usize> },
97}
98
99impl Row {
100 fn add_object(&mut self, column: usize, object: &str) {
101 match &mut self.wire {
102 Wire::Qubit { .. } => {
103 self.add(column, fmt_on_qubit_wire(object));
104 }
105 Wire::Classical { .. } => {
106 self.add(column, fmt_on_classical_wire(object));
107 }
108 };
109 }
110
111 fn add_gate(&mut self, column: usize, gate: &str, args: Option<&str>, is_adjoint: bool) {
112 let mut gate_label = String::new();
113 gate_label.push_str(gate);
114 if is_adjoint {
115 gate_label.push('\'');
116 }
117 if let Some(args) = args {
118 let _ = write!(&mut gate_label, "({args})");
119 }
120 self.add_object(column, &gate_label);
121 }
122
123 fn add_vertical(&mut self, column: usize) {
124 if !self.objects.contains_key(&column) {
125 match self.wire {
126 Wire::Qubit { .. } => self.add(column, QUBIT_WIRE_CROSS),
127 Wire::Classical { start_column } => {
128 if start_column.is_some() {
129 self.add(column, CLASSICAL_WIRE_CROSS);
130 } else {
131 self.add(column, VERTICAL);
132 }
133 }
134 }
135 }
136 }
137
138 fn add_dashed_vertical(&mut self, column: usize) {
139 if !self.objects.contains_key(&column) {
140 match self.wire {
141 Wire::Qubit { .. } => self.add(column, QUBIT_WIRE_DASHED_CROSS),
142 Wire::Classical { start_column } => {
143 if start_column.is_some() {
144 self.add(column, CLASSICAL_WIRE_DASHED_CROSS);
145 } else {
146 self.add(column, VERTICAL_DASHED);
147 }
148 }
149 }
150 }
151 }
152
153 fn start_classical(&mut self, column: usize) {
154 self.add(column, CLASSICAL_WIRE_START);
155 if let Wire::Classical { start_column } = &mut self.wire {
156 start_column.replace(column);
157 }
158 }
159
160 fn add(&mut self, column: usize, v: impl Into<String>) {
161 self.objects.insert(column, v.into());
162 self.next_column = column + 1;
163 }
164
165 fn fmt(&self, f: &mut std::fmt::Formatter<'_>, end_column: usize) -> std::fmt::Result {
166 // Temporary string so we can trim whitespace at the end
167 let mut s = String::new();
168 match &self.wire {
169 Wire::Qubit { q_id: label } => {
170 s.write_str(&fmt_qubit_label(*label))?;
171 for column in 1..end_column {
172 let val = self.objects.get(&column);
173 if let Some(v) = val {
174 s.write_str(v)?;
175 } else {
176 s.write_str(QUBIT_WIRE)?;
177 }
178 }
179 }
180 Wire::Classical { start_column } => {
181 for column in 0..end_column {
182 let val = self.objects.get(&column);
183 if let Some(v) = val {
184 s.write_str(v)?;
185 } else if start_column.map_or(false, |s| column > s) {
186 s.write_str(CLASSICAL_WIRE)?;
187 } else {
188 s.write_str(BLANK)?;
189 }
190 }
191 }
192 }
193 writeln!(f, "{}", s.trim_end())?;
194 Ok(())
195 }
196}
197
198const COLUMN_WIDTH: usize = 7;
199const QUBIT_WIRE: &str = "───────";
200const CLASSICAL_WIRE: &str = "═══════";
201const QUBIT_WIRE_CROSS: &str = "───┼───";
202const CLASSICAL_WIRE_CROSS: &str = "═══╪═══";
203const CLASSICAL_WIRE_START: &str = " ╘═══";
204const QUBIT_WIRE_DASHED_CROSS: &str = "───┆───";
205const CLASSICAL_WIRE_DASHED_CROSS: &str = "═══┆═══";
206const VERTICAL_DASHED: &str = " ┆ ";
207const VERTICAL: &str = " │ ";
208const BLANK: &str = " ";
209
210/// "q_0 "
211#[allow(clippy::doc_markdown)]
212fn fmt_qubit_label(id: usize) -> String {
213 let rest = COLUMN_WIDTH - 2;
214 format!("q_{id: <rest$}")
215}
216
217/// "── A ──"
218fn fmt_on_qubit_wire(obj: &str) -> String {
219 format!("{:─^COLUMN_WIDTH$}", format!(" {obj} "))
220}
221
222/// "══ A ══"
223fn fmt_on_classical_wire(obj: &str) -> String {
224 format!("{:═^COLUMN_WIDTH$}", format!(" {obj} "))
225}
226
227impl Display for Circuit {
228 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229 let mut rows = vec![];
230
231 // Maintain a mapping from from Registers in the Circuit schema
232 // to row in the diagram
233 let mut register_to_row = FxHashMap::default();
234
235 // Initialize all qubit and classical wires
236 for q in &self.qubits {
237 rows.push(Row {
238 wire: Wire::Qubit { q_id: q.id },
239 objects: FxHashMap::default(),
240 next_column: 1,
241 });
242
243 register_to_row.insert((q.id, None), rows.len() - 1);
244
245 for i in 0..q.num_children {
246 rows.push(Row {
247 wire: Wire::Classical { start_column: None },
248 objects: FxHashMap::default(),
249 next_column: 1,
250 });
251
252 register_to_row.insert((q.id, Some(i)), rows.len() - 1);
253 }
254 }
255
256 for o in &self.operations {
257 // Row indexes for the targets for this operation
258 let targets = o
259 .targets
260 .iter()
261 .filter_map(|reg| {
262 let reg = (reg.q_id, reg.c_id);
263 register_to_row.get(&reg).cloned()
264 })
265 .collect::<Vec<_>>();
266
267 // Row indexes for the controls for this operation
268 let controls = o
269 .controls
270 .iter()
271 .filter_map(|reg| {
272 let reg = (reg.q_id, reg.c_id);
273 register_to_row.get(&reg).cloned()
274 })
275 .collect::<Vec<_>>();
276
277 let mut all_rows = targets.clone();
278 all_rows.extend(controls.iter());
279 all_rows.sort_unstable();
280 // We'll need to know the entire range of rows for this operation so we can
281 // figure out the starting column and also so we can draw any
282 // vertical lines that cross wires.
283 let (begin, end) = all_rows.split_first().map_or((0, 0), |(first, tail)| {
284 (*first, tail.last().unwrap_or(first) + 1)
285 });
286
287 // The starting column - the first available column in all
288 // the rows that this operation spans.
289 let mut column = 1;
290 for row in &rows[begin..end] {
291 if row.next_column > column {
292 column = row.next_column;
293 }
294 }
295
296 // Add the operation to the diagram
297 for i in targets {
298 let row = &mut rows[i];
299 if matches!(row.wire, Wire::Classical { .. }) && o.is_measurement {
300 row.start_classical(column);
301 } else {
302 row.add_gate(column, &o.gate, o.display_args.as_deref(), o.is_adjoint);
303 };
304 }
305
306 if o.is_controlled || o.is_measurement {
307 for i in controls {
308 let row = &mut rows[i];
309 if matches!(row.wire, Wire::Qubit { .. }) && o.is_measurement {
310 row.add_object(column, "M");
311 } else {
312 row.add_object(column, "●");
313 };
314 }
315
316 // If we have a control wire, draw vertical lines spanning all
317 // control and target wires and crossing any in between
318 // (vertical lines may overlap if there are multiple controls/targets,
319 // this is ok in practice)
320 for row in &mut rows[begin..end] {
321 row.add_vertical(column);
322 }
323 } else {
324 // No control wire. Draw dashed vertical lines to connect
325 // target wires if there are multiple targets
326 for row in &mut rows[begin..end] {
327 row.add_dashed_vertical(column);
328 }
329 }
330 }
331
332 // Find the end column for the whole circuit so that
333 // all qubit wires will extend until the end
334 let end_column = rows
335 .iter()
336 .max_by_key(|r| r.next_column)
337 .map_or(1, |r| r.next_column);
338
339 // Draw the diagram
340 for row in rows {
341 row.fmt(f, end_column)?;
342 }
343
344 Ok(())
345 }
346}
347