aboutsummaryrefslogtreecommitdiff
path: root/libpsi-visualizer
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2025-12-06 22:33:56 +0100
committerhachem <im@hachem.wtf>2025-12-06 22:33:56 +0100
commit1fb7989e9405f9795a1d5b3953881b9bb8a137b4 (patch)
treec680f041fe41e5ccd1e311ec7b1502abb96237af /libpsi-visualizer
parentedc6bae7bf5039c8cef2f2c9c164187478b1c7eb (diff)
[add]: custom gates + rework vertical renderer
Diffstat (limited to 'libpsi-visualizer')
-rw-r--r--libpsi-visualizer/src/cli/horizontal_cli.rs25
-rw-r--r--libpsi-visualizer/src/cli/vertical_cli.rs219
2 files changed, 169 insertions, 75 deletions
diff --git a/libpsi-visualizer/src/cli/horizontal_cli.rs b/libpsi-visualizer/src/cli/horizontal_cli.rs
index 067e23d..bac0c5e 100644
--- a/libpsi-visualizer/src/cli/horizontal_cli.rs
+++ b/libpsi-visualizer/src/cli/horizontal_cli.rs
@@ -246,6 +246,31 @@ impl<'a> fmt::Display for HorizontalRenderer<'a> {
}
gap_line.push_str(" ║ ");
}
+ GateOp::Custom(gate, targets) => {
+ let name = &gate.name;
+ let label = format!("[{}]", name);
+
+ for (i, line) in q_lines.iter_mut().enumerate() {
+ if targets.contains(&i) {
+ if i == targets[0] {
+ line.push_str(&format!("─{}─", label));
+ } else {
+ line.push_str(&format!("─{}─", "─".repeat(label.len())));
+ }
+ } else if i > min_q && i < max_q {
+ line.push_str(&format!(
+ "─{}─",
+ "│".to_string() + &"─".repeat(label.len() - 1)
+ ));
+ } else {
+ line.push_str(&format!("─{}─", "─".repeat(label.len())));
+ }
+ }
+ for line in c_lines.iter_mut() {
+ line.push_str(&format!("═{}═", "═".repeat(label.len())));
+ }
+ gap_line.push_str(&format!(" {} ", " ".repeat(label.len())));
+ }
}
}
diff --git a/libpsi-visualizer/src/cli/vertical_cli.rs b/libpsi-visualizer/src/cli/vertical_cli.rs
index de0ee89..317833c 100644
--- a/libpsi-visualizer/src/cli/vertical_cli.rs
+++ b/libpsi-visualizer/src/cli/vertical_cli.rs
@@ -10,6 +10,44 @@ impl<'a> VerticalRenderer<'a> {
pub fn new(circuit: &'a QuantumCircuit) -> Self {
VerticalRenderer { circuit }
}
+
+ fn gate_label(op: &GateOp) -> String {
+ match op {
+ GateOp::H(_) => "[H]".to_string(),
+ GateOp::X(_) => "[X]".to_string(),
+ GateOp::Y(_) => "[Y]".to_string(),
+ GateOp::Z(_) => "[Z]".to_string(),
+ GateOp::S(_) => "[S]".to_string(),
+ GateOp::T(_) => "[T]".to_string(),
+ GateOp::CNOT(_, _) => "●".to_string(),
+ GateOp::CZ(_, _) => "●".to_string(),
+ GateOp::SWAP(_, _) => "╳".to_string(),
+ GateOp::CCNOT(_, _, _) => "●".to_string(),
+ GateOp::CSWAP(_, _, _) => "●".to_string(),
+ GateOp::Measure(_, _) => "[M]".to_string(),
+ GateOp::Custom(gate, _) => format!("[{}]", gate.name),
+ }
+ }
+
+ fn calculate_col_width(&self) -> usize {
+ let min_width = 3;
+ let mut max_label_len = min_width;
+
+ for op in self.circuit.operations() {
+ let label = Self::gate_label(op);
+ let char_count: usize = label.chars().count();
+ if char_count > max_label_len {
+ max_label_len = char_count;
+ }
+ }
+
+ let width = max_label_len + 2;
+ if width % 2 == 0 {
+ width + 1
+ } else {
+ width
+ }
+ }
}
impl<'a> Visualizer for VerticalRenderer<'a> {
@@ -24,7 +62,7 @@ impl<'a> fmt::Display for VerticalRenderer<'a> {
let nc = self.circuit.num_classical();
let ops = self.circuit.operations();
- let col_width = 5;
+ let col_width = self.calculate_col_width();
let gap_width = 3;
let q_header: String = (0..nq)
@@ -64,6 +102,10 @@ impl<'a> fmt::Display for VerticalRenderer<'a> {
return Ok(());
}
+ let q_total = nq * col_width + (nq - 1);
+ let c_total = if nc > 0 { nc * col_width + (nc - 1) } else { 0 };
+ let total_width = q_total + gap_width + c_total;
+
for op in ops {
writeln!(f, "{}", full_wires)?;
@@ -71,45 +113,46 @@ impl<'a> fmt::Display for VerticalRenderer<'a> {
let min_q = q_targets.iter().min().copied().unwrap_or(0);
let max_q = q_targets.iter().max().copied().unwrap_or(0);
- let mut q_cols: Vec<String> = (0..nq)
- .map(|_| format!("{:^width$}", "│", width = col_width))
- .collect();
-
- let c_cols: Vec<String> = (0..nc)
- .map(|_| format!("{:^width$}", "║", width = col_width))
- .collect();
+ let label = Self::gate_label(op);
match op {
- GateOp::H(t) => {
- q_cols[*t] = format!("{:^width$}", "[H]", width = col_width);
- }
- GateOp::X(t) => {
- q_cols[*t] = format!("{:^width$}", "[X]", width = col_width);
- }
- GateOp::Y(t) => {
- q_cols[*t] = format!("{:^width$}", "[Y]", width = col_width);
- }
- GateOp::Z(t) => {
- q_cols[*t] = format!("{:^width$}", "[Z]", width = col_width);
- }
- GateOp::S(t) => {
- q_cols[*t] = format!("{:^width$}", "[S]", width = col_width);
- }
- GateOp::T(t) => {
- q_cols[*t] = format!("{:^width$}", "[T]", width = col_width);
+ GateOp::H(t)
+ | GateOp::X(t)
+ | GateOp::Y(t)
+ | GateOp::Z(t)
+ | GateOp::S(t)
+ | GateOp::T(t) => {
+ let mut line: Vec<char> = vec![' '; total_width];
+
+ for i in 0..nq {
+ let col_start = i * (col_width + 1);
+ let center = col_start + col_width / 2;
+ if i == *t {
+ let label_start = col_start + (col_width - label.chars().count()) / 2;
+ for (j, ch) in label.chars().enumerate() {
+ line[label_start + j] = ch;
+ }
+ } else {
+ line[center] = '│';
+ }
+ }
+
+ for i in 0..nc {
+ let center = q_total + gap_width + i * (col_width + 1) + col_width / 2;
+ line[center] = '║';
+ }
+
+ let gate_line: String = line.into_iter().collect();
+ writeln!(f, "{}", gate_line)?;
}
GateOp::CNOT(c, t) | GateOp::CZ(c, t) | GateOp::SWAP(c, t) => {
let (sym1, sym2) = match op {
- GateOp::CNOT(_, _) => ("●", "⊕"),
- GateOp::CZ(_, _) => ("●", "●"),
- GateOp::SWAP(_, _) => ("╳", "╳"),
+ GateOp::CNOT(_, _) => ('●', '⊕'),
+ GateOp::CZ(_, _) => ('●', '●'),
+ GateOp::SWAP(_, _) => ('╳', '╳'),
_ => unreachable!(),
};
- let q_total = nq * col_width + (nq - 1);
- let c_total = if nc > 0 { nc * col_width + (nc - 1) } else { 0 };
- let total_width = q_total + gap_width + c_total;
-
let mut line: Vec<char> = vec![' '; total_width];
for i in 0..nq {
@@ -117,9 +160,9 @@ impl<'a> fmt::Display for VerticalRenderer<'a> {
if i < min_q || i > max_q {
line[center] = '│';
} else if i == *c {
- line[center] = sym1.chars().next().unwrap();
+ line[center] = sym1;
} else if i == *t {
- line[center] = sym2.chars().next().unwrap();
+ line[center] = sym2;
}
}
@@ -138,7 +181,6 @@ impl<'a> fmt::Display for VerticalRenderer<'a> {
let gate_line: String = line.into_iter().collect();
writeln!(f, "{}", gate_line)?;
- continue;
}
GateOp::CCNOT(c1, c2, t) | GateOp::CSWAP(c1, c2, t) => {
let (sym_c, sym_t) = match op {
@@ -148,10 +190,6 @@ impl<'a> fmt::Display for VerticalRenderer<'a> {
};
let is_cswap = matches!(op, GateOp::CSWAP(_, _, _));
- let q_total = nq * col_width + (nq - 1);
- let c_total = if nc > 0 { nc * col_width + (nc - 1) } else { 0 };
- let total_width = q_total + gap_width + c_total;
-
let mut line: Vec<char> = vec![' '; total_width];
for i in 0..nq {
@@ -161,11 +199,7 @@ impl<'a> fmt::Display for VerticalRenderer<'a> {
} else if i == *c1 {
line[center] = sym_c;
} else if i == *c2 {
- if is_cswap {
- line[center] = sym_t;
- } else {
- line[center] = sym_c;
- }
+ line[center] = if is_cswap { sym_t } else { sym_c };
} else if i == *t {
line[center] = sym_t;
}
@@ -186,31 +220,25 @@ impl<'a> fmt::Display for VerticalRenderer<'a> {
let gate_line: String = line.into_iter().collect();
writeln!(f, "{}", gate_line)?;
- continue;
}
GateOp::Measure(mq, mc) => {
- let q_total = nq * col_width + (nq - 1);
- let c_total = if nc > 0 { nc * col_width + (nc - 1) } else { 0 };
- let total_width = q_total + gap_width + c_total;
-
let mut line: Vec<char> = vec![' '; total_width];
for i in 0..nq {
- let center = i * (col_width + 1) + col_width / 2;
+ let col_start = i * (col_width + 1);
+ let center = col_start + col_width / 2;
if i < *mq {
line[center] = '│';
} else if i == *mq {
- let start = i * (col_width + 1);
- let chars: Vec<char> = "[M]".chars().collect();
- for (j, ch) in chars.iter().enumerate() {
- if start + j + 1 < total_width {
- line[start + j + 1] = *ch;
- }
+ let label_start = col_start + (col_width - label.chars().count()) / 2;
+ for (j, ch) in label.chars().enumerate() {
+ line[label_start + j] = ch;
}
}
}
- let mq_center = *mq * (col_width + 1) + col_width / 2;
+ let mq_col_start = *mq * (col_width + 1);
+ let mq_center = mq_col_start + col_width / 2;
let mc_start = q_total + gap_width;
let mc_center = mc_start + *mc * (col_width + 1) + col_width / 2;
@@ -221,36 +249,77 @@ impl<'a> fmt::Display for VerticalRenderer<'a> {
}
line[mc_center] = '╣';
- if nc > 0 {
- for i in 0..nc {
- let center = mc_start + i * (col_width + 1) + col_width / 2;
- if i > *mc {
- line[center] = '║';
- }
+ for i in 0..nc {
+ let center = mc_start + i * (col_width + 1) + col_width / 2;
+ if i > *mc {
+ line[center] = '║';
}
}
let measure_line: String = line.into_iter().collect();
writeln!(f, "{}", measure_line)?;
- continue;
}
- }
+ GateOp::Custom(_, targets) => {
+ let mut line: Vec<char> = vec![' '; total_width];
+
+ if targets.len() == 1 {
+ for i in 0..nq {
+ let col_start = i * (col_width + 1);
+ let center = col_start + col_width / 2;
+ if i == targets[0] {
+ let label_start =
+ col_start + (col_width - label.chars().count()) / 2;
+ for (j, ch) in label.chars().enumerate() {
+ line[label_start + j] = ch;
+ }
+ } else {
+ line[center] = '│';
+ }
+ }
+
+ for i in 0..nc {
+ let center = q_total + gap_width + i * (col_width + 1) + col_width / 2;
+ line[center] = '║';
+ }
+ } else {
+ for i in 0..nq {
+ let col_start = i * (col_width + 1);
+ let center = col_start + col_width / 2;
+ if i < min_q || i > max_q {
+ line[center] = '│';
+ } else if i == targets[0] {
+ let label_start =
+ col_start + (col_width - label.chars().count()) / 2;
+ for (j, ch) in label.chars().enumerate() {
+ line[label_start + j] = ch;
+ }
+ } else if targets.contains(&i) {
+ line[center] = '□';
+ }
+ }
+
+ let min_center = min_q * (col_width + 1) + col_width / 2;
+ let max_center = max_q * (col_width + 1) + col_width / 2;
+ for pos in (min_center + 1)..max_center {
+ if line[pos] == ' ' {
+ line[pos] = '─';
+ }
+ }
+
+ for i in 0..nc {
+ let center = q_total + gap_width + i * (col_width + 1) + col_width / 2;
+ line[center] = '║';
+ }
+ }
- let q_row: String = q_cols.join(" ");
- let c_row: String = c_cols.join(" ");
- if nc > 0 {
- writeln!(f, "{}{}{}", q_row, " ".repeat(gap_width), c_row)?;
- } else {
- writeln!(f, "{}", q_row)?;
+ let gate_line: String = line.into_iter().collect();
+ writeln!(f, "{}", gate_line)?;
+ }
}
}
writeln!(f, "{}", full_wires)?;
- let q_total = nq * col_width + (nq - 1);
- let c_total = if nc > 0 { nc * col_width + (nc - 1) } else { 0 };
- let total_width = q_total + gap_width + c_total;
-
let end_line: String = "░".repeat(total_width);
writeln!(f, "{}", end_line)?;