aboutsummaryrefslogtreecommitdiff
path: root/libpsi-core
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2025-12-06 21:20:43 +0100
committerhachem <im@hachem.wtf>2025-12-06 21:20:43 +0100
commit609f7276fc1fb484be34b232d35426f91ddb9b71 (patch)
tree8cfc9a68ac184cb01111ddbef1f3a77b0b62b91f /libpsi-core
parent1fef726728a14cc923c7cbaf80d337b4eb35db3c (diff)
[add]: complete horizonal renderer
Diffstat (limited to 'libpsi-core')
-rw-r--r--libpsi-core/src/core/circuit.rs308
1 files changed, 184 insertions, 124 deletions
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(&amp))?;
- }
- }
- 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(&amp))?;
+ }
+ }
+ } else {
+ writeln!(f, "State: (not computed)")?;
+ }
+ Ok(())
+ }
+}