microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.29.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/compiler/qsc_circuit/src/circuit.rs

1240lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#[cfg(test)]
5mod tests;
6
7use indenter::indented;
8use rustc_hash::{FxHashMap, FxHashSet};
9use serde::{Deserialize, Serialize};
10use std::{
11 cmp::max,
12 fmt::{Display, Write},
13 hash::Hash,
14 mem::take,
15 ops::Not,
16 vec,
17};
18
19/// Current format version.
20pub const CURRENT_VERSION: usize = 1;
21
22/// Representation of a quantum circuit group.
23#[derive(Clone, Serialize, Deserialize, Default, Debug)]
24pub struct CircuitGroup {
25 pub circuits: Vec<Circuit>,
26 pub version: usize,
27}
28
29impl Display for CircuitGroup {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 for circuit in &self.circuits {
32 writeln!(f, "{circuit}")?;
33 }
34 Ok(())
35 }
36}
37
38/// Representation of a quantum circuit.
39#[derive(Clone, Serialize, Deserialize, Default, Debug)]
40pub struct Circuit {
41 pub qubits: Vec<Qubit>,
42 #[serde(rename = "componentGrid")]
43 pub component_grid: ComponentGrid,
44}
45
46impl Circuit {
47 #[must_use]
48 pub fn display_no_locations(&self) -> impl Display {
49 CircuitDisplay {
50 circuit: self,
51 render_locations: false,
52 render_groups: false,
53 }
54 }
55
56 #[must_use]
57 pub fn display_with_groups(&self) -> impl Display {
58 // Groups rendered only in tests since the current line rendering
59 // doesn't look good enough to be user-facing.
60 CircuitDisplay {
61 circuit: self,
62 render_locations: true,
63 render_groups: true,
64 }
65 }
66}
67
68impl Display for Circuit {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 write!(
71 f,
72 "{}",
73 CircuitDisplay {
74 circuit: self,
75 render_locations: true,
76 render_groups: false,
77 }
78 )
79 }
80}
81
82/// Type alias for a grid of components.
83pub type ComponentGrid = Vec<ComponentColumn>;
84
85/// Representation of a column in the component grid.
86#[derive(Clone, Serialize, Deserialize, Default, Debug)]
87pub struct ComponentColumn {
88 pub components: Vec<Component>,
89}
90
91/// Union type for components.
92pub type Component = Operation;
93
94/// Union type for operations.
95#[derive(Clone, Serialize, Deserialize, Debug)]
96#[serde(tag = "kind")]
97pub enum Operation {
98 #[serde(rename = "measurement")]
99 Measurement(Measurement),
100 #[serde(rename = "unitary")]
101 Unitary(Unitary),
102 #[serde(rename = "ket")]
103 Ket(Ket),
104}
105
106impl Operation {
107 /// Returns the gate name of the operation.
108 #[must_use]
109 pub fn gate(&self) -> String {
110 match self {
111 Operation::Measurement(m) => m.gate.clone(),
112 Operation::Unitary(u) => u.gate.clone(),
113 #[allow(clippy::unicode_not_nfc)]
114 Operation::Ket(k) => format!("|{}〉", k.gate),
115 }
116 }
117
118 pub fn gate_mut(&mut self) -> &mut String {
119 match self {
120 Self::Measurement(measurement) => &mut measurement.gate,
121 Self::Unitary(unitary) => &mut unitary.gate,
122 Self::Ket(ket) => &mut ket.gate,
123 }
124 }
125
126 /// Returns the arguments for the operation.
127 #[must_use]
128 pub fn args(&self) -> Vec<String> {
129 match self {
130 Operation::Measurement(m) => m.args.clone(),
131 Operation::Unitary(u) => u.args.clone(),
132 Operation::Ket(k) => k.args.clone(),
133 }
134 }
135
136 pub fn args_mut(&mut self) -> &mut Vec<String> {
137 match self {
138 Self::Measurement(measurement) => &mut measurement.args,
139 Self::Unitary(unitary) => &mut unitary.args,
140 Self::Ket(ket) => &mut ket.args,
141 }
142 }
143
144 #[must_use]
145 pub fn source_location(&self) -> Option<&SourceLocation> {
146 match self {
147 Self::Measurement(measurement) => measurement.metadata.as_ref(),
148 Self::Unitary(unitary) => unitary.metadata.as_ref(),
149 Self::Ket(ket) => ket.metadata.as_ref(),
150 }
151 .and_then(|m| m.source.as_ref())
152 }
153
154 #[must_use]
155 pub fn source_location_mut(&mut self) -> &mut Option<SourceLocation> {
156 let md = match self {
157 Self::Measurement(measurement) => &mut measurement.metadata,
158 Self::Unitary(unitary) => &mut unitary.metadata,
159 Self::Ket(ket) => &mut ket.metadata,
160 };
161
162 if md.is_none() {
163 md.replace(Metadata::default());
164 }
165
166 if let Some(md) = md {
167 &mut md.source
168 } else {
169 unreachable!()
170 }
171 }
172
173 #[must_use]
174 pub fn scope_location_mut(&mut self) -> &mut Option<SourceLocation> {
175 let md = match self {
176 Self::Measurement(measurement) => &mut measurement.metadata,
177 Self::Unitary(unitary) => &mut unitary.metadata,
178 Self::Ket(ket) => &mut ket.metadata,
179 };
180
181 if md.is_none() {
182 md.replace(Metadata::default());
183 }
184
185 if let Some(md) = md {
186 &mut md.scope_location
187 } else {
188 unreachable!()
189 }
190 }
191
192 /// Returns the children for the operation.
193 #[must_use]
194 pub fn children(&self) -> &ComponentGrid {
195 match self {
196 Operation::Measurement(m) => &m.children,
197 Operation::Unitary(u) => &u.children,
198 Operation::Ket(k) => &k.children,
199 }
200 }
201
202 /// Returns the children for the operation.
203 #[must_use]
204 pub fn children_mut(&mut self) -> &mut ComponentGrid {
205 match self {
206 Operation::Measurement(m) => &mut m.children,
207 Operation::Unitary(u) => &mut u.children,
208 Operation::Ket(k) => &mut k.children,
209 }
210 }
211
212 #[must_use]
213 pub fn targets_mut(&mut self) -> &mut Vec<Register> {
214 match self {
215 Operation::Measurement(m) => &mut m.qubits,
216 Operation::Unitary(u) => &mut u.targets,
217 Operation::Ket(k) => &mut k.targets,
218 }
219 }
220 /// Returns if the operation is a controlled operation.
221 #[must_use]
222 pub fn is_controlled(&self) -> bool {
223 match self {
224 Operation::Measurement(_) | Operation::Ket(_) => false,
225 Operation::Unitary(u) => !u.controls.is_empty(),
226 }
227 }
228
229 /// Returns if the operation is a measurement operation.
230 #[must_use]
231 pub fn is_measurement(&self) -> bool {
232 match self {
233 Operation::Measurement(_) => true,
234 Operation::Unitary(_) | Operation::Ket(_) => false,
235 }
236 }
237
238 /// Returns if the operation is an adjoint operation.
239 #[must_use]
240 pub fn is_adjoint(&self) -> bool {
241 match self {
242 Operation::Measurement(_) | Operation::Ket(_) => false,
243 Operation::Unitary(u) => u.is_adjoint,
244 }
245 }
246}
247
248/// Representation of a measurement operation.
249#[derive(Clone, Serialize, Deserialize, Default, Debug)]
250pub struct Measurement {
251 pub gate: String,
252 #[serde(skip_serializing_if = "Vec::is_empty")]
253 #[serde(default)]
254 pub args: Vec<String>,
255 #[serde(skip_serializing_if = "Vec::is_empty")]
256 #[serde(default)]
257 pub children: ComponentGrid,
258 pub qubits: Vec<Register>,
259 pub results: Vec<Register>,
260 #[serde(skip_serializing_if = "Option::is_none")]
261 pub metadata: Option<Metadata>,
262}
263
264/// Representation of a unitary operation.
265#[derive(Clone, Serialize, Deserialize, Default, Debug)]
266pub struct Unitary {
267 pub gate: String,
268 #[serde(skip_serializing_if = "Vec::is_empty")]
269 #[serde(default)]
270 pub args: Vec<String>,
271 #[serde(skip_serializing_if = "Vec::is_empty")]
272 #[serde(default)]
273 pub children: ComponentGrid,
274 pub targets: Vec<Register>,
275 #[serde(skip_serializing_if = "Vec::is_empty")]
276 #[serde(default)]
277 pub controls: Vec<Register>,
278 #[serde(rename = "isAdjoint")]
279 #[serde(skip_serializing_if = "Not::not")]
280 #[serde(default)]
281 pub is_adjoint: bool,
282 #[serde(rename = "isConditional")]
283 #[serde(skip_serializing_if = "Not::not")]
284 #[serde(default)]
285 pub is_conditional: bool,
286 #[serde(skip_serializing_if = "Option::is_none")]
287 pub metadata: Option<Metadata>,
288}
289
290/// Representation of a gate that will set the target to a specific state.
291#[derive(Clone, Serialize, Deserialize, Default, Debug)]
292pub struct Ket {
293 pub gate: String,
294 #[serde(skip_serializing_if = "Vec::is_empty")]
295 #[serde(default)]
296 pub args: Vec<String>,
297 #[serde(skip_serializing_if = "Vec::is_empty")]
298 #[serde(default)]
299 pub children: ComponentGrid,
300 pub targets: Vec<Register>,
301 #[serde(skip_serializing_if = "Option::is_none")]
302 pub metadata: Option<Metadata>,
303}
304
305#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Clone)]
306pub struct Register {
307 pub qubit: usize,
308 #[serde(skip_serializing_if = "Option::is_none")]
309 pub result: Option<usize>,
310}
311
312impl Register {
313 #[must_use]
314 pub fn quantum(qubit_id: usize) -> Self {
315 Self {
316 qubit: qubit_id,
317 result: None,
318 }
319 }
320
321 #[must_use]
322 pub fn classical(qubit_id: usize, result_id: usize) -> Self {
323 Self {
324 qubit: qubit_id,
325 result: Some(result_id),
326 }
327 }
328
329 #[must_use]
330 pub fn is_classical(&self) -> bool {
331 self.result.is_some()
332 }
333}
334
335#[derive(Clone, Serialize, Deserialize, Debug)]
336pub struct Qubit {
337 pub id: usize,
338 #[serde(rename = "numResults")]
339 #[serde(default)]
340 pub num_results: usize,
341 #[serde(skip_serializing_if = "Vec::is_empty")]
342 #[serde(default)]
343 pub declarations: Vec<SourceLocation>,
344}
345
346#[derive(Clone, Serialize, Deserialize, Debug, Default)]
347#[serde(rename_all = "camelCase")]
348/// The schema of `Metadata` may change and its contents
349/// are never meant to be persisted in a .qsc file.
350pub struct Metadata {
351 #[serde(skip_serializing_if = "Option::is_none")]
352 /// The location in the source code that this operation originated from.
353 pub source: Option<SourceLocation>,
354 #[serde(skip_serializing_if = "Option::is_none")]
355 /// Only populated if this operation represents a scope group.
356 pub scope_location: Option<SourceLocation>,
357 #[serde(skip_serializing_if = "Vec::is_empty")]
358 /// Map from Register to "result ID" which will be used to display labels in UI
359 pub control_result_ids: Vec<(Register, usize)>,
360}
361
362#[derive(Clone, Serialize, Deserialize, Debug)]
363pub struct SourceLocation {
364 pub file: String,
365 pub line: u32,
366 pub column: u32,
367}
368
369impl Display for SourceLocation {
370 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
371 write!(f, "{}:{}:{}", self.file, self.line, self.column)
372 }
373}
374
375type ObjectsByColumn = FxHashMap<usize, CircuitObject>;
376
377struct Row {
378 wire: Wire,
379 objects: ObjectsByColumn,
380 next_column: usize,
381 render_locations: bool,
382}
383
384enum Wire {
385 Qubit { label: String },
386 Classical { start_column: Option<usize> },
387}
388
389#[derive(Debug)]
390enum CircuitObject {
391 Blank,
392 Wire,
393 WireCross,
394 WireStart,
395 DashedCross,
396 Vertical,
397 VerticalDashed,
398 Object(String),
399}
400
401impl Row {
402 fn add_object(&mut self, column: usize, object: &str) {
403 self.add(column, CircuitObject::Object(object.to_string()));
404 }
405
406 fn add_measurement(&mut self, column: usize, source: Option<&SourceLocation>) {
407 let mut gate_label = String::from("M");
408 if self.render_locations
409 && let Some(loc) = source
410 {
411 let _ = write!(&mut gate_label, "@{loc}");
412 }
413 self.add(column, CircuitObject::Object(gate_label.clone()));
414 }
415
416 fn add_gate(&mut self, column: usize, operation: &Operation, inset_marker: Option<usize>) {
417 let mut gate_label = self.operation_label(operation);
418 if let Some(inset_id) = inset_marker {
419 let _ = write!(&mut gate_label, "[{inset_id}]");
420 }
421 self.add_object(column, gate_label.as_str());
422 }
423
424 fn operation_label(&self, operation: &Operation) -> String {
425 let mut gate_label = String::new();
426 gate_label.push_str(&operation.gate());
427 if operation.is_adjoint() {
428 gate_label.push('\'');
429 }
430
431 if !operation.args().is_empty() {
432 let args = operation.args().join(", ");
433 let _ = write!(&mut gate_label, "({args})");
434 }
435
436 if self.render_locations
437 && let Some(loc) = operation.source_location()
438 {
439 let _ = write!(&mut gate_label, "@{loc}");
440 }
441 gate_label
442 }
443
444 fn add_vertical(&mut self, column: usize) {
445 if !self.objects.contains_key(&column) {
446 match self.wire {
447 Wire::Qubit { .. } => self.add(column, CircuitObject::WireCross),
448 Wire::Classical { start_column } => {
449 if start_column.is_some() {
450 self.add(column, CircuitObject::WireCross);
451 } else {
452 self.add(column, CircuitObject::Vertical);
453 }
454 }
455 }
456 }
457 }
458
459 fn add_dashed_vertical(&mut self, column: usize) {
460 if !self.objects.contains_key(&column) {
461 match self.wire {
462 Wire::Qubit { .. } => self.add(column, CircuitObject::DashedCross),
463 Wire::Classical { start_column } => {
464 if start_column.is_some() {
465 self.add(column, CircuitObject::DashedCross);
466 } else {
467 self.add(column, CircuitObject::VerticalDashed);
468 }
469 }
470 }
471 }
472 }
473
474 fn start_classical(&mut self, column: usize) {
475 if let Wire::Classical { start_column: None } = &self.wire {
476 self.add(column, CircuitObject::WireStart);
477 self.wire = Wire::Classical {
478 start_column: Some(column),
479 };
480 }
481 }
482
483 fn add(&mut self, column: usize, circuit_object: CircuitObject) {
484 self.objects.insert(column, circuit_object);
485 self.next_column = column + 1;
486 }
487
488 fn fmt(&self, f: &mut impl Write, columns: &[Column]) -> std::fmt::Result {
489 // Temporary string so we can trim whitespace at the end
490 let mut s = String::new();
491 match &self.wire {
492 Wire::Qubit { label } => {
493 s.write_str(&columns[0].fmt_qubit_label(label))?;
494 for (column_index, column) in columns.iter().enumerate().skip(1) {
495 let obj = self.objects.get(&column_index);
496
497 s.write_str(&column.fmt_object_on_qubit_wire(obj))?;
498 }
499 }
500 Wire::Classical { start_column } => {
501 for (column_index, column) in columns.iter().enumerate() {
502 let obj = self.objects.get(&column_index);
503
504 if let Some(start) = *start_column
505 && column_index >= start
506 {
507 s.write_str(&column.fmt_object_on_classical_wire(obj))?;
508 } else {
509 s.write_str(&column.fmt_object(obj))?;
510 }
511 }
512 }
513 }
514 writeln!(f, "{}", s.trim_end())?;
515 Ok(())
516 }
517}
518
519const MIN_COLUMN_WIDTH: usize = 7;
520
521const QUBIT_WIRE: [char; 3] = ['─', '─', '─']; // "───────"
522const CLASSICAL_WIRE: [char; 3] = ['═', '═', '═']; // "═══════"
523const QUBIT_WIRE_CROSS: [char; 3] = ['─', '┼', '─']; // "───┼───"
524const CLASSICAL_WIRE_CROSS: [char; 3] = ['═', '╪', '═']; // "═══╪═══"
525const CLASSICAL_WIRE_START: [char; 3] = [' ', '╘', '═']; // " ╘═══"
526const QUBIT_WIRE_DASHED_CROSS: [char; 3] = ['─', '┆', '─']; // "───┆───"
527const CLASSICAL_WIRE_DASHED_CROSS: [char; 3] = ['═', '┆', '═']; // "═══┆═══"
528const VERTICAL_DASHED: [char; 3] = [' ', '┆', ' ']; // " ┆ "
529const VERTICAL: [char; 3] = [' ', '│', ' ']; // " │ "
530const BLANK: [char; 3] = [' ', ' ', ' ']; // " "
531
532struct Column {
533 column_width: usize,
534}
535
536impl Column {
537 fn new(column_width: usize) -> Self {
538 // Column widths should be odd numbers for this struct to work well
539 let odd_column_width = column_width | 1;
540 Self {
541 column_width: odd_column_width,
542 }
543 }
544
545 /// "q_0 "
546 #[allow(clippy::doc_markdown)]
547 fn fmt_qubit_label(&self, label: &str) -> String {
548 let column_width = self.column_width;
549 let s = format!("{label:<column_width$}");
550 s
551 }
552
553 /// "── A ──"
554 fn fmt_on_qubit_wire(&self, obj: &str) -> String {
555 let column_width = self.column_width;
556 format!("{:─^column_width$}", format!(" {obj} "))
557 }
558
559 /// "══ A ══"
560 fn fmt_on_classical_wire(&self, obj: &str) -> String {
561 let column_width = self.column_width;
562 format!("{:═^column_width$}", format!(" {obj} "))
563 }
564
565 /// " A "
566 fn fmt_on_blank(&self, obj: &str) -> String {
567 let column_width = self.column_width;
568 format!("{: ^column_width$}", format!(" {obj} "))
569 }
570
571 fn expand_template(&self, template: &[char; 3]) -> String {
572 let half_width = self.column_width / 2;
573 let left = template[0].to_string().repeat(half_width);
574 let right = template[2].to_string().repeat(half_width);
575
576 format!("{left}{}{right}", template[1])
577 }
578
579 fn fmt_object_on_classical_wire(&self, circuit_object: Option<&CircuitObject>) -> String {
580 let circuit_object = circuit_object.unwrap_or(&CircuitObject::Wire);
581
582 if let CircuitObject::Object(label) = circuit_object {
583 return self.fmt_on_classical_wire(label.as_str());
584 }
585
586 let template = match circuit_object {
587 CircuitObject::Wire => CLASSICAL_WIRE,
588 CircuitObject::WireCross | CircuitObject::Vertical => CLASSICAL_WIRE_CROSS,
589 CircuitObject::WireStart => CLASSICAL_WIRE_START,
590 CircuitObject::DashedCross => CLASSICAL_WIRE_DASHED_CROSS,
591 o @ (CircuitObject::VerticalDashed | CircuitObject::Blank) => {
592 unreachable!("unexpected object on blank row: {o:?}")
593 }
594 CircuitObject::Object(_) => unreachable!("case should have been handled earlier"),
595 };
596
597 self.expand_template(&template)
598 }
599
600 fn fmt_object_on_qubit_wire(&self, circuit_object: Option<&CircuitObject>) -> String {
601 let circuit_object = circuit_object.unwrap_or(&CircuitObject::Wire);
602 if let CircuitObject::Object(label) = circuit_object {
603 return self.fmt_on_qubit_wire(label.as_str());
604 }
605
606 let template = match circuit_object {
607 CircuitObject::Wire => QUBIT_WIRE,
608 CircuitObject::WireCross | CircuitObject::Vertical => QUBIT_WIRE_CROSS,
609 CircuitObject::DashedCross => QUBIT_WIRE_DASHED_CROSS,
610 CircuitObject::WireStart
611 | CircuitObject::VerticalDashed
612 | CircuitObject::Blank
613 | CircuitObject::Object(_) => unreachable!(),
614 };
615
616 self.expand_template(&template)
617 }
618
619 fn fmt_object(&self, circuit_object: Option<&CircuitObject>) -> String {
620 let circuit_object = circuit_object.unwrap_or(&CircuitObject::Blank);
621 if let CircuitObject::Object(label) = circuit_object {
622 return self.fmt_on_blank(label.as_str());
623 }
624
625 let template = match circuit_object {
626 CircuitObject::WireStart => CLASSICAL_WIRE_START,
627 CircuitObject::Blank => BLANK,
628 CircuitObject::Vertical => VERTICAL,
629 CircuitObject::VerticalDashed => VERTICAL_DASHED,
630 o @ (CircuitObject::Wire | CircuitObject::WireCross | CircuitObject::DashedCross) => {
631 unreachable!("unexpected object on blank row: {o:?}")
632 }
633 CircuitObject::Object(_) => {
634 unreachable!("case should have been handled earlier")
635 }
636 };
637
638 self.expand_template(&template)
639 }
640}
641
642struct CircuitDisplay<'a> {
643 circuit: &'a Circuit,
644 render_locations: bool,
645 render_groups: bool,
646}
647
648impl Display for CircuitDisplay<'_> {
649 /// Formats the circuit into a diagram.
650 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
651 let mut insets = vec![];
652 let grid = &self.circuit.component_grid;
653 let qubits = &self.circuit.qubits;
654
655 self.fmt_grid(f, &mut insets, grid, qubits)?;
656
657 let mut i = 0;
658 while i < insets.len() {
659 let (label, inset_grid) = take(&mut insets[i]);
660 writeln!(f)?;
661 writeln!(f, "{label}:")?;
662 let mut indent = indented(f).with_str(" ");
663 self.fmt_grid(&mut indent, &mut insets, &inset_grid, qubits)?;
664 i += 1;
665 }
666
667 Ok(())
668 }
669}
670
671impl CircuitDisplay<'_> {
672 fn fmt_grid(
673 &self,
674 f: &mut impl Write,
675 insets: &mut Vec<(String, Vec<ComponentColumn>)>,
676 grid: &Vec<ComponentColumn>,
677 qubits: &[Qubit],
678 ) -> Result<(), std::fmt::Error> {
679 let mut rows = vec![];
680 // Maintain a mapping from from Registers in the Circuit schema
681 // to row in the diagram
682 let mut register_to_row = FxHashMap::default();
683
684 // Keep track of which qubits have the qubit after them in the same multi-qubit operation,
685 // because those qubits need to get a gap row below them.
686 let mut qubits_with_gap_row_below = FxHashSet::default();
687
688 // Identify qubits that require gap rows
689 self.identify_qubits_with_gap_rows(grid, &mut qubits_with_gap_row_below);
690
691 // Initialize rows for qubits and classical wires
692 self.initialize_rows(
693 &mut rows,
694 qubits,
695 &mut register_to_row,
696 &qubits_with_gap_row_below,
697 );
698
699 // Add operations to the diagram
700 self.add_grid(1, grid, &mut rows, insets, &register_to_row);
701
702 // Finalize the diagram by extending wires and formatting columns
703 let columns = finalize_columns(&rows);
704
705 // Draw the diagram
706 for row in rows {
707 row.fmt(f, &columns)?;
708 }
709
710 Ok(())
711 }
712
713 /// Identifies qubits that require gap rows for multi-qubit operations.
714 fn identify_qubits_with_gap_rows(
715 &self,
716 grid: &ComponentGrid,
717 qubits_with_gap_row_below: &mut FxHashSet<usize>,
718 ) {
719 for col in grid {
720 self.add_qubits_with_gap_rows(&col.components, qubits_with_gap_row_below);
721 }
722 }
723
724 fn add_qubits_with_gap_rows(
725 &self,
726 components: &Vec<Operation>,
727 qubits_with_gap_row_below: &mut FxHashSet<usize>,
728 ) {
729 for op in components {
730 if !op.children().is_empty() && !self.render_groups {
731 for c in op.children() {
732 self.add_qubits_with_gap_rows(&c.components, qubits_with_gap_row_below);
733 }
734 continue;
735 }
736
737 let targets = match op {
738 Operation::Measurement(m) => &m.qubits,
739 Operation::Unitary(u) => &u.targets,
740 Operation::Ket(k) => &k.targets,
741 };
742 for target in targets {
743 let qubit = target.qubit;
744
745 if qubits_with_gap_row_below.contains(&qubit) {
746 continue;
747 }
748
749 let next_qubit = qubit + 1;
750
751 // Check if the next qubit is also in this operation.
752 if targets.iter().any(|t| t.qubit == next_qubit) {
753 qubits_with_gap_row_below.insert(qubit);
754 }
755 }
756 }
757 }
758
759 /// Initializes rows for qubits and classical wires.
760 fn initialize_rows(
761 &self,
762 rows: &mut Vec<Row>,
763 qubits: &[Qubit],
764 register_to_row: &mut FxHashMap<(usize, Option<usize>), usize>,
765 qubits_with_gap_row_below: &FxHashSet<usize>,
766 ) {
767 for q in qubits {
768 let mut label = format!("q_{}", q.id);
769 if self.render_locations {
770 let mut first = true;
771 for loc in &q.declarations {
772 if first {
773 label.push('@');
774 first = false;
775 } else {
776 label.push_str(", ");
777 }
778 let _ = write!(&mut label, "{loc}");
779 }
780 }
781 rows.push(Row {
782 wire: Wire::Qubit { label },
783 objects: FxHashMap::default(),
784 next_column: 1,
785 render_locations: self.render_locations,
786 });
787
788 register_to_row.insert((q.id, None), rows.len() - 1);
789
790 // If this qubit has no children, but it is in a multi-qubit operation with
791 // the next qubit, we add an empty row to make room for the vertical connector.
792 // We can just use a classical wire type for this row since the wire won't actually be rendered.
793 let extra_rows = if qubits_with_gap_row_below.contains(&q.id) {
794 max(1, q.num_results)
795 } else {
796 q.num_results
797 };
798
799 for i in 0..extra_rows {
800 rows.push(Row {
801 wire: Wire::Classical { start_column: None },
802 objects: FxHashMap::default(),
803 next_column: 1,
804 render_locations: self.render_locations,
805 });
806
807 register_to_row.insert((q.id, Some(i)), rows.len() - 1);
808 }
809 }
810 }
811
812 /// Adds operations to the diagram.
813 fn add_grid(
814 &self,
815 start_column: usize,
816 component_grid: &ComponentGrid,
817 rows: &mut [Row],
818 insets: &mut Vec<(String, Vec<ComponentColumn>)>,
819 register_to_row: &FxHashMap<(usize, Option<usize>), usize>,
820 ) -> usize {
821 let mut curr_column = start_column;
822 for column_operations in component_grid {
823 let offset = self.add_column(
824 rows,
825 insets,
826 register_to_row,
827 curr_column,
828 column_operations,
829 );
830 curr_column += offset;
831 }
832 curr_column - start_column
833 }
834
835 fn add_column(
836 &self,
837 rows: &mut [Row],
838 insets: &mut Vec<(String, Vec<ComponentColumn>)>,
839 register_to_row: &FxHashMap<(usize, Option<usize>), usize>,
840 column: usize,
841 col: &ComponentColumn,
842 ) -> usize {
843 let mut col_width = 0;
844 for op in &col.components {
845 let target_rows = get_row_indexes(op, register_to_row, true);
846 let control_rows = get_row_indexes(op, register_to_row, false);
847
848 let mut all_rows = target_rows.clone();
849 all_rows.extend(control_rows.iter());
850 all_rows.sort_unstable();
851
852 // We'll need to know the entire range of rows for this operation so we can
853 // figure out the starting column and also so we can draw any
854 // vertical lines that cross wires.
855 let (begin, end) = all_rows.split_first().map_or((0, 0), |(first, tail)| {
856 (*first, tail.last().unwrap_or(first) + 1)
857 });
858
859 if op.children().is_empty() {
860 add_operation_to_rows(
861 op,
862 rows,
863 &target_rows,
864 &control_rows,
865 column,
866 begin,
867 end,
868 None,
869 );
870 col_width = max(col_width, 1);
871 } else if self.render_groups {
872 if all_rows.len() <= 1 {
873 // This operation has children. The entire operation fits on a single qubit wire, so
874 // we can just render the group in-place without needing an inset. let offset = self.add_boxed_group(
875 let offset = self.add_boxed_group(
876 rows,
877 insets,
878 register_to_row,
879 &all_rows,
880 column,
881 op,
882 op.children(),
883 );
884 col_width = max(col_width, offset);
885 } else {
886 // This operation has children and would span multiple wires. We render a placeholder for this operation
887 // and then later render the children in an inset below.
888 let i = insets.len() + 1;
889
890 let mut placeholder_op = op.clone();
891 let children = take(placeholder_op.children_mut());
892
893 let inset_label = format!("[{i}] {}", op.gate());
894 insets.push((inset_label, children));
895
896 add_operation_to_rows(
897 &placeholder_op,
898 rows,
899 &target_rows,
900 &control_rows,
901 column,
902 begin,
903 end,
904 Some(i),
905 );
906
907 col_width = max(col_width, 1);
908 }
909 } else if op.is_controlled() {
910 // Rendering groups is disabled, but this is a controlled group - the meaning of the diagram would change if we showed just
911 // the group's contents without the control. So just render the group as a "black box" with the control,
912 // as if it's a standalone gate
913 add_operation_to_rows(
914 op,
915 rows,
916 &target_rows,
917 &control_rows,
918 column,
919 begin,
920 end,
921 None,
922 );
923 col_width = max(col_width, 1);
924 } else {
925 // Don't render the group, render all the children directly
926 let offset = self.add_grid(column, op.children(), rows, insets, register_to_row);
927
928 col_width = max(col_width, offset);
929 }
930 }
931
932 col_width
933 }
934
935 #[allow(clippy::too_many_arguments)]
936 fn add_boxed_group(
937 &self,
938 rows: &mut [Row],
939 insets: &mut Vec<(String, Vec<ComponentColumn>)>,
940 register_to_row: &FxHashMap<(usize, Option<usize>), usize>,
941 target_rows: &[usize],
942 column: usize,
943 op: &Operation,
944 children: &Vec<ComponentColumn>,
945 ) -> usize {
946 assert!(
947 !op.children().is_empty(),
948 "must only be called for an operation with children"
949 );
950 assert!(
951 !op.is_measurement(),
952 "rendering measurement boxes not supported"
953 );
954
955 let mut offset = 0;
956 add_box_start(op, rows, target_rows, column);
957 offset += 1;
958
959 offset += self.add_grid(column + offset, children, rows, insets, register_to_row);
960
961 add_box_end(op, rows, target_rows, column + offset);
962 offset += 1;
963 offset
964 }
965}
966
967#[allow(clippy::too_many_arguments)]
968/// Adds a single operation to the rows.
969fn add_operation_to_rows(
970 operation: &Operation,
971 rows: &mut [Row],
972 targets: &[usize],
973 controls: &[usize],
974 column: usize,
975 begin: usize,
976 end: usize,
977 inset_marker: Option<usize>,
978) {
979 for i in targets {
980 let row = &mut rows[*i];
981 if matches!(row.wire, Wire::Classical { .. }) {
982 row.start_classical(column);
983 } else {
984 row.add_gate(column, operation, inset_marker);
985 }
986 }
987
988 if operation.is_controlled() || operation.is_measurement() {
989 for i in controls {
990 let row = &mut rows[*i];
991 if matches!(row.wire, Wire::Qubit { .. }) && operation.is_measurement() {
992 row.add_measurement(column, operation.source_location());
993 } else {
994 row.add_object(column, "●");
995 }
996 }
997
998 // If we have a control wire, draw vertical lines spanning all
999 // control and target wires and crossing any in between
1000 // (vertical lines may overlap if there are multiple controls/targets,
1001 // this is ok in practice)
1002 for row in &mut rows[begin..end] {
1003 row.add_vertical(column);
1004 }
1005 } else {
1006 // No control wire. Draw dashed vertical lines to connect
1007 // target wires if there are multiple targets
1008 for row in &mut rows[begin..end] {
1009 row.add_dashed_vertical(column);
1010 }
1011 }
1012}
1013
1014fn add_box_start(operation: &Operation, rows: &mut [Row], target_rows: &[usize], column: usize) {
1015 assert!(
1016 !operation.children().is_empty(),
1017 "must only be called for an operation with children"
1018 );
1019
1020 let mut first = true;
1021
1022 for i in target_rows {
1023 if first {
1024 first = false;
1025 let label = rows[*i].operation_label(operation);
1026 rows[*i].add_object(column, format!("[ [{label}]").as_str());
1027 } else {
1028 rows[*i].add_object(column, "[");
1029 }
1030 }
1031}
1032
1033fn add_box_end(operation: &Operation, rows: &mut [Row], target_rows: &[usize], column: usize) {
1034 assert!(
1035 !operation.children().is_empty(),
1036 "must only be called for an operation with children"
1037 );
1038
1039 for i in target_rows {
1040 rows[*i].add_object(column, "]");
1041 }
1042}
1043
1044/// Finalizes the columns by calculating their widths.
1045fn finalize_columns(rows: &[Row]) -> Vec<Column> {
1046 // Find the end column for the whole circuit so that
1047 // all qubit wires will extend until the end
1048 let end_column = rows
1049 .iter()
1050 .max_by_key(|r| r.next_column)
1051 .map_or(1, |r| r.next_column);
1052
1053 let longest_qubit_label = rows
1054 .iter()
1055 .map(|r| {
1056 if let Wire::Qubit { label } = &r.wire {
1057 label.len() + 1
1058 } else {
1059 0
1060 }
1061 })
1062 .chain(std::iter::once(MIN_COLUMN_WIDTH))
1063 .max()
1064 .unwrap_or_default();
1065
1066 // To be able to fit long-named operations, we calculate the required width for each column,
1067 // based on the maximum length needed for gates, where a gate X is printed as "- X -".
1068 std::iter::once(longest_qubit_label)
1069 .chain((1..end_column).map(|column| {
1070 rows.iter()
1071 .filter_map(|row| row.objects.get(&column))
1072 .filter_map(|object| match object {
1073 CircuitObject::Object(string) => Some(string.len() + 4),
1074 _ => None,
1075 })
1076 .chain(std::iter::once(MIN_COLUMN_WIDTH))
1077 .max()
1078 .expect("Column width should be at least 1")
1079 }))
1080 .map(Column::new)
1081 .collect()
1082}
1083
1084/// Gets the row indexes for the targets or controls of an operation.
1085fn get_row_indexes(
1086 operation: &Operation,
1087 register_to_row: &FxHashMap<(usize, Option<usize>), usize>,
1088 is_target: bool,
1089) -> Vec<usize> {
1090 let registers = match operation {
1091 Operation::Measurement(m) => {
1092 if is_target {
1093 &m.results
1094 } else {
1095 &m.qubits
1096 }
1097 }
1098 Operation::Unitary(u) => {
1099 if is_target {
1100 &u.targets
1101 } else {
1102 &u.controls
1103 }
1104 }
1105 Operation::Ket(k) => {
1106 if is_target {
1107 &k.targets
1108 } else {
1109 &vec![]
1110 }
1111 }
1112 };
1113
1114 registers
1115 .iter()
1116 .filter_map(|reg| {
1117 let reg = (reg.qubit, reg.result);
1118 register_to_row.get(&reg).copied()
1119 })
1120 .collect()
1121}
1122
1123/// Converts a list of operations into a 2D grid of operations in col-row format.
1124/// Operations will be left-justified as much as possible in the resulting grid.
1125/// Children operations are recursively converted into a grid.
1126///
1127/// # Arguments
1128///
1129/// * `operations` - A vector of operations to be converted.
1130/// * `num_qubits` - The number of qubits in the circuit.
1131///
1132/// # Returns
1133///
1134/// A component grid representing the operations.
1135#[must_use]
1136pub fn operation_list_to_grid(
1137 mut operations: Vec<Operation>,
1138 qubits: &[Qubit],
1139) -> Vec<ComponentColumn> {
1140 for op in &mut operations {
1141 // The children data structure is a grid, so checking if it is
1142 // length 1 is actually checking if it has a single column,
1143 // or in other words, we are checking if its children are in a single list.
1144 // If the operation has children in a single list, it needs to be converted to a grid.
1145 // If it was already converted to a grid, but the grid was still a single list,
1146 // then doing it again won't effect anything.
1147 if op.children().len() == 1 {
1148 match op {
1149 Operation::Measurement(m) => {
1150 let child_vec = m.children.remove(0).components;
1151 m.children = operation_list_to_grid(child_vec, qubits);
1152 }
1153 Operation::Unitary(u) => {
1154 let child_vec = u.children.remove(0).components;
1155 u.children = operation_list_to_grid(child_vec, qubits);
1156 }
1157 Operation::Ket(k) => {
1158 let child_vec = k.children.remove(0).components;
1159 k.children = operation_list_to_grid(child_vec, qubits);
1160 }
1161 }
1162 }
1163 }
1164
1165 // Convert the operations into a component grid
1166 operation_list_to_grid_base(operations, qubits)
1167}
1168
1169#[derive(Debug)]
1170struct RowInfo {
1171 register: Register,
1172 next_available_column: usize,
1173}
1174
1175fn get_row_for_register(register: &Register, rows: &[RowInfo]) -> usize {
1176 rows.iter()
1177 .position(|r| r.register == *register)
1178 .unwrap_or_else(|| panic!("register {register:?} not found in rows {rows:?}"))
1179}
1180
1181fn operation_list_to_grid_base(
1182 operations: Vec<Operation>,
1183 qubits: &[Qubit],
1184) -> Vec<ComponentColumn> {
1185 let mut rows = vec![];
1186 for q in qubits {
1187 rows.push(RowInfo {
1188 register: Register::quantum(q.id),
1189 next_available_column: 0,
1190 });
1191 for i in 0..q.num_results {
1192 rows.push(RowInfo {
1193 register: Register::classical(q.id, i),
1194 next_available_column: 0,
1195 });
1196 }
1197 }
1198
1199 let mut columns: Vec<ComponentColumn> = vec![];
1200
1201 for op in operations {
1202 // get the entire range that this operation spans
1203 let targets = match &op {
1204 Operation::Measurement(m) => &m.qubits,
1205 Operation::Unitary(u) => &u.targets,
1206 Operation::Ket(k) => &k.targets,
1207 };
1208 let controls = match &op {
1209 Operation::Measurement(m) => &m.results,
1210 Operation::Unitary(u) => &u.controls,
1211 Operation::Ket(_) => &vec![],
1212 };
1213 let mut all_rows = targets
1214 .iter()
1215 .chain(controls.iter())
1216 .map(|r| get_row_for_register(r, &rows))
1217 .collect::<Vec<_>>();
1218 all_rows.sort_unstable();
1219 let (begin, end) = all_rows.split_first().map_or((0, 0), |(first, tail)| {
1220 (*first, tail.last().unwrap_or(first) + 1)
1221 });
1222 // find the earliest column that all rows in this range are available
1223 let column = rows[begin..end]
1224 .iter()
1225 .map(|r| r.next_available_column)
1226 .max()
1227 .unwrap_or(0);
1228 // assign this operation to that column
1229 // and update the rows to mark them as occupied until the next column
1230 for r in &mut rows[begin..end] {
1231 r.next_available_column = column + 1;
1232 }
1233 if columns.len() <= column {
1234 columns.resize_with(column + 1, || ComponentColumn { components: vec![] });
1235 }
1236 columns[column].components.push(op);
1237 }
1238
1239 columns
1240}
1241