diff options
| author | hachem <im@hachem.wtf> | 2025-12-06 21:20:43 +0100 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2025-12-06 21:20:43 +0100 |
| commit | 609f7276fc1fb484be34b232d35426f91ddb9b71 (patch) | |
| tree | 8cfc9a68ac184cb01111ddbef1f3a77b0b62b91f | |
| parent | 1fef726728a14cc923c7cbaf80d337b4eb35db3c (diff) | |
[add]: complete horizonal renderer
| -rw-r--r-- | README.md | 4 | ||||
| -rw-r--r-- | libpsi-core/src/core/circuit.rs | 308 | ||||
| -rw-r--r-- | libpsi-visualizer/src/cli/horizontal_cli.rs | 279 | ||||
| -rw-r--r-- | libpsi-visualizer/src/cli/mod.rs | 2 | ||||
| -rw-r--r-- | libpsi-visualizer/src/cli/renderer.rs | 312 | ||||
| -rw-r--r-- | libpsi-visualizer/src/cli/vertical_cli.rs | 26 | ||||
| -rw-r--r-- | libpsi-visualizer/src/lib.rs | 11 | ||||
| -rw-r--r-- | tester/src/main.rs | 92 |
8 files changed, 474 insertions, 560 deletions
@@ -3,7 +3,7 @@ $\psi$ is a powerful quantum computing toolkit designed for simulating quantum c > :warning: **Warning: Work in Progress** -## Project Prospects +## About - **`libpsi-core`**: A core library for writing and designing quantum circuits, used across all $\psi$ sub-projects. - **`libpsi-core:runtime`**: A set of four runtimes for executing quantum circuits: - **`BasicRuntime`**: A single-threaded runtime that executes a quantum circuit statistically, running it $n$ times. @@ -15,8 +15,6 @@ $\psi$ is a powerful quantum computing toolkit designed for simulating quantum c - **`libpsi-core:core`**: Contains all core quantum components, including quantum gates, classical/quantum bits, and quantum circuits. - **`libpsi-visualizer`**: An extension of `libpsi-core` that offers visual representations of quantum circuits. It supports both text-based (ASCII) output in the terminal and graphical output using APIs like OpenGL and Vulkan. - **`libpsi-qasmc`**: An [OpenQASM](https://openqasm.com/) compiler that enables you to write quantum programs, which are then compiled into native executables for classical computers using an [LLVM](https://llvm.org/) backend. -- **`psi`**: A C-like programming language that integrates quantum and classical computing, making it easier to build and simulate quantum systems alongside classical ones. -- **`psi-gui`**: A graphical interface for designing quantum circuits, allowing you to create circuits visually rather than through code. It integrates `libpsi` and `psi`. ## Disclaimer This project is a large and ongoing effort, and I try my hardest to deliver the advertised feature, some may not arrive as planned or according to any scheduled timeline. The development process is subject to change based on technical challenges, research priorities and the simultaneous management of multiple on-going projects, spanning both computer science, physics and unrelated domains. diff --git a/libpsi-core/src/core/circuit.rs b/libpsi-core/src/core/circuit.rs index eb64aaf..f938ef1 100644 --- a/libpsi-core/src/core/circuit.rs +++ b/libpsi-core/src/core/circuit.rs @@ -1,122 +1,183 @@ -use super::{QuantumGate, QuantumRegister, QuantumState}; +use super::{QuantumRegister, QuantumState}; use crate::{format_amplitude, format_probability, Vector}; use core::fmt; -#[derive(Clone)] -pub struct CircuitOperation<'a> { - pub gate: &'a QuantumGate<'a>, - pub targets: Vec<usize>, +#[derive(Clone, Copy)] +pub enum GateOp { + H(usize), + X(usize), + Y(usize), + Z(usize), + S(usize), + T(usize), + CNOT(usize, usize), + CZ(usize, usize), + SWAP(usize, usize), + CCNOT(usize, usize, usize), + CSWAP(usize, usize, usize), + Measure(usize, usize), } -impl<'a> CircuitOperation<'a> { - pub fn new(gate: &'a QuantumGate<'a>, targets: Vec<usize>) -> Self { - CircuitOperation { gate, targets } +impl GateOp { + pub fn name(&self) -> &'static str { + match self { + GateOp::H(_) => "H", + GateOp::X(_) => "X", + GateOp::Y(_) => "Y", + GateOp::Z(_) => "Z", + GateOp::S(_) => "S", + GateOp::T(_) => "T", + GateOp::CNOT(_, _) => "CNOT", + GateOp::CZ(_, _) => "CZ", + GateOp::SWAP(_, _) => "SWAP", + GateOp::CCNOT(_, _, _) => "CCNOT", + GateOp::CSWAP(_, _, _) => "CSWAP", + GateOp::Measure(_, _) => "M", + } + } + + pub fn quantum_targets(&self) -> Vec<usize> { + match self { + GateOp::H(t) | GateOp::X(t) | GateOp::Y(t) | GateOp::Z(t) | GateOp::S(t) | GateOp::T(t) => vec![*t], + GateOp::CNOT(c, t) | GateOp::CZ(c, t) | GateOp::SWAP(c, t) => vec![*c, *t], + GateOp::CCNOT(c1, c2, t) | GateOp::CSWAP(c1, c2, t) => vec![*c1, *c2, *t], + GateOp::Measure(q, _) => vec![*q], + } + } + + pub fn classical_targets(&self) -> Vec<usize> { + match self { + GateOp::Measure(_, c) => vec![*c], + _ => vec![], + } } -} -pub struct QuantumCircuit<'a> { - register: QuantumRegister<'a>, - operations: Vec<CircuitOperation<'a>>, + pub fn is_measurement(&self) -> bool { + matches!(self, GateOp::Measure(_, _)) + } } -impl<'a> QuantumCircuit<'a> { - pub fn new(num_qubits: usize) -> QuantumCircuit<'a> { - let names: Vec<String> = (0..num_qubits).map(|i| format!("q{}", i)).collect(); - let leaked_names: &'a [String] = Box::leak(names.into_boxed_slice()); - let name_refs: Vec<&'a str> = leaked_names.iter().map(|s| s.as_str()).collect(); +pub struct QuantumCircuit { + num_qubits: usize, + num_classical: usize, + operations: Vec<GateOp>, + computed_state: Option<QuantumState>, +} +impl QuantumCircuit { + pub fn new(num_qubits: usize) -> QuantumCircuit { QuantumCircuit { - register: QuantumRegister::new( - Box::leak(Box::new("circuit".to_string())).as_str(), - &name_refs, - ), + num_qubits, + num_classical: 0, operations: Vec::new(), + computed_state: None, } } - pub fn from_register(register: QuantumRegister<'a>) -> QuantumCircuit<'a> { + pub fn with_classical(num_qubits: usize, num_classical: usize) -> QuantumCircuit { QuantumCircuit { - register, + num_qubits, + num_classical, operations: Vec::new(), + computed_state: None, } } pub fn num_qubits(&self) -> usize { - self.register.num_qubits() + self.num_qubits } - pub fn state(&self) -> QuantumState { - self.register.get_state() + pub fn num_classical(&self) -> usize { + self.num_classical } - pub fn register(&self) -> &QuantumRegister<'a> { - &self.register + pub fn operations(&self) -> &[GateOp] { + &self.operations } - pub fn operations(&self) -> &[CircuitOperation<'a>] { - &self.operations + pub fn is_computed(&self) -> bool { + self.computed_state.is_some() } - pub fn apply(&mut self, gate: &'a QuantumGate<'a>, targets: &[usize]) -> &mut Self { - self.register.apply_gate(gate, targets); - self.operations - .push(CircuitOperation::new(gate, targets.to_vec())); - self + pub fn compute(&mut self) -> &QuantumState { + if self.computed_state.is_some() { + return self.computed_state.as_ref().unwrap(); + } + + let names: Vec<String> = (0..self.num_qubits).map(|i| format!("q{}", i)).collect(); + let leaked_names: &'static [String] = Box::leak(names.into_boxed_slice()); + let name_refs: Vec<&'static str> = leaked_names.iter().map(|s| s.as_str()).collect(); + + let mut register = QuantumRegister::new( + Box::leak(Box::new("circuit".to_string())).as_str(), + &name_refs, + ); + + use crate::gates::*; + for op in &self.operations { + match op { + GateOp::H(t) => register.apply_gate(&HADAMARD, &[*t]), + GateOp::X(t) => register.apply_gate(&PAULI_X, &[*t]), + GateOp::Y(t) => register.apply_gate(&PAULI_Y, &[*t]), + GateOp::Z(t) => register.apply_gate(&PAULI_Z, &[*t]), + GateOp::S(t) => register.apply_gate(&S_GATE, &[*t]), + GateOp::T(t) => register.apply_gate(&T_GATE, &[*t]), + GateOp::CNOT(c, t) => register.apply_gate(&CNOT, &[*c, *t]), + GateOp::CZ(c, t) => register.apply_gate(&CZ, &[*c, *t]), + GateOp::SWAP(a, b) => register.apply_gate(&SWAP, &[*a, *b]), + GateOp::CCNOT(c1, c2, t) => register.apply_gate(&TOFFOLI, &[*c1, *c2, *t]), + GateOp::CSWAP(c, t1, t2) => register.apply_gate(&FREDKIN, &[*c, *t1, *t2]), + GateOp::Measure(_, _) => {} + } + } + + self.computed_state = Some(register.get_state()); + self.computed_state.as_ref().unwrap() + } + + pub fn state(&mut self) -> &QuantumState { + self.compute() } pub fn h(&mut self, target: usize) -> &mut Self { - use crate::gates::HADAMARD; - self.register.apply_gate(&HADAMARD, &[target]); - self.operations - .push(CircuitOperation::new(&HADAMARD, vec![target])); + self.operations.push(GateOp::H(target)); + self.computed_state = None; self } pub fn x(&mut self, target: usize) -> &mut Self { - use crate::gates::PAULI_X; - self.register.apply_gate(&PAULI_X, &[target]); - self.operations - .push(CircuitOperation::new(&PAULI_X, vec![target])); + self.operations.push(GateOp::X(target)); + self.computed_state = None; self } pub fn y(&mut self, target: usize) -> &mut Self { - use crate::gates::PAULI_Y; - self.register.apply_gate(&PAULI_Y, &[target]); - self.operations - .push(CircuitOperation::new(&PAULI_Y, vec![target])); + self.operations.push(GateOp::Y(target)); + self.computed_state = None; self } pub fn z(&mut self, target: usize) -> &mut Self { - use crate::gates::PAULI_Z; - self.register.apply_gate(&PAULI_Z, &[target]); - self.operations - .push(CircuitOperation::new(&PAULI_Z, vec![target])); + self.operations.push(GateOp::Z(target)); + self.computed_state = None; self } pub fn s(&mut self, target: usize) -> &mut Self { - use crate::gates::S_GATE; - self.register.apply_gate(&S_GATE, &[target]); - self.operations - .push(CircuitOperation::new(&S_GATE, vec![target])); + self.operations.push(GateOp::S(target)); + self.computed_state = None; self } pub fn t(&mut self, target: usize) -> &mut Self { - use crate::gates::T_GATE; - self.register.apply_gate(&T_GATE, &[target]); - self.operations - .push(CircuitOperation::new(&T_GATE, vec![target])); + self.operations.push(GateOp::T(target)); + self.computed_state = None; self } pub fn cnot(&mut self, control: usize, target: usize) -> &mut Self { - use crate::gates::CNOT; - self.register.apply_gate(&CNOT, &[control, target]); - self.operations - .push(CircuitOperation::new(&CNOT, vec![control, target])); + self.operations.push(GateOp::CNOT(control, target)); + self.computed_state = None; self } @@ -125,29 +186,20 @@ impl<'a> QuantumCircuit<'a> { } pub fn cz(&mut self, control: usize, target: usize) -> &mut Self { - use crate::gates::CZ; - self.register.apply_gate(&CZ, &[control, target]); - self.operations - .push(CircuitOperation::new(&CZ, vec![control, target])); + self.operations.push(GateOp::CZ(control, target)); + self.computed_state = None; self } pub fn swap(&mut self, qubit1: usize, qubit2: usize) -> &mut Self { - use crate::gates::SWAP; - self.register.apply_gate(&SWAP, &[qubit1, qubit2]); - self.operations - .push(CircuitOperation::new(&SWAP, vec![qubit1, qubit2])); + self.operations.push(GateOp::SWAP(qubit1, qubit2)); + self.computed_state = None; self } pub fn ccnot(&mut self, control1: usize, control2: usize, target: usize) -> &mut Self { - use crate::gates::TOFFOLI; - self.register - .apply_gate(&TOFFOLI, &[control1, control2, target]); - self.operations.push(CircuitOperation::new( - &TOFFOLI, - vec![control1, control2, target], - )); + self.operations.push(GateOp::CCNOT(control1, control2, target)); + self.computed_state = None; self } @@ -156,13 +208,8 @@ impl<'a> QuantumCircuit<'a> { } pub fn cswap(&mut self, control: usize, target1: usize, target2: usize) -> &mut Self { - use crate::gates::FREDKIN; - self.register - .apply_gate(&FREDKIN, &[control, target1, target2]); - self.operations.push(CircuitOperation::new( - &FREDKIN, - vec![control, target1, target2], - )); + self.operations.push(GateOp::CSWAP(control, target1, target2)); + self.computed_state = None; self } @@ -170,58 +217,44 @@ impl<'a> QuantumCircuit<'a> { self.cswap(control, target1, target2) } - pub fn reset(&mut self) -> &mut Self { - let n = self.num_qubits(); - let names: Vec<String> = (0..n).map(|i| format!("q{}", i)).collect(); - let leaked_names: &'a [String] = Box::leak(names.into_boxed_slice()); - let name_refs: Vec<&'a str> = leaked_names.iter().map(|s| s.as_str()).collect(); + pub fn measure(&mut self, qubit: usize, classical: usize) -> &mut Self { + if classical >= self.num_classical { + self.num_classical = classical + 1; + } + self.operations.push(GateOp::Measure(qubit, classical)); + self + } - self.register = QuantumRegister::new( - Box::leak(Box::new("circuit".to_string())).as_str(), - &name_refs, - ); + pub fn measure_all(&mut self) -> &mut Self { + for i in 0..self.num_qubits { + self.measure(i, i); + } + self + } + + pub fn reset(&mut self) -> &mut Self { self.operations.clear(); + self.computed_state = None; self } - pub fn probability(&self, state_index: usize) -> f64 { - let state = self.state(); + pub fn probability(&mut self, state_index: usize) -> f64 { + self.compute(); + let state = self.computed_state.as_ref().unwrap(); let amp = state.get(state_index); amp.norm2() } - pub fn probabilities(&self) -> Vec<f64> { - let state = self.state(); - let n = 1 << self.num_qubits(); + pub fn probabilities(&mut self) -> Vec<f64> { + self.compute(); + let n = 1 << self.num_qubits; + let state = self.computed_state.as_ref().unwrap(); (0..n).map(|i| state.get(i).norm2()).collect() } -} -impl<'a> fmt::Display for QuantumCircuit<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "QuantumCircuit ({} qubits)", self.num_qubits())?; - writeln!(f, "Operations:")?; - for (i, op) in self.operations.iter().enumerate() { - writeln!(f, " {}: {} on {:?}", i, op.gate.name, op.targets)?; - } - writeln!(f, "State:")?; - let state = self.state(); - let n = 1 << self.num_qubits(); - for i in 0..n { - let amp = state.get(i); - if amp.real.abs() > 1e-10 || amp.imaginary.abs() > 1e-10 { - let basis: String = format!("{:0width$b}", i, width = self.num_qubits()); - writeln!(f, " |{}⟩: {}", basis, format_amplitude(&))?; - } - } - Ok(()) - } -} - -impl<'a> QuantumCircuit<'a> { - pub fn print_probabilities(&self) { + pub fn print_probabilities(&mut self) { let probs = self.probabilities(); - let n = self.num_qubits(); + let n = self.num_qubits; println!("Probabilities:"); for (i, p) in probs.iter().enumerate() { if *p > 1e-10 { @@ -231,3 +264,30 @@ impl<'a> QuantumCircuit<'a> { } } } + +impl fmt::Display for QuantumCircuit { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "QuantumCircuit ({} qubits, {} classical)", self.num_qubits, self.num_classical)?; + writeln!(f, "Operations:")?; + for (i, op) in self.operations.iter().enumerate() { + match op { + GateOp::Measure(q, c) => writeln!(f, " {}: {} q{} → c{}", i, op.name(), q, c)?, + _ => writeln!(f, " {}: {} on {:?}", i, op.name(), op.quantum_targets())?, + } + } + if let Some(state) = &self.computed_state { + writeln!(f, "State:")?; + let n = 1 << self.num_qubits; + for i in 0..n { + let amp = state.get(i); + if amp.real.abs() > 1e-10 || amp.imaginary.abs() > 1e-10 { + let basis: String = format!("{:0width$b}", i, width = self.num_qubits); + writeln!(f, " |{}⟩: {}", basis, format_amplitude(&))?; + } + } + } else { + writeln!(f, "State: (not computed)")?; + } + Ok(()) + } +} diff --git a/libpsi-visualizer/src/cli/horizontal_cli.rs b/libpsi-visualizer/src/cli/horizontal_cli.rs index 3f02c59..b1b851b 100644 --- a/libpsi-visualizer/src/cli/horizontal_cli.rs +++ b/libpsi-visualizer/src/cli/horizontal_cli.rs @@ -1,44 +1,257 @@ -/* - NOTE(Hachem): This is what I would like the output to be like - - ┌───┐ ┌───────┐ ┌─────────┐ - |q0>: ─────┤ H ├────┤ C-NOT ├────┤ MEASURE ├───────────────────▓ - └───┘ └───┬───┘ └────┬────┘ - │ │ ┌─────────┐ - |q1>: ──────────────────■─────────────┼─────────┤ MEASURE ├────▓ - │ └────┬────┘ - │ │ - c0: ════════════════════════════════■══════════════╪═════════▓ - c1: ═══════════════════════════════════════════════■═════════▓ -*/ - use core::fmt; +use libpsi_core::{GateOp, QuantumCircuit}; -use crate::{renderer::*, Visualizer}; -use libpsi_core::QuantumCircuit; - -#[allow(unused)] -pub struct HorizontalCLIVisualizer<'a> { - circuit: &'a QuantumCircuit<'a>, - renderer: CLIRenderer, +pub struct HorizontalRenderer<'a> { + circuit: &'a QuantumCircuit, } -impl<'a> fmt::Display for HorizontalCLIVisualizer<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.renderer.to_string()) +impl<'a> HorizontalRenderer<'a> { + pub fn new(circuit: &'a QuantumCircuit) -> Self { + HorizontalRenderer { circuit } } } -impl<'a> Visualizer<'a> for HorizontalCLIVisualizer<'a> { - fn new(circuit: &'a QuantumCircuit) -> HorizontalCLIVisualizer<'a> { - let terminal_width = term_size::dimensions().unwrap().0; - HorizontalCLIVisualizer { - circuit, - renderer: CLIRenderer::new(terminal_width), +impl<'a> fmt::Display for HorizontalRenderer<'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 mut q_lines: Vec<String> = (0..nq).map(|i| format!("q{}: ", i)).collect(); + let mut c_lines: Vec<String> = (0..nc).map(|i| format!("c{}: ", i)).collect(); + + let max_label = q_lines + .iter() + .chain(c_lines.iter()) + .map(|s| s.len()) + .max() + .unwrap_or(3); + + for line in &mut q_lines { + while line.len() < max_label { + line.insert(0, ' '); + } + } + for line in &mut c_lines { + while line.len() < max_label { + line.insert(0, ' '); + } + } + let mut gap_line = " ".repeat(max_label); + + if ops.is_empty() { + for line in &q_lines { + writeln!(f, "{}───", line)?; + } + if nc > 0 { + writeln!(f, "{} ", gap_line)?; + for line in &c_lines { + writeln!(f, "{}═══", line)?; + } + } + return Ok(()); + } + + for op in ops { + 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); + + match op { + GateOp::H(t) => { + for (i, line) in q_lines.iter_mut().enumerate() { + if i == *t { + line.push_str("─[H]─"); + } else { + line.push_str("─────"); + } + } + for line in c_lines.iter_mut() { + line.push_str("═════"); + } + gap_line.push_str(" "); + } + GateOp::X(t) => { + for (i, line) in q_lines.iter_mut().enumerate() { + if i == *t { + line.push_str("─[X]─"); + } else { + line.push_str("─────"); + } + } + for line in c_lines.iter_mut() { + line.push_str("═════"); + } + gap_line.push_str(" "); + } + GateOp::Y(t) => { + for (i, line) in q_lines.iter_mut().enumerate() { + if i == *t { + line.push_str("─[Y]─"); + } else { + line.push_str("─────"); + } + } + for line in c_lines.iter_mut() { + line.push_str("═════"); + } + gap_line.push_str(" "); + } + GateOp::Z(t) => { + for (i, line) in q_lines.iter_mut().enumerate() { + if i == *t { + line.push_str("─[Z]─"); + } else { + line.push_str("─────"); + } + } + for line in c_lines.iter_mut() { + line.push_str("═════"); + } + gap_line.push_str(" "); + } + GateOp::S(t) => { + for (i, line) in q_lines.iter_mut().enumerate() { + if i == *t { + line.push_str("─[S]─"); + } else { + line.push_str("─────"); + } + } + for line in c_lines.iter_mut() { + line.push_str("═════"); + } + gap_line.push_str(" "); + } + GateOp::T(t) => { + for (i, line) in q_lines.iter_mut().enumerate() { + if i == *t { + line.push_str("─[T]─"); + } else { + line.push_str("─────"); + } + } + for line in c_lines.iter_mut() { + line.push_str("═════"); + } + gap_line.push_str(" "); + } + GateOp::CNOT(c, t) => { + for (i, line) in q_lines.iter_mut().enumerate() { + if i == *c { + line.push_str("──●──"); + } else if i == *t { + line.push_str("──⊕──"); + } else if i > min_q && i < max_q { + line.push_str("──│──"); + } else { + line.push_str("─────"); + } + } + for line in c_lines.iter_mut() { + line.push_str("═════"); + } + gap_line.push_str(" "); + } + GateOp::CZ(c, t) => { + for (i, line) in q_lines.iter_mut().enumerate() { + if i == *c || i == *t { + line.push_str("──●──"); + } else if i > min_q && i < max_q { + line.push_str("──│──"); + } else { + line.push_str("─────"); + } + } + for line in c_lines.iter_mut() { + line.push_str("═════"); + } + gap_line.push_str(" "); + } + GateOp::SWAP(a, b) => { + for (i, line) in q_lines.iter_mut().enumerate() { + if i == *a || i == *b { + line.push_str("──╳──"); + } else if i > min_q && i < max_q { + line.push_str("──│──"); + } else { + line.push_str("─────"); + } + } + for line in c_lines.iter_mut() { + line.push_str("═════"); + } + gap_line.push_str(" "); + } + GateOp::CCNOT(c1, c2, t) => { + for (i, line) in q_lines.iter_mut().enumerate() { + if i == *c1 || i == *c2 { + line.push_str("──●──"); + } else if i == *t { + line.push_str("──⊕──"); + } else if i > min_q && i < max_q { + line.push_str("──│──"); + } else { + line.push_str("─────"); + } + } + for line in c_lines.iter_mut() { + line.push_str("═════"); + } + gap_line.push_str(" "); + } + GateOp::CSWAP(c, t1, t2) => { + for (i, line) in q_lines.iter_mut().enumerate() { + if i == *c { + line.push_str("──●──"); + } else if i == *t1 || i == *t2 { + line.push_str("──╳──"); + } else if i > min_q && i < max_q { + line.push_str("──│──"); + } else { + line.push_str("─────"); + } + } + for line in c_lines.iter_mut() { + line.push_str("═════"); + } + gap_line.push_str(" "); + } + GateOp::Measure(q, c) => { + for (i, line) in q_lines.iter_mut().enumerate() { + if i == *q { + line.push_str("─[M]─"); + } else if i > *q { + line.push_str("──║──"); + } else { + line.push_str("─────"); + } + } + for (i, line) in c_lines.iter_mut().enumerate() { + if i == *c { + line.push_str("══╩══"); + } else if i < *c { + line.push_str("══║══"); + } else { + line.push_str("═════"); + } + } + gap_line.push_str(" ║ "); + } + } + } + + for line in &q_lines { + writeln!(f, "{}", line)?; + } + if nc > 0 { + writeln!(f, "{}", gap_line)?; + for line in &c_lines { + writeln!(f, "{}", line)?; + } } - } - fn render(&mut self) { - println!("{}", self); + Ok(()) } } diff --git a/libpsi-visualizer/src/cli/mod.rs b/libpsi-visualizer/src/cli/mod.rs index e1c8736..fe10249 100644 --- a/libpsi-visualizer/src/cli/mod.rs +++ b/libpsi-visualizer/src/cli/mod.rs @@ -1,4 +1,2 @@ pub mod horizontal_cli; -pub mod renderer; - pub use horizontal_cli::*; diff --git a/libpsi-visualizer/src/cli/renderer.rs b/libpsi-visualizer/src/cli/renderer.rs deleted file mode 100644 index ba5a28b..0000000 --- a/libpsi-visualizer/src/cli/renderer.rs +++ /dev/null @@ -1,312 +0,0 @@ -#[derive(Clone, Copy)] -pub struct Quad { - pub x: usize, - pub y: usize, - pub width: usize, - pub height: usize, -} - -impl Quad { - pub fn new(x: usize, y: usize, width: usize, height: usize) -> Quad { - Quad { - x, - y, - width, - height, - } - } -} - -pub struct CLIRenderer { - buffer: Vec<Vec<char>>, -} - -impl CLIRenderer { - pub fn new(width: usize) -> CLIRenderer { - CLIRenderer { - buffer: vec![vec![' '; width]; 1], - } - } - - fn expand(&mut self, x: usize, y: usize) { - if y >= self.buffer.len() { - self.buffer.resize( - y + 1, - vec![' '; self.buffer.get(0).map_or(0, |row| row.len())], - ); - } - - if x >= self.buffer[y].len() { - self.buffer[y].resize(x + 1, ' '); - } - } - - pub fn to_string(&self) -> String { - self.buffer - .clone() - .into_iter() - .map(|inner| inner.into_iter().collect::<String>()) - .collect::<Vec<String>>() - .join("\n") - } - - pub fn place(&mut self, x: usize, y: usize, character: char) { - self.expand(x, y); - self.buffer[y][x] = character; - } - - pub fn get(&mut self, x: usize, y: usize) -> char { - self.expand(x, y); - self.buffer[y][x] - } - - pub fn draw_text(&mut self, x: usize, y: usize, text: &'static str) { - self.expand(x + text.len(), y); - for i in 0..text.len() { - self.place(x + i, y, text.as_bytes()[i].into()); - } - } - - pub fn draw_vline(&mut self, x: usize, y: usize, height: usize, thick: bool) { - self.expand(x, y + height); - - for i in y..y + height { - if self.get(x, i) == '─' { - self.place(x, i, if thick { '╫' } else { '┼' }); - } else if self.get(x, i) == '═' { - self.place(x, i, if thick { '║' } else { '╫' }); - } else { - self.place(x, i, if thick { '║' } else { '│' }) - } - } - } - - pub fn draw_hline(&mut self, x: usize, y: usize, width: usize, thick: bool) { - self.expand(x + width, y); - - for i in x..x + width { - if self.get(i, y) == '│' { - self.place(i, y, if thick { '╪' } else { '┼' }); - } else if self.get(i, y) == '║' { - self.place(i, y, if thick { '║' } else { '╫' }); - } else { - self.place(i, y, if thick { '═' } else { '─' }) - } - } - } - - pub fn draw_quad(&mut self, quad: Quad, thick: bool) { - self.expand(quad.x + quad.width, quad.y + quad.height); - - for x in quad.x..quad.x + quad.width { - for y in quad.y..quad.y + quad.height { - if x == quad.x && y == quad.y { - self.place(x, y, if thick { '╔' } else { '┌' }); - } else if x == quad.x + quad.width - 1 && y == quad.y { - self.place(x, y, if thick { '╗' } else { '┐' }); - } else if x == quad.x && y == quad.y + quad.height - 1 { - self.place(x, y, if thick { '╚' } else { '└' }); - } else if x == quad.x + quad.width - 1 && y == quad.y + quad.height - 1 { - self.place(x, y, if thick { '╝' } else { '┘' }); - } else if y == quad.y || y == quad.y + quad.height - 1 { - self.draw_hline(x, y, 1, thick); - } else if x == quad.x || x == quad.x + quad.width - 1 { - self.draw_vline(x, y, 1, thick); - } - } - } - } - - pub fn connect(&mut self, quad: Quad, point: (usize, usize)) { - enum Directions { - Bottom, - BottomLeft, - BottomRight, - - Top, - TopLeft, - TopRight, - - Right, - Left, - None, - } - - let center_x = (((quad.x + quad.width) as f32 / 2.0).ceil()) as usize; - let center_y = (((quad.y + quad.height) as f32 / 2.0).ceil()) as usize; - - let point_x = point.0; - let point_y = point.1; - - let mut edge_midpoint_x = 0usize; - let mut edge_midpoint_y = 0usize; - - let line_midpoint_x = center_x; - let line_midpoint_y = point_y; - - let mut direction = Directions::None; - - if point_y > center_y && point_x == center_x { - edge_midpoint_x = center_x; - edge_midpoint_y = quad.y + quad.height - 1; - direction = Directions::Bottom; - } else if point_y > center_y && point_x < center_x { - edge_midpoint_x = center_x; - edge_midpoint_y = quad.y + quad.height - 1; - direction = Directions::BottomLeft; - } else if point_y > center_y && point_x > center_x { - edge_midpoint_x = center_x; - edge_midpoint_y = quad.y + quad.height - 1; - direction = Directions::BottomRight; - } else if point_y < center_y && point_x == center_x { - edge_midpoint_x = center_x; - edge_midpoint_y = quad.y; - direction = Directions::Top; - } else if point_y < center_y && point_x < center_x { - edge_midpoint_x = center_x; - edge_midpoint_y = quad.y; - direction = Directions::TopLeft; - } else if point_y < center_y && point_x > center_x { - edge_midpoint_x = center_x; - edge_midpoint_y = quad.y; - direction = Directions::TopRight; - } else if point_x > center_x && point_y == center_y { - edge_midpoint_x = quad.x + quad.width - 1; - edge_midpoint_y = center_y; - direction = Directions::Right; - } else if point_x < center_x && point_y == center_y { - edge_midpoint_x = quad.x; - edge_midpoint_y = center_y; - - direction = Directions::Left; - } - - match direction { - Directions::Bottom | Directions::BottomRight | Directions::BottomLeft => { - let character = self.get(edge_midpoint_x, edge_midpoint_y); - self.place( - edge_midpoint_x, - edge_midpoint_y, - if character == '─' { '┬' } else { '╤' }, - ); - } - - Directions::Top | Directions::TopRight | Directions::TopLeft => { - let character = self.get(edge_midpoint_x, edge_midpoint_y); - self.place( - edge_midpoint_x, - edge_midpoint_y, - if character == '─' { '┴' } else { '╧' }, - ); - } - - Directions::Right => { - let character = self.get(edge_midpoint_x, edge_midpoint_y); - self.place( - edge_midpoint_x, - edge_midpoint_y, - if character == '│' { '├' } else { '╟' }, - ); - } - - Directions::Left => { - let character = self.get(edge_midpoint_x, edge_midpoint_y); - self.place( - edge_midpoint_x, - edge_midpoint_y, - if character == '│' { '┤' } else { '╢' }, - ); - } - - Directions::None => {} - } - - match direction { - Directions::Top => { - self.draw_vline(edge_midpoint_x, point_y, edge_midpoint_y - point_y, false); - } - - Directions::Bottom => { - self.draw_vline( - edge_midpoint_x, - edge_midpoint_y + 1, - point_y - edge_midpoint_y - 1, - false, - ); - } - - Directions::Right => { - self.draw_hline( - edge_midpoint_x + 1, - edge_midpoint_y, - point_x - edge_midpoint_x - 1, - false, - ); - } - - Directions::Left => { - self.draw_hline( - point_x + 1, - edge_midpoint_y, - edge_midpoint_x - point_x - 1, - false, - ); - } - - Directions::BottomRight => { - self.place(line_midpoint_x, line_midpoint_y, '└'); - self.draw_hline( - line_midpoint_x + 1, - line_midpoint_y, - point_x - line_midpoint_x - 1, - false, - ); - self.draw_vline( - edge_midpoint_x, - edge_midpoint_y + 1, - point_y - edge_midpoint_y - 1, - false, - ); - } - Directions::BottomLeft => { - self.place(line_midpoint_x, line_midpoint_y, '┘'); - self.draw_hline(point_x, point_y, line_midpoint_x - point_x, false); - self.draw_vline( - edge_midpoint_x, - edge_midpoint_y + 1, - point_y - edge_midpoint_y - 1, - false, - ); - } - Directions::TopRight => { - self.place(line_midpoint_x, line_midpoint_y, '┌'); - self.draw_hline( - line_midpoint_x + 1, - line_midpoint_y, - point_x - line_midpoint_x - 1, - false, - ); - - self.draw_vline( - edge_midpoint_x, - point_y + 1, - edge_midpoint_y - point_y - 1, - false, - ); - } - Directions::TopLeft => { - self.place(line_midpoint_x, line_midpoint_y, '┐'); - self.draw_hline(point_x, point_y, line_midpoint_x - point_x, false); - self.draw_vline( - edge_midpoint_x, - point_y + 1, - edge_midpoint_y - point_y - 1, - false, - ); - } - _ => {} - } - - self.place(point_x, point_y, '■'); - } -} diff --git a/libpsi-visualizer/src/cli/vertical_cli.rs b/libpsi-visualizer/src/cli/vertical_cli.rs index cf69d0b..8b13789 100644 --- a/libpsi-visualizer/src/cli/vertical_cli.rs +++ b/libpsi-visualizer/src/cli/vertical_cli.rs @@ -1,27 +1 @@ -/* - NOTE(Hachem): This is what I would like the vertical output to be like - |q0>: |q0>: c0: c1: - │ │ ║ ║ - │ │ ║ ║ - ┌─┴─┐ │ ║ ║ - │ H │ │ ║ ║ - └─┬─┘ │ ║ ║ - │ │ ║ ║ - │ │ ║ ║ - ┌───┴───┐ │ ║ ║ - │ C-NOT ├────────■ ║ ║ - └───┬───┘ │ ║ ║ - │ │ ║ ║ - │ │ ║ ║ - ┌────┴────┐ │ ║ ║ - │ MEASURE ├───────┼───────■ ║ - └────┬────┘ │ ║ ║ - │ │ ║ ║ - │ ┌────┴────┐ ║ ║ - │ │ MEASURE ├──╫────■ - │ └────┬────┘ ║ ║ - │ │ ║ ║ - │ │ ║ ║ - ▓ ▓ ▓ ▓ -*/ diff --git a/libpsi-visualizer/src/lib.rs b/libpsi-visualizer/src/lib.rs index 11436c4..373ae6d 100644 --- a/libpsi-visualizer/src/lib.rs +++ b/libpsi-visualizer/src/lib.rs @@ -1,13 +1,2 @@ -use libpsi_core::*; -use std::fmt; - pub mod cli; pub use cli::*; - -pub trait Visualizer<'a> -where - Self: fmt::Display, -{ - fn new(circuit: &'a QuantumCircuit) -> Self; - fn render(&mut self); -} diff --git a/tester/src/main.rs b/tester/src/main.rs index 4bab72d..497e286 100644 --- a/tester/src/main.rs +++ b/tester/src/main.rs @@ -1,82 +1,76 @@ use libpsi_core::*; +use libpsi_visualizer::*; fn main() { - println!("Bell State Creation: |Φ+⟩ = (|00⟩ + |11⟩)/√2"); - println!(" Circuit: H(q0) → CNOT(q0, q1)"); - let mut bell = QuantumCircuit::new(2); - bell.h(0).cnot(0, 1); + println!("Bell State with Measurement\n"); + let mut bell = QuantumCircuit::with_classical(2, 2); + bell.h(0).cnot(0, 1).measure(0, 0).measure(1, 1); + + println!("{}", HorizontalRenderer::new(&bell)); + + bell.compute(); println!("{}", bell); print!("------\n\n"); - println!("GHZ State (3-qubit entanglement): |GHZ> = (|000⟩ + |111⟩)/√2"); - println!(" Circuit: H(q0) → CNOT(q0, q1) → CNOT(q0, q2)"); + println!("GHZ State\n"); let mut ghz = QuantumCircuit::new(3); ghz.h(0).cnot(0, 1).cnot(0, 2); + + println!("{}", HorizontalRenderer::new(&ghz)); + + ghz.compute(); println!("{}", ghz); print!("------\n\n"); - println!("SWAP via 3 CNOTs"); - println!(" Start with |10>, apply CNOT chain"); + println!("SWAP via 3 CNOTs\n"); let mut swap_circuit = QuantumCircuit::new(2); - swap_circuit - .x(0) // Set to |10⟩ - .cnot(0, 1) - .cnot(1, 0) - .cnot(0, 1); + swap_circuit.x(0).cnot(0, 1).cnot(1, 0).cnot(0, 1); + + println!("{}", HorizontalRenderer::new(&swap_circuit)); + + swap_circuit.compute(); println!("{}", swap_circuit); print!("------\n\n"); - println!("Toffoli Gate (Reversible AND)"); - println!(" CCNOT flips q2 only when q0=1 AND q1=1"); + println!("Toffoli Gate\n"); let mut toffoli_circuit = QuantumCircuit::new(3); - toffoli_circuit - .x(0) - .x(1) // Set to |110⟩ - .toffoli(0, 1, 2); - println!("{}", toffoli_circuit); + toffoli_circuit.x(0).x(1).toffoli(0, 1, 2); - print!("------\n\n"); + println!("{}", HorizontalRenderer::new(&toffoli_circuit)); - println!("Fredkin Gate (Controlled SWAP)"); - println!(" CSWAP swaps q1 and q2 only when q0=1"); - let mut fredkin_circuit = QuantumCircuit::new(3); - fredkin_circuit - .x(0) - .x(1) // Set to |110⟩ - .fredkin(0, 1, 2); - println!("{}", fredkin_circuit); + toffoli_circuit.compute(); + println!("{}", toffoli_circuit); print!("------\n\n"); - println!("6. Non-contiguous CNOT (q0 controls q2, skipping q1)"); - let mut nc_circuit = QuantumCircuit::new(3); - nc_circuit - .x(0) // |100⟩ - .cnot(0, 2); // CNOT with control=q0, target=q2 - println!("{}", nc_circuit); + println!("Full Circuit with Measurements\n"); + let mut full = QuantumCircuit::with_classical(3, 3); + full.h(0).h(1).h(2).measure_all(); - print!("------\n\n"); + println!("{}", HorizontalRenderer::new(&full)); - println!("Full Superposition (H on all qubits)"); - let mut super_circuit = QuantumCircuit::new(3); - super_circuit.h(0).h(1).h(2); - println!("{}", super_circuit); + full.compute(); + println!("{}", full); print!("------\n\n"); - println!("Probability Test"); - let mut prob_circuit = QuantumCircuit::new(2); - prob_circuit.h(0).cnot(0, 1); - prob_circuit.print_probabilities(); - println!(); + println!("Complex Circuit\n"); + let mut complex = QuantumCircuit::with_classical(4, 2); + complex + .h(0) + .h(1) + .cnot(0, 2) + .cnot(1, 3) + .cz(2, 3) + .swap(0, 1) + .measure(0, 0) + .measure(1, 1); - print!("------\n\n"); + println!("{}", HorizontalRenderer::new(&complex)); - println!("Complex Circuit with Method Chaining"); - let mut complex = QuantumCircuit::new(4); - complex.h(0).h(1).cnot(0, 2).cnot(1, 3).cz(2, 3).swap(0, 1); + complex.compute(); println!("{}", complex); } |
