From edc6bae7bf5039c8cef2f2c9c164187478b1c7eb Mon Sep 17 00:00:00 2001 From: hachem Date: Sat, 6 Dec 2025 21:52:43 +0100 Subject: [add]: complete vertical renderer --- libpsi-visualizer/src/cli/horizontal_cli.rs | 19 +- libpsi-visualizer/src/cli/mod.rs | 5 + libpsi-visualizer/src/cli/vertical_cli.rs | 258 ++++++++++++++++++++++++++++ libpsi-visualizer/src/cli/visualizer.rs | 3 + tester/src/main.rs | 18 ++ 5 files changed, 297 insertions(+), 6 deletions(-) create mode 100644 libpsi-visualizer/src/cli/visualizer.rs diff --git a/libpsi-visualizer/src/cli/horizontal_cli.rs b/libpsi-visualizer/src/cli/horizontal_cli.rs index b1b851b..067e23d 100644 --- a/libpsi-visualizer/src/cli/horizontal_cli.rs +++ b/libpsi-visualizer/src/cli/horizontal_cli.rs @@ -1,3 +1,4 @@ +use super::visualizer::Visualizer; use core::fmt; use libpsi_core::{GateOp, QuantumCircuit}; @@ -11,6 +12,12 @@ impl<'a> HorizontalRenderer<'a> { } } +impl<'a> Visualizer for HorizontalRenderer<'a> { + fn export(&self) -> String { + format!("{}", self) + } +} + impl<'a> fmt::Display for HorizontalRenderer<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let nq = self.circuit.num_qubits(); @@ -41,12 +48,12 @@ impl<'a> fmt::Display for HorizontalRenderer<'a> { if ops.is_empty() { for line in &q_lines { - writeln!(f, "{}───", line)?; + writeln!(f, "{}───░", line)?; } if nc > 0 { - writeln!(f, "{} ", gap_line)?; + writeln!(f, "{} ░", gap_line)?; for line in &c_lines { - writeln!(f, "{}═══", line)?; + writeln!(f, "{}═══░", line)?; } } return Ok(()); @@ -243,12 +250,12 @@ impl<'a> fmt::Display for HorizontalRenderer<'a> { } for line in &q_lines { - writeln!(f, "{}", line)?; + writeln!(f, "{}░", line)?; } if nc > 0 { - writeln!(f, "{}", gap_line)?; + writeln!(f, "{}░", gap_line)?; for line in &c_lines { - writeln!(f, "{}", line)?; + writeln!(f, "{}░", line)?; } } diff --git a/libpsi-visualizer/src/cli/mod.rs b/libpsi-visualizer/src/cli/mod.rs index fe10249..213311b 100644 --- a/libpsi-visualizer/src/cli/mod.rs +++ b/libpsi-visualizer/src/cli/mod.rs @@ -1,2 +1,7 @@ pub mod horizontal_cli; +pub mod vertical_cli; +pub mod visualizer; + pub use horizontal_cli::*; +pub use vertical_cli::*; +pub use visualizer::*; diff --git a/libpsi-visualizer/src/cli/vertical_cli.rs b/libpsi-visualizer/src/cli/vertical_cli.rs index 8b13789..de0ee89 100644 --- a/libpsi-visualizer/src/cli/vertical_cli.rs +++ b/libpsi-visualizer/src/cli/vertical_cli.rs @@ -1 +1,259 @@ +use super::visualizer::Visualizer; +use core::fmt; +use libpsi_core::{GateOp, QuantumCircuit}; +pub struct VerticalRenderer<'a> { + circuit: &'a QuantumCircuit, +} + +impl<'a> VerticalRenderer<'a> { + pub fn new(circuit: &'a QuantumCircuit) -> Self { + VerticalRenderer { circuit } + } +} + +impl<'a> Visualizer for VerticalRenderer<'a> { + fn export(&self) -> String { + format!("{}", self) + } +} + +impl<'a> fmt::Display for VerticalRenderer<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let nq = self.circuit.num_qubits(); + let nc = self.circuit.num_classical(); + let ops = self.circuit.operations(); + + let col_width = 5; + let gap_width = 3; + + let q_header: String = (0..nq) + .map(|i| format!("{:^width$}", format!("q{}", i), width = col_width)) + .collect::>() + .join(" "); + + let c_header: String = (0..nc) + .map(|i| format!("{:^width$}", format!("c{}", i), width = col_width)) + .collect::>() + .join(" "); + + if nc > 0 { + writeln!(f, "{}{}{}", q_header, " ".repeat(gap_width), c_header)?; + } else { + writeln!(f, "{}", q_header)?; + } + + let q_wires: String = (0..nq) + .map(|_| format!("{:^width$}", "│", width = col_width)) + .collect::>() + .join(" "); + + let c_wires: String = (0..nc) + .map(|_| format!("{:^width$}", "║", width = col_width)) + .collect::>() + .join(" "); + + let full_wires = if nc > 0 { + format!("{}{}{}", q_wires, " ".repeat(gap_width), c_wires) + } else { + q_wires.clone() + }; + + if ops.is_empty() { + writeln!(f, "{}", full_wires)?; + return Ok(()); + } + + for op in ops { + writeln!(f, "{}", full_wires)?; + + let q_targets = op.quantum_targets(); + 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 = (0..nq) + .map(|_| format!("{:^width$}", "│", width = col_width)) + .collect(); + + let c_cols: Vec = (0..nc) + .map(|_| format!("{:^width$}", "║", width = col_width)) + .collect(); + + 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::CNOT(c, t) | GateOp::CZ(c, t) | GateOp::SWAP(c, t) => { + let (sym1, sym2) = match op { + 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 = vec![' '; total_width]; + + for i in 0..nq { + let center = i * (col_width + 1) + col_width / 2; + if i < min_q || i > max_q { + line[center] = '│'; + } else if i == *c { + line[center] = sym1.chars().next().unwrap(); + } else if i == *t { + line[center] = sym2.chars().next().unwrap(); + } + } + + 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 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 { + GateOp::CCNOT(_, _, _) => ('●', '⊕'), + GateOp::CSWAP(_, _, _) => ('●', '╳'), + _ => unreachable!(), + }; + 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 = vec![' '; total_width]; + + for i in 0..nq { + let center = i * (col_width + 1) + col_width / 2; + if i < min_q || i > max_q { + line[center] = '│'; + } else if i == *c1 { + line[center] = sym_c; + } else if i == *c2 { + if is_cswap { + line[center] = sym_t; + } else { + line[center] = sym_c; + } + } else if i == *t { + line[center] = sym_t; + } + } + + 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 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 = vec![' '; total_width]; + + for i in 0..nq { + let center = i * (col_width + 1) + col_width / 2; + if i < *mq { + line[center] = '│'; + } else if i == *mq { + let start = i * (col_width + 1); + let chars: Vec = "[M]".chars().collect(); + for (j, ch) in chars.iter().enumerate() { + if start + j + 1 < total_width { + line[start + j + 1] = *ch; + } + } + } + } + + let mq_center = *mq * (col_width + 1) + col_width / 2; + let mc_start = q_total + gap_width; + let mc_center = mc_start + *mc * (col_width + 1) + col_width / 2; + + for pos in (mq_center + 2)..=mc_center { + if line[pos] == ' ' { + line[pos] = '═'; + } + } + 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] = '║'; + } + } + } + + let measure_line: String = line.into_iter().collect(); + writeln!(f, "{}", measure_line)?; + continue; + } + } + + 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)?; + } + } + + 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)?; + + Ok(()) + } +} diff --git a/libpsi-visualizer/src/cli/visualizer.rs b/libpsi-visualizer/src/cli/visualizer.rs new file mode 100644 index 0000000..33f730f --- /dev/null +++ b/libpsi-visualizer/src/cli/visualizer.rs @@ -0,0 +1,3 @@ +pub trait Visualizer { + fn export(&self) -> String; +} diff --git a/tester/src/main.rs b/tester/src/main.rs index 497e286..ab4c78c 100644 --- a/tester/src/main.rs +++ b/tester/src/main.rs @@ -6,7 +6,10 @@ fn main() { let mut bell = QuantumCircuit::with_classical(2, 2); bell.h(0).cnot(0, 1).measure(0, 0).measure(1, 1); + println!("Horizontal:"); println!("{}", HorizontalRenderer::new(&bell)); + println!("Vertical:"); + println!("{}", VerticalRenderer::new(&bell)); bell.compute(); println!("{}", bell); @@ -17,7 +20,10 @@ fn main() { let mut ghz = QuantumCircuit::new(3); ghz.h(0).cnot(0, 1).cnot(0, 2); + println!("Horizontal:"); println!("{}", HorizontalRenderer::new(&ghz)); + println!("Vertical:"); + println!("{}", VerticalRenderer::new(&ghz)); ghz.compute(); println!("{}", ghz); @@ -28,7 +34,10 @@ fn main() { let mut swap_circuit = QuantumCircuit::new(2); swap_circuit.x(0).cnot(0, 1).cnot(1, 0).cnot(0, 1); + println!("Horizontal:"); println!("{}", HorizontalRenderer::new(&swap_circuit)); + println!("Vertical:"); + println!("{}", VerticalRenderer::new(&swap_circuit)); swap_circuit.compute(); println!("{}", swap_circuit); @@ -39,7 +48,10 @@ fn main() { let mut toffoli_circuit = QuantumCircuit::new(3); toffoli_circuit.x(0).x(1).toffoli(0, 1, 2); + println!("Horizontal:"); println!("{}", HorizontalRenderer::new(&toffoli_circuit)); + println!("Vertical:"); + println!("{}", VerticalRenderer::new(&toffoli_circuit)); toffoli_circuit.compute(); println!("{}", toffoli_circuit); @@ -50,7 +62,10 @@ fn main() { let mut full = QuantumCircuit::with_classical(3, 3); full.h(0).h(1).h(2).measure_all(); + println!("Horizontal:"); println!("{}", HorizontalRenderer::new(&full)); + println!("Vertical:"); + println!("{}", VerticalRenderer::new(&full)); full.compute(); println!("{}", full); @@ -69,7 +84,10 @@ fn main() { .measure(0, 0) .measure(1, 1); + println!("Horizontal:"); println!("{}", HorizontalRenderer::new(&complex)); + println!("Vertical:"); + println!("{}", VerticalRenderer::new(&complex)); complex.compute(); println!("{}", complex); -- cgit v1.3