diff options
| author | hachem <im@hachem.wtf> | 2025-12-06 21:52:43 +0100 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2025-12-06 21:52:43 +0100 |
| commit | edc6bae7bf5039c8cef2f2c9c164187478b1c7eb (patch) | |
| tree | d190762eaa2838c11938f3a4f65ad05463d190a6 /libpsi-visualizer/src | |
| parent | 609f7276fc1fb484be34b232d35426f91ddb9b71 (diff) | |
[add]: complete vertical renderer
Diffstat (limited to 'libpsi-visualizer/src')
| -rw-r--r-- | libpsi-visualizer/src/cli/horizontal_cli.rs | 19 | ||||
| -rw-r--r-- | libpsi-visualizer/src/cli/mod.rs | 5 | ||||
| -rw-r--r-- | libpsi-visualizer/src/cli/vertical_cli.rs | 258 | ||||
| -rw-r--r-- | libpsi-visualizer/src/cli/visualizer.rs | 3 |
4 files changed, 279 insertions, 6 deletions
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::<Vec<_>>() + .join(" "); + + let c_header: String = (0..nc) + .map(|i| format!("{:^width$}", format!("c{}", i), width = col_width)) + .collect::<Vec<_>>() + .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::<Vec<_>>() + .join(" "); + + let c_wires: String = (0..nc) + .map(|_| format!("{:^width$}", "║", width = col_width)) + .collect::<Vec<_>>() + .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<String> = (0..nq) + .map(|_| format!("{:^width$}", "│", width = col_width)) + .collect(); + + let c_cols: Vec<String> = (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<char> = 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<char> = 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<char> = 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<char> = "[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; +} |
