diff options
| author | hachem <im@hachem.wtf> | 2025-12-06 20:59:32 +0100 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2025-12-06 20:59:32 +0100 |
| commit | 1fef726728a14cc923c7cbaf80d337b4eb35db3c (patch) | |
| tree | be65614b80e566196beb2ed74d679ac2d51d78c5 | |
| parent | c7f082381606700ed49c1d57f85aa4d38a9380e7 (diff) | |
[fix]: rounding errors and fancy printing
| -rw-r--r-- | libpsi-core/src/core/circuit.rs | 18 | ||||
| -rw-r--r-- | libpsi-core/src/lib.rs | 1 | ||||
| -rw-r--r-- | libpsi-core/src/maths/format.rs | 141 | ||||
| -rw-r--r-- | libpsi-core/src/maths/mod.rs | 2 | ||||
| -rw-r--r-- | tester/src/main.rs | 24 |
5 files changed, 169 insertions, 17 deletions
diff --git a/libpsi-core/src/core/circuit.rs b/libpsi-core/src/core/circuit.rs index 293d509..eb64aaf 100644 --- a/libpsi-core/src/core/circuit.rs +++ b/libpsi-core/src/core/circuit.rs @@ -1,5 +1,5 @@ use super::{QuantumGate, QuantumRegister, QuantumState}; -use crate::Vector; +use crate::{format_amplitude, format_probability, Vector}; use core::fmt; #[derive(Clone)] @@ -211,9 +211,23 @@ impl<'a> fmt::Display for QuantumCircuit<'a> { 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, " |{}⟩: {:.4}", basis, amp)?; + writeln!(f, " |{}⟩: {}", basis, format_amplitude(&))?; } } Ok(()) } } + +impl<'a> QuantumCircuit<'a> { + pub fn print_probabilities(&self) { + let probs = self.probabilities(); + let n = self.num_qubits(); + println!("Probabilities:"); + for (i, p) in probs.iter().enumerate() { + if *p > 1e-10 { + let basis: String = format!("{:0width$b}", i, width = n); + println!(" |{}⟩: {}", basis, format_probability(*p)); + } + } + } +} diff --git a/libpsi-core/src/lib.rs b/libpsi-core/src/lib.rs index 81c9fe2..23da477 100644 --- a/libpsi-core/src/lib.rs +++ b/libpsi-core/src/lib.rs @@ -2,6 +2,7 @@ pub mod core; pub mod maths; pub use maths::complex::*; +pub use maths::format::*; pub use maths::matrix::*; pub use maths::numeric::*; pub use maths::vector::*; diff --git a/libpsi-core/src/maths/format.rs b/libpsi-core/src/maths/format.rs new file mode 100644 index 0000000..957f1b6 --- /dev/null +++ b/libpsi-core/src/maths/format.rs @@ -0,0 +1,141 @@ +use crate::Complex; + +const EPSILON: f64 = 1e-10; +const SQRT_2: f64 = 1.4142135623730951; +const INV_SQRT_2: f64 = 0.7071067811865475; +const INV_SQRT_8: f64 = 0.3535533905932738; +const INV_SQRT_32: f64 = 0.1767766952966369; + +fn approx_eq(a: f64, b: f64) -> bool { + (a - b).abs() < EPSILON +} + +fn format_real_symbolic(v: f64) -> Option<String> { + let abs_v = v.abs(); + let sign = if v < 0.0 { "-" } else { "" }; + + if approx_eq(abs_v, 0.0) { + return Some("0".to_string()); + } + if approx_eq(abs_v, 1.0) { + return Some(format!("{}1", sign)); + } + if approx_eq(abs_v, 0.5) { + return Some(format!("{}½", sign)); + } + if approx_eq(abs_v, 0.25) { + return Some(format!("{}¼", sign)); + } + if approx_eq(abs_v, 0.75) { + return Some(format!("{}¾", sign)); + } + if approx_eq(abs_v, 0.125) { + return Some(format!("{}⅛", sign)); + } + if approx_eq(abs_v, SQRT_2) { + return Some(format!("{}√2", sign)); + } + if approx_eq(abs_v, INV_SQRT_2) { + return Some(format!("{}¹⁄√2", sign)); + } + if approx_eq(abs_v, INV_SQRT_8) { + return Some(format!("{}¹⁄√8", sign)); + } + if approx_eq(abs_v, INV_SQRT_32) { + return Some(format!("{}¹⁄√32", sign)); + } + if approx_eq(abs_v, 2.0) { + return Some(format!("{}2", sign)); + } + if approx_eq(abs_v, 1.0 / 3.0) { + return Some(format!("{}⅓", sign)); + } + if approx_eq(abs_v, 2.0 / 3.0) { + return Some(format!("{}⅔", sign)); + } + + None +} + +pub fn format_amplitude(c: &Complex<f64>) -> String { + let re = c.real; + let im = c.imaginary; + + let re_zero = approx_eq(re.abs(), 0.0); + let im_zero = approx_eq(im.abs(), 0.0); + + if re_zero && im_zero { + return "0".to_string(); + } + + if im_zero { + if let Some(s) = format_real_symbolic(re) { + return s; + } + return format!("{:.4}", re); + } + + if re_zero { + if approx_eq(im.abs(), 1.0) { + return if im > 0.0 { + "i".to_string() + } else { + "-i".to_string() + }; + } + if let Some(s) = format_real_symbolic(im) { + return format!("{}i", s); + } + return format!("{:.4}i", im); + } + + let re_str = format_real_symbolic(re).unwrap_or_else(|| format!("{:.4}", re)); + let im_str = if approx_eq(im.abs(), 1.0) { + if im > 0.0 { + "+i".to_string() + } else { + "-i".to_string() + } + } else { + let im_sym = format_real_symbolic(im.abs()); + let sign = if im > 0.0 { "+" } else { "-" }; + match im_sym { + Some(s) => format!("{}{}i", sign, s.trim_start_matches('-')), + None => format!("{}{:.4}i", sign, im.abs()), + } + }; + + format!("{}{}", re_str, im_str) +} + +pub fn format_probability(p: f64) -> String { + if approx_eq(p, 0.0) { + return "0".to_string(); + } + if approx_eq(p, 1.0) { + return "1".to_string(); + } + if approx_eq(p, 0.5) { + return "½".to_string(); + } + if approx_eq(p, 0.25) { + return "¼".to_string(); + } + if approx_eq(p, 0.75) { + return "¾".to_string(); + } + if approx_eq(p, 0.125) { + return "⅛".to_string(); + } + if approx_eq(p, 0.0625) { + return "¹⁄₁₆".to_string(); + } + if approx_eq(p, 1.0 / 3.0) { + return "⅓".to_string(); + } + if approx_eq(p, 2.0 / 3.0) { + return "⅔".to_string(); + } + + format!("{:.4}", p) +} diff --git a/libpsi-core/src/maths/mod.rs b/libpsi-core/src/maths/mod.rs index 5bc7f6d..e716181 100644 --- a/libpsi-core/src/maths/mod.rs +++ b/libpsi-core/src/maths/mod.rs @@ -1,10 +1,12 @@ pub mod complex; +pub mod format; pub mod matrix; pub mod numeric; pub mod vector; pub mod vector_ops; pub use complex::*; +pub use format::*; pub use matrix::*; pub use numeric::*; pub use vector::*; diff --git a/tester/src/main.rs b/tester/src/main.rs index 96fc86f..4bab72d 100644 --- a/tester/src/main.rs +++ b/tester/src/main.rs @@ -7,7 +7,7 @@ fn main() { bell.h(0).cnot(0, 1); println!("{}", bell); - print!("\n------\n\n"); + print!("------\n\n"); println!("GHZ State (3-qubit entanglement): |GHZ> = (|000⟩ + |111⟩)/√2"); println!(" Circuit: H(q0) → CNOT(q0, q1) → CNOT(q0, q2)"); @@ -15,7 +15,7 @@ fn main() { ghz.h(0).cnot(0, 1).cnot(0, 2); println!("{}", ghz); - print!("\n------\n\n"); + print!("------\n\n"); println!("SWAP via 3 CNOTs"); println!(" Start with |10>, apply CNOT chain"); @@ -27,7 +27,7 @@ fn main() { .cnot(0, 1); println!("{}", swap_circuit); - print!("\n------\n\n"); + print!("------\n\n"); println!("Toffoli Gate (Reversible AND)"); println!(" CCNOT flips q2 only when q0=1 AND q1=1"); @@ -38,7 +38,7 @@ fn main() { .toffoli(0, 1, 2); println!("{}", toffoli_circuit); - print!("\n------\n\n"); + print!("------\n\n"); println!("Fredkin Gate (Controlled SWAP)"); println!(" CSWAP swaps q1 and q2 only when q0=1"); @@ -49,7 +49,7 @@ fn main() { .fredkin(0, 1, 2); println!("{}", fredkin_circuit); - print!("\n------\n\n"); + print!("------\n\n"); println!("6. Non-contiguous CNOT (q0 controls q2, skipping q1)"); let mut nc_circuit = QuantumCircuit::new(3); @@ -58,28 +58,22 @@ fn main() { .cnot(0, 2); // CNOT with control=q0, target=q2 println!("{}", nc_circuit); - print!("\n------\n\n"); + print!("------\n\n"); 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); - print!("\n------\n\n"); + print!("------\n\n"); println!("Probability Test"); let mut prob_circuit = QuantumCircuit::new(2); prob_circuit.h(0).cnot(0, 1); - println!(" Bell state probabilities:"); - let probs = prob_circuit.probabilities(); - for (i, p) in probs.iter().enumerate() { - if *p > 1e-10 { - println!(" |{:02b}⟩: {:.4}", i, p); - } - } + prob_circuit.print_probabilities(); println!(); - print!("\n------\n\n"); + print!("------\n\n"); println!("Complex Circuit with Method Chaining"); let mut complex = QuantumCircuit::new(4); |
