diff options
| author | hachem <im@hachem.wtf> | 2025-12-06 22:33:56 +0100 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2025-12-06 22:33:56 +0100 |
| commit | 1fb7989e9405f9795a1d5b3953881b9bb8a137b4 (patch) | |
| tree | c680f041fe41e5ccd1e311ec7b1502abb96237af | |
| parent | edc6bae7bf5039c8cef2f2c9c164187478b1c7eb (diff) | |
[add]: custom gates + rework vertical renderer
| -rw-r--r-- | libpsi-core/src/core/circuit.rs | 31 | ||||
| -rw-r--r-- | libpsi-core/src/core/custom_gate.rs | 245 | ||||
| -rw-r--r-- | libpsi-core/src/core/mod.rs | 2 | ||||
| -rw-r--r-- | libpsi-core/src/lib.rs | 1 | ||||
| -rw-r--r-- | libpsi-core/src/maths/complex.rs | 48 | ||||
| -rw-r--r-- | libpsi-visualizer/src/cli/horizontal_cli.rs | 25 | ||||
| -rw-r--r-- | libpsi-visualizer/src/cli/vertical_cli.rs | 219 | ||||
| -rw-r--r-- | tester/src/main.rs | 67 |
8 files changed, 556 insertions, 82 deletions
diff --git a/libpsi-core/src/core/circuit.rs b/libpsi-core/src/core/circuit.rs index f938ef1..e797511 100644 --- a/libpsi-core/src/core/circuit.rs +++ b/libpsi-core/src/core/circuit.rs @@ -1,8 +1,9 @@ -use super::{QuantumRegister, QuantumState}; +use super::{CustomGate, QuantumRegister, QuantumState}; use crate::{format_amplitude, format_probability, Vector}; +use std::sync::Arc; use core::fmt; -#[derive(Clone, Copy)] +#[derive(Clone)] pub enum GateOp { H(usize), X(usize), @@ -16,10 +17,11 @@ pub enum GateOp { CCNOT(usize, usize, usize), CSWAP(usize, usize, usize), Measure(usize, usize), + Custom(Arc<CustomGate>, Vec<usize>), } impl GateOp { - pub fn name(&self) -> &'static str { + pub fn name(&self) -> &str { match self { GateOp::H(_) => "H", GateOp::X(_) => "X", @@ -33,6 +35,7 @@ impl GateOp { GateOp::CCNOT(_, _, _) => "CCNOT", GateOp::CSWAP(_, _, _) => "CSWAP", GateOp::Measure(_, _) => "M", + GateOp::Custom(gate, _) => &gate.name, } } @@ -42,6 +45,7 @@ impl GateOp { 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], + GateOp::Custom(_, targets) => targets.clone(), } } @@ -55,6 +59,10 @@ impl GateOp { pub fn is_measurement(&self) -> bool { matches!(self, GateOp::Measure(_, _)) } + + pub fn is_custom(&self) -> bool { + matches!(self, GateOp::Custom(_, _)) + } } pub struct QuantumCircuit { @@ -128,6 +136,10 @@ impl QuantumCircuit { 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(_, _) => {} + GateOp::Custom(gate, targets) => { + let quantum_gate = gate.to_quantum_gate(); + register.apply_gate(&quantum_gate, targets); + } } } @@ -232,6 +244,18 @@ impl QuantumCircuit { self } + pub fn custom(&mut self, gate: &Arc<CustomGate>, targets: &[usize]) -> &mut Self { + self.operations.push(GateOp::Custom(Arc::clone(gate), targets.to_vec())); + self.computed_state = None; + self + } + + pub fn apply_custom(&mut self, gate: CustomGate, targets: &[usize]) -> &mut Self { + self.operations.push(GateOp::Custom(Arc::new(gate), targets.to_vec())); + self.computed_state = None; + self + } + pub fn reset(&mut self) -> &mut Self { self.operations.clear(); self.computed_state = None; @@ -272,6 +296,7 @@ impl fmt::Display for QuantumCircuit { for (i, op) in self.operations.iter().enumerate() { match op { GateOp::Measure(q, c) => writeln!(f, " {}: {} q{} → c{}", i, op.name(), q, c)?, + GateOp::Custom(gate, targets) => writeln!(f, " {}: [{}] on {:?}", i, gate.name, targets)?, _ => writeln!(f, " {}: {} on {:?}", i, op.name(), op.quantum_targets())?, } } diff --git a/libpsi-core/src/core/custom_gate.rs b/libpsi-core/src/core/custom_gate.rs new file mode 100644 index 0000000..9e49ac9 --- /dev/null +++ b/libpsi-core/src/core/custom_gate.rs @@ -0,0 +1,245 @@ +use crate::{Complex, Matrix, QuantumGate}; + +#[derive(Clone)] +pub enum CustomGateDefinition { + Matrix(Matrix<Complex<f64>>), + Composite(Vec<(CompositeOp, Vec<usize>)>), +} + +#[derive(Clone, Copy)] +pub enum CompositeOp { + H, + X, + Y, + Z, + S, + T, + CNOT, + CZ, + SWAP, + CCNOT, + CSWAP, +} + +#[derive(Clone)] +pub struct CustomGate { + pub name: String, + pub num_qubits: usize, + pub definition: CustomGateDefinition, +} + +impl CustomGate { + pub fn from_matrix(name: &str, matrix: Matrix<Complex<f64>>) -> Self { + let dim = matrix.rows; + let num_qubits = (dim as f64).log2() as usize; + assert_eq!( + 1 << num_qubits, + dim, + "Matrix dimension must be a power of 2" + ); + assert_eq!(matrix.rows, matrix.cols, "Matrix must be square"); + + CustomGate { + name: String::from(name), + num_qubits, + definition: CustomGateDefinition::Matrix(matrix), + } + } + + pub fn from_composite( + name: &str, + num_qubits: usize, + ops: Vec<(CompositeOp, Vec<usize>)>, + ) -> Self { + CustomGate { + name: String::from(name), + num_qubits, + definition: CustomGateDefinition::Composite(ops), + } + } + + pub fn to_quantum_gate(&self) -> QuantumGate<'static> { + match &self.definition { + CustomGateDefinition::Matrix(matrix) => { + let name: &'static str = Box::leak(self.name.clone().into_boxed_str()); + QuantumGate { + name, + matrix: matrix.clone(), + num_qubits: self.num_qubits, + } + } + CustomGateDefinition::Composite(ops) => { + let matrix = self.compute_composite_matrix(ops); + let name: &'static str = Box::leak(self.name.clone().into_boxed_str()); + QuantumGate { + name, + matrix, + num_qubits: self.num_qubits, + } + } + } + } + + fn compute_composite_matrix(&self, ops: &[(CompositeOp, Vec<usize>)]) -> Matrix<Complex<f64>> { + use crate::gates::*; + use crate::Complex; + + let dim = 1 << self.num_qubits; + let mut result = Matrix::new(dim, dim, vec![Complex::new(0.0, 0.0); dim * dim]); + for i in 0..dim { + result.data[i * dim + i] = Complex::new(1.0, 0.0); + } + + for (op, targets) in ops { + let gate: &QuantumGate = match op { + CompositeOp::H => &HADAMARD, + CompositeOp::X => &PAULI_X, + CompositeOp::Y => &PAULI_Y, + CompositeOp::Z => &PAULI_Z, + CompositeOp::S => &S_GATE, + CompositeOp::T => &T_GATE, + CompositeOp::CNOT => &CNOT, + CompositeOp::CZ => &CZ, + CompositeOp::SWAP => &SWAP, + CompositeOp::CCNOT => &TOFFOLI, + CompositeOp::CSWAP => &FREDKIN, + }; + + let full_gate = build_full_operator(&gate.matrix, targets, self.num_qubits); + result = matrix_multiply(&full_gate, &result); + } + + result + } +} + +fn build_full_operator( + gate_matrix: &Matrix<Complex<f64>>, + targets: &[usize], + total_qubits: usize, +) -> Matrix<Complex<f64>> { + let dim = 1 << total_qubits; + let gate_dim = gate_matrix.rows; + let num_gate_qubits = targets.len(); + + let mut result = Matrix::new(dim, dim, vec![Complex::new(0.0, 0.0); dim * dim]); + + for i in 0..dim { + for j in 0..dim { + let mut gate_i = 0usize; + let mut gate_j = 0usize; + let mut match_non_targets = true; + + for q in 0..total_qubits { + let bit_i = (i >> (total_qubits - 1 - q)) & 1; + let bit_j = (j >> (total_qubits - 1 - q)) & 1; + + if let Some(pos) = targets.iter().position(|&t| t == q) { + gate_i |= bit_i << (num_gate_qubits - 1 - pos); + gate_j |= bit_j << (num_gate_qubits - 1 - pos); + } else if bit_i != bit_j { + match_non_targets = false; + break; + } + } + + if match_non_targets { + result.data[i * dim + j] = gate_matrix.data[gate_i * gate_dim + gate_j]; + } + } + } + + result +} + +fn matrix_multiply(a: &Matrix<Complex<f64>>, b: &Matrix<Complex<f64>>) -> Matrix<Complex<f64>> { + let n = a.rows; + let mut result = Matrix::new(n, n, vec![Complex::new(0.0, 0.0); n * n]); + + for i in 0..n { + for j in 0..n { + let mut sum = Complex::new(0.0, 0.0); + for k in 0..n { + sum = sum + a.data[i * n + k] * b.data[k * n + j]; + } + result.data[i * n + j] = sum; + } + } + + result +} + +pub struct CustomGateBuilder { + name: String, + num_qubits: usize, + ops: Vec<(CompositeOp, Vec<usize>)>, +} + +impl CustomGateBuilder { + pub fn new(name: &str, num_qubits: usize) -> Self { + CustomGateBuilder { + name: String::from(name), + num_qubits, + ops: Vec::new(), + } + } + + pub fn h(mut self, target: usize) -> Self { + self.ops.push((CompositeOp::H, vec![target])); + self + } + + pub fn x(mut self, target: usize) -> Self { + self.ops.push((CompositeOp::X, vec![target])); + self + } + + pub fn y(mut self, target: usize) -> Self { + self.ops.push((CompositeOp::Y, vec![target])); + self + } + + pub fn z(mut self, target: usize) -> Self { + self.ops.push((CompositeOp::Z, vec![target])); + self + } + + pub fn s(mut self, target: usize) -> Self { + self.ops.push((CompositeOp::S, vec![target])); + self + } + + pub fn t(mut self, target: usize) -> Self { + self.ops.push((CompositeOp::T, vec![target])); + self + } + + pub fn cnot(mut self, control: usize, target: usize) -> Self { + self.ops.push((CompositeOp::CNOT, vec![control, target])); + self + } + + pub fn cz(mut self, control: usize, target: usize) -> Self { + self.ops.push((CompositeOp::CZ, vec![control, target])); + self + } + + pub fn swap(mut self, a: usize, b: usize) -> Self { + self.ops.push((CompositeOp::SWAP, vec![a, b])); + self + } + + pub fn ccnot(mut self, c1: usize, c2: usize, target: usize) -> Self { + self.ops.push((CompositeOp::CCNOT, vec![c1, c2, target])); + self + } + + pub fn cswap(mut self, control: usize, t1: usize, t2: usize) -> Self { + self.ops.push((CompositeOp::CSWAP, vec![control, t1, t2])); + self + } + + pub fn build(self) -> CustomGate { + CustomGate::from_composite(&self.name, self.num_qubits, self.ops) + } +} diff --git a/libpsi-core/src/core/mod.rs b/libpsi-core/src/core/mod.rs index 6936487..1b950f3 100644 --- a/libpsi-core/src/core/mod.rs +++ b/libpsi-core/src/core/mod.rs @@ -1,9 +1,11 @@ pub mod circuit; pub mod classical_components; +pub mod custom_gate; pub mod gates; pub mod quantum_components; pub use circuit::*; pub use classical_components::*; +pub use custom_gate::*; pub use gates::*; pub use quantum_components::*; diff --git a/libpsi-core/src/lib.rs b/libpsi-core/src/lib.rs index 23da477..68ced7b 100644 --- a/libpsi-core/src/lib.rs +++ b/libpsi-core/src/lib.rs @@ -9,5 +9,6 @@ pub use maths::vector::*; pub use core::circuit::*; pub use core::classical_components::*; +pub use core::custom_gate::*; pub use core::gates; pub use core::quantum_components::*; diff --git a/libpsi-core/src/maths/complex.rs b/libpsi-core/src/maths/complex.rs index 86e4788..31eae69 100644 --- a/libpsi-core/src/maths/complex.rs +++ b/libpsi-core/src/maths/complex.rs @@ -126,13 +126,53 @@ impl<T: Float> Complex<T> { impl_ops!(Add, add, +); impl_ops!(Sub, sub, -); -impl_ops!(Mul, mul, *); -impl_ops!(Div, div, /); + +impl<T: Float> ops::Mul for Complex<T> { + type Output = Complex<T>; + + fn mul(self, other: Complex<T>) -> Complex<T> { + // (a + bi) * (c + di) = (ac - bd) + (ad + bc)i + Complex { + real: self.real * other.real - self.imaginary * other.imaginary, + imaginary: self.real * other.imaginary + self.imaginary * other.real, + } + } +} + +impl<T: Float> ops::Div for Complex<T> { + type Output = Complex<T>; + + fn div(self, other: Complex<T>) -> Complex<T> { + // (a + bi) / (c + di) = ((ac + bd) + (bc - ad)i) / (c² + d²) + let denom = other.real * other.real + other.imaginary * other.imaginary; + Complex { + real: (self.real * other.real + self.imaginary * other.imaginary) / denom, + imaginary: (self.imaginary * other.real - self.real * other.imaginary) / denom, + } + } +} impl_ops!(AddAssign, add_assign, +, assign); impl_ops!(SubAssign, sub_assign, -, assign); -impl_ops!(MulAssign, mul_assign, *, assign); -impl_ops!(DivAssign, div_assign, /, assign); + +impl<T: Float> ops::MulAssign for Complex<T> { + fn mul_assign(&mut self, other: Complex<T>) { + let new_real = self.real * other.real - self.imaginary * other.imaginary; + let new_imag = self.real * other.imaginary + self.imaginary * other.real; + self.real = new_real; + self.imaginary = new_imag; + } +} + +impl<T: Float> ops::DivAssign for Complex<T> { + fn div_assign(&mut self, other: Complex<T>) { + let denom = other.real * other.real + other.imaginary * other.imaginary; + let new_real = (self.real * other.real + self.imaginary * other.imaginary) / denom; + let new_imag = (self.imaginary * other.real - self.real * other.imaginary) / denom; + self.real = new_real; + self.imaginary = new_imag; + } +} impl_ops!(Add, add, +, real); impl_ops!(Sub, sub, -, real); 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)?; diff --git a/tester/src/main.rs b/tester/src/main.rs index ab4c78c..7981e8d 100644 --- a/tester/src/main.rs +++ b/tester/src/main.rs @@ -91,4 +91,71 @@ fn main() { complex.compute(); println!("{}", complex); + + print!("------\n\n"); + + println!("Custom Gate: Bell Pair Creator\n"); + + let bell_gate = CustomGateBuilder::new("BELL", 2).h(0).cnot(0, 1).build(); + + let mut custom_circuit = QuantumCircuit::new(4); + custom_circuit + .apply_custom(bell_gate.clone(), &[0, 1]) + .apply_custom(bell_gate.clone(), &[2, 3]); + + println!("Horizontal:"); + println!("{}", HorizontalRenderer::new(&custom_circuit)); + println!("Vertical:"); + println!("{}", VerticalRenderer::new(&custom_circuit)); + + custom_circuit.compute(); + println!("{}", custom_circuit); + + print!("------\n\n"); + + println!("Custom Gate: Swap via CNOTs\n"); + + let swap_gate = CustomGateBuilder::new("MYSWAP", 2) + .cnot(0, 1) + .cnot(1, 0) + .cnot(0, 1) + .build(); + + let mut swap_test = QuantumCircuit::new(2); + swap_test.x(0).apply_custom(swap_gate, &[0, 1]); + + println!("Horizontal:"); + println!("{}", HorizontalRenderer::new(&swap_test)); + println!("Vertical:"); + println!("{}", VerticalRenderer::new(&swap_test)); + + swap_test.compute(); + println!("{}", swap_test); + + print!("------\n\n"); + + println!("Custom Gate: Matrix-defined √X gate\n"); + + // √X gate (square root of NOT) + // When applied twice, it equals X gate + let sqrt_x_matrix = matrix!( + [complex!(0.5, 0.5), complex!(0.5, -0.5)]; + [complex!(0.5, -0.5), complex!(0.5, 0.5)] + ); + + let sqrt_x = CustomGate::from_matrix("√X", sqrt_x_matrix); + + let mut matrix_test = QuantumCircuit::new(1); + matrix_test + .apply_custom(sqrt_x.clone(), &[0]) + .apply_custom(sqrt_x, &[0]); // Two √X = X + + println!("Horizontal:"); + println!("{}", HorizontalRenderer::new(&matrix_test)); + println!("Vertical:"); + println!("{}", VerticalRenderer::new(&matrix_test)); + + matrix_test.compute(); + println!("{}", matrix_test); + println!("(Two √X gates should equal X, so |0⟩ becomes |1⟩)"); } |
