aboutsummaryrefslogtreecommitdiff
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
parentedc6bae7bf5039c8cef2f2c9c164187478b1c7eb (diff)
[add]: custom gates + rework vertical renderer
-rw-r--r--libpsi-core/src/core/circuit.rs31
-rw-r--r--libpsi-core/src/core/custom_gate.rs245
-rw-r--r--libpsi-core/src/core/mod.rs2
-rw-r--r--libpsi-core/src/lib.rs1
-rw-r--r--libpsi-core/src/maths/complex.rs48
-rw-r--r--libpsi-visualizer/src/cli/horizontal_cli.rs25
-rw-r--r--libpsi-visualizer/src/cli/vertical_cli.rs219
-rw-r--r--tester/src/main.rs67
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⟩)");
}