diff options
Diffstat (limited to 'src/core')
| -rw-r--r-- | src/core/circuit.rs | 477 | ||||
| -rw-r--r-- | src/core/classical_components.rs | 63 | ||||
| -rw-r--r-- | src/core/custom_gate.rs | 245 | ||||
| -rw-r--r-- | src/core/gates.rs | 253 | ||||
| -rw-r--r-- | src/core/kernel.rs | 621 | ||||
| -rw-r--r-- | src/core/mod.rs | 17 | ||||
| -rw-r--r-- | src/core/noise.rs | 559 | ||||
| -rw-r--r-- | src/core/quantum_components.rs | 318 | ||||
| -rw-r--r-- | src/core/runtime.rs | 585 |
9 files changed, 3138 insertions, 0 deletions
diff --git a/src/core/circuit.rs b/src/core/circuit.rs new file mode 100644 index 0000000..6647f58 --- /dev/null +++ b/src/core/circuit.rs @@ -0,0 +1,477 @@ +use super::{CustomGate, QuantumState, Runtime, RuntimeConfig}; +use crate::{format_amplitude, format_probability, Vector}; +use core::fmt; +use std::sync::Arc; + +#[derive(Clone)] +pub enum GateOp { + H(usize), + X(usize), + Y(usize), + Z(usize), + S(usize), + T(usize), + Sdg(usize), + Tdg(usize), + Sx(usize), + Sxdg(usize), + Rx(usize, f64), + Ry(usize, f64), + Rz(usize, f64), + P(usize, f64), + U1(usize, f64), + U2(usize, f64, f64), + U3(usize, f64, f64, f64), + CNOT(usize, usize), + CZ(usize, usize), + SWAP(usize, usize), + CRx(usize, usize, f64), + CRy(usize, usize, f64), + CRz(usize, usize, f64), + CP(usize, usize, f64), + CCNOT(usize, usize, usize), + CSWAP(usize, usize, usize), + Measure(usize, usize), + Custom(Arc<CustomGate>, Vec<usize>), +} + +impl GateOp { + pub fn name(&self) -> &str { + match self { + GateOp::H(_) => "H", + GateOp::X(_) => "X", + GateOp::Y(_) => "Y", + GateOp::Z(_) => "Z", + GateOp::S(_) => "S", + GateOp::T(_) => "T", + GateOp::Sdg(_) => "S†", + GateOp::Tdg(_) => "T†", + GateOp::Sx(_) => "√X", + GateOp::Sxdg(_) => "√X†", + GateOp::Rx(_, _) => "Rx", + GateOp::Ry(_, _) => "Ry", + GateOp::Rz(_, _) => "Rz", + GateOp::P(_, _) => "P", + GateOp::U1(_, _) => "U1", + GateOp::U2(_, _, _) => "U2", + GateOp::U3(_, _, _, _) => "U3", + GateOp::CRx(_, _, _) => "CRx", + GateOp::CRy(_, _, _) => "CRy", + GateOp::CRz(_, _, _) => "CRz", + GateOp::CP(_, _, _) => "CP", + GateOp::CNOT(_, _) => "CNOT", + GateOp::CZ(_, _) => "CZ", + GateOp::SWAP(_, _) => "SWAP", + GateOp::CCNOT(_, _, _) => "CCNOT", + GateOp::CSWAP(_, _, _) => "CSWAP", + GateOp::Measure(_, _) => "M", + GateOp::Custom(gate, _) => &gate.name, + } + } + + 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) + | GateOp::Sdg(t) + | GateOp::Tdg(t) + | GateOp::Sx(t) + | GateOp::Sxdg(t) + | GateOp::Rx(t, _) + | GateOp::Ry(t, _) + | GateOp::Rz(t, _) + | GateOp::P(t, _) + | GateOp::U1(t, _) + | GateOp::U2(t, _, _) + | GateOp::U3(t, _, _, _) => vec![*t], + GateOp::CNOT(c, t) + | GateOp::CZ(c, t) + | GateOp::SWAP(c, t) + | GateOp::CRx(c, t, _) + | GateOp::CRy(c, t, _) + | GateOp::CRz(c, t, _) + | GateOp::CP(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(), + } + } + + pub fn classical_targets(&self) -> Vec<usize> { + match self { + GateOp::Measure(_, c) => vec![*c], + _ => vec![], + } + } + + pub fn is_measurement(&self) -> bool { + matches!(self, GateOp::Measure(_, _)) + } + + pub fn is_custom(&self) -> bool { + matches!(self, GateOp::Custom(_, _)) + } + + pub fn is_non_clifford(&self) -> bool { + matches!( + self, + GateOp::T(_) + | GateOp::Tdg(_) + | GateOp::Sx(_) + | GateOp::Sxdg(_) + | GateOp::Rx(_, _) + | GateOp::Ry(_, _) + | GateOp::Rz(_, _) + | GateOp::P(_, _) + | GateOp::U1(_, _) + | GateOp::U2(_, _, _) + | GateOp::U3(_, _, _, _) + | GateOp::CRx(_, _, _) + | GateOp::CRy(_, _, _) + | GateOp::CRz(_, _, _) + | GateOp::CP(_, _, _) + ) + } +} + +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 { + num_qubits, + num_classical: 0, + operations: Vec::new(), + computed_state: None, + } + } + + pub fn with_classical(num_qubits: usize, num_classical: usize) -> QuantumCircuit { + QuantumCircuit { + num_qubits, + num_classical, + operations: Vec::new(), + computed_state: None, + } + } + + pub fn num_qubits(&self) -> usize { + self.num_qubits + } + + pub fn num_classical(&self) -> usize { + self.num_classical + } + + pub fn operations(&self) -> &[GateOp] { + &self.operations + } + + pub fn is_computed(&self) -> bool { + self.computed_state.is_some() + } + + pub fn compute(&mut self) -> &QuantumState { + self.compute_with(Runtime::default()) + } + + pub fn compute_with(&mut self, runtime: Runtime) -> &QuantumState { + if self.computed_state.is_none() { + self.computed_state = Some(runtime.compute(self.num_qubits, &self.operations)); + } + self.computed_state.as_ref().unwrap() + } + + pub fn compute_with_config(&mut self, config: RuntimeConfig) -> &QuantumState { + if self.computed_state.is_none() { + self.computed_state = Some(config.compute(self.num_qubits, &self.operations)); + } + self.computed_state.as_ref().unwrap() + } + + pub fn state(&mut self) -> &QuantumState { + self.compute() + } + + pub fn state_with(&mut self, runtime: Runtime) -> &QuantumState { + self.compute_with(runtime) + } + + pub fn state_with_config(&mut self, config: RuntimeConfig) -> &QuantumState { + self.compute_with_config(config) + } + + pub fn h(&mut self, target: usize) -> &mut Self { + self.operations.push(GateOp::H(target)); + self.computed_state = None; + self + } + + pub fn x(&mut self, target: usize) -> &mut Self { + self.operations.push(GateOp::X(target)); + self.computed_state = None; + self + } + + pub fn y(&mut self, target: usize) -> &mut Self { + self.operations.push(GateOp::Y(target)); + self.computed_state = None; + self + } + + pub fn z(&mut self, target: usize) -> &mut Self { + self.operations.push(GateOp::Z(target)); + self.computed_state = None; + self + } + + pub fn s(&mut self, target: usize) -> &mut Self { + self.operations.push(GateOp::S(target)); + self.computed_state = None; + self + } + + pub fn t(&mut self, target: usize) -> &mut Self { + self.operations.push(GateOp::T(target)); + self.computed_state = None; + self + } + + pub fn sdg(&mut self, target: usize) -> &mut Self { + self.operations.push(GateOp::Sdg(target)); + self.computed_state = None; + self + } + + pub fn tdg(&mut self, target: usize) -> &mut Self { + self.operations.push(GateOp::Tdg(target)); + self.computed_state = None; + self + } + + pub fn sx(&mut self, target: usize) -> &mut Self { + self.operations.push(GateOp::Sx(target)); + self.computed_state = None; + self + } + + pub fn sxdg(&mut self, target: usize) -> &mut Self { + self.operations.push(GateOp::Sxdg(target)); + self.computed_state = None; + self + } + + pub fn rx(&mut self, target: usize, theta: f64) -> &mut Self { + self.operations.push(GateOp::Rx(target, theta)); + self.computed_state = None; + self + } + + pub fn ry(&mut self, target: usize, theta: f64) -> &mut Self { + self.operations.push(GateOp::Ry(target, theta)); + self.computed_state = None; + self + } + + pub fn rz(&mut self, target: usize, theta: f64) -> &mut Self { + self.operations.push(GateOp::Rz(target, theta)); + self.computed_state = None; + self + } + + pub fn p(&mut self, target: usize, theta: f64) -> &mut Self { + self.operations.push(GateOp::P(target, theta)); + self.computed_state = None; + self + } + + pub fn u1(&mut self, target: usize, lambda: f64) -> &mut Self { + self.operations.push(GateOp::U1(target, lambda)); + self.computed_state = None; + self + } + + pub fn u2(&mut self, target: usize, phi: f64, lambda: f64) -> &mut Self { + self.operations.push(GateOp::U2(target, phi, lambda)); + self.computed_state = None; + self + } + + pub fn u3(&mut self, target: usize, theta: f64, phi: f64, lambda: f64) -> &mut Self { + self.operations.push(GateOp::U3(target, theta, phi, lambda)); + self.computed_state = None; + self + } + + pub fn crx(&mut self, control: usize, target: usize, theta: f64) -> &mut Self { + self.operations.push(GateOp::CRx(control, target, theta)); + self.computed_state = None; + self + } + + pub fn cry(&mut self, control: usize, target: usize, theta: f64) -> &mut Self { + self.operations.push(GateOp::CRy(control, target, theta)); + self.computed_state = None; + self + } + + pub fn crz(&mut self, control: usize, target: usize, theta: f64) -> &mut Self { + self.operations.push(GateOp::CRz(control, target, theta)); + self.computed_state = None; + self + } + + pub fn cp(&mut self, control: usize, target: usize, theta: f64) -> &mut Self { + self.operations.push(GateOp::CP(control, target, theta)); + self.computed_state = None; + self + } + + pub fn cnot(&mut self, control: usize, target: usize) -> &mut Self { + self.operations.push(GateOp::CNOT(control, target)); + self.computed_state = None; + self + } + + pub fn cx(&mut self, control: usize, target: usize) -> &mut Self { + self.cnot(control, target) + } + + pub fn cz(&mut self, control: usize, target: usize) -> &mut Self { + self.operations.push(GateOp::CZ(control, target)); + self.computed_state = None; + self + } + + pub fn swap(&mut self, qubit1: usize, qubit2: usize) -> &mut Self { + 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 { + self.operations + .push(GateOp::CCNOT(control1, control2, target)); + self.computed_state = None; + self + } + + pub fn toffoli(&mut self, control1: usize, control2: usize, target: usize) -> &mut Self { + self.ccnot(control1, control2, target) + } + + pub fn cswap(&mut self, control: usize, target1: usize, target2: usize) -> &mut Self { + self.operations + .push(GateOp::CSWAP(control, target1, target2)); + self.computed_state = None; + self + } + + pub fn fredkin(&mut self, control: usize, target1: usize, target2: usize) -> &mut Self { + self.cswap(control, target1, target2) + } + + 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 + } + + pub fn measure_all(&mut self) -> &mut Self { + for i in 0..self.num_qubits { + self.measure(i, i); + } + 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; + self + } + + 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(&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() + } + + pub fn print_probabilities(&mut 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)); + } + } + } +} + +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)?, + GateOp::Custom(gate, targets) => { + writeln!(f, " {}: [{}] on {:?}", i, gate.name, targets)? + } + _ => 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/src/core/classical_components.rs b/src/core/classical_components.rs new file mode 100644 index 0000000..b2018d4 --- /dev/null +++ b/src/core/classical_components.rs @@ -0,0 +1,63 @@ +use core::ops; + +#[derive(Clone, Copy)] +pub struct ClassicalBit<'a> { + state: bool, + name: &'a str, +} + +#[derive(Clone)] +pub struct ClassicalRegister<'a> { + bits: Vec<ClassicalBit<'a>>, + name: &'a str, +} + +impl<'a> ClassicalBit<'a> { + pub fn new(name: &'a str, state: bool) -> ClassicalBit<'a> { + ClassicalBit { name, state } + } + + pub fn get_name(&self) -> &'a str { + self.name + } + + pub fn get_state(&self) -> bool { + self.state + } +} + +impl<'a> ClassicalRegister<'a> { + pub fn new(name: &'a str, names: &'a [&'a str]) -> ClassicalRegister<'a> { + let mut bits: Vec<ClassicalBit<'a>> = Vec::new(); + for &name in names { + bits.push(ClassicalBit::new(name, false)); + } + ClassicalRegister { name, bits } + } + + pub fn set_bits(&mut self, bits: Vec<ClassicalBit<'a>>) { + self.bits = bits; + } + + pub fn get_bits(&self) -> Vec<ClassicalBit<'a>> { + self.bits.clone() + } + + pub fn get_name(&self) -> &'a str { + self.name + } +} + +impl<'a> ops::Index<usize> for ClassicalRegister<'a> { + type Output = ClassicalBit<'a>; + + fn index(&self, index: usize) -> &Self::Output { + &self.bits[index] + } +} + +impl<'a> ops::IndexMut<usize> for ClassicalRegister<'a> { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.bits[index] + } +} diff --git a/src/core/custom_gate.rs b/src/core/custom_gate.rs new file mode 100644 index 0000000..51d3a56 --- /dev/null +++ b/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 += 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/src/core/gates.rs b/src/core/gates.rs new file mode 100644 index 0000000..cee4ebc --- /dev/null +++ b/src/core/gates.rs @@ -0,0 +1,253 @@ +use crate::{complex, matrix, Complex, Matrix, QuantumGate}; +use std::f64::consts::FRAC_1_SQRT_2; + +pub fn rx_matrix(theta: f64) -> Matrix<Complex<f64>> { + let cos = (theta / 2.0).cos(); + let sin = (theta / 2.0).sin(); + matrix!( + [complex!(cos, 0.0), complex!(0.0, -sin)]; + [complex!(0.0, -sin), complex!(cos, 0.0)] + ) +} + +pub fn ry_matrix(theta: f64) -> Matrix<Complex<f64>> { + let cos = (theta / 2.0).cos(); + let sin = (theta / 2.0).sin(); + matrix!( + [complex!(cos, 0.0), complex!(-sin, 0.0)]; + [complex!(sin, 0.0), complex!(cos, 0.0)] + ) +} + +pub fn rz_matrix(theta: f64) -> Matrix<Complex<f64>> { + let half = theta / 2.0; + matrix!( + [complex!(half.cos(), -half.sin()), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(half.cos(), half.sin())] + ) +} + +pub fn p_matrix(theta: f64) -> Matrix<Complex<f64>> { + matrix!( + [complex!(1.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(theta.cos(), theta.sin())] + ) +} + +pub fn u1_matrix(lambda: f64) -> Matrix<Complex<f64>> { + p_matrix(lambda) +} + +pub fn u2_matrix(phi: f64, lambda: f64) -> Matrix<Complex<f64>> { + let inv_sqrt2 = FRAC_1_SQRT_2; + matrix!( + [complex!(inv_sqrt2, 0.0), complex!(-inv_sqrt2 * lambda.cos(), -inv_sqrt2 * lambda.sin())]; + [complex!(inv_sqrt2 * phi.cos(), inv_sqrt2 * phi.sin()), complex!((phi + lambda).cos() * inv_sqrt2, (phi + lambda).sin() * inv_sqrt2)] + ) +} + +pub fn u3_matrix(theta: f64, phi: f64, lambda: f64) -> Matrix<Complex<f64>> { + let cos = (theta / 2.0).cos(); + let sin = (theta / 2.0).sin(); + matrix!( + [complex!(cos, 0.0), complex!(-sin * lambda.cos(), -sin * lambda.sin())]; + [complex!(sin * phi.cos(), sin * phi.sin()), complex!(cos * (phi + lambda).cos(), cos * (phi + lambda).sin())] + ) +} + +pub fn crx_matrix(theta: f64) -> Matrix<Complex<f64>> { + let cos = (theta / 2.0).cos(); + let sin = (theta / 2.0).sin(); + matrix!( + [complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(cos, 0.0), complex!(0.0, -sin)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, -sin), complex!(cos, 0.0)] + ) +} + +pub fn cry_matrix(theta: f64) -> Matrix<Complex<f64>> { + let cos = (theta / 2.0).cos(); + let sin = (theta / 2.0).sin(); + matrix!( + [complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(cos, 0.0), complex!(-sin, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(sin, 0.0), complex!(cos, 0.0)] + ) +} + +pub fn crz_matrix(theta: f64) -> Matrix<Complex<f64>> { + let half = theta / 2.0; + matrix!( + [complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(half.cos(), -half.sin()), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(half.cos(), half.sin())] + ) +} + +pub fn cp_matrix(theta: f64) -> Matrix<Complex<f64>> { + matrix!( + [complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(1.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(theta.cos(), theta.sin())] + ) +} + +#[rustfmt::skip] +lazy_static::lazy_static! { + pub static ref HADAMARD: QuantumGate<'static> = QuantumGate { + name: "H", + matrix: matrix!([complex!(1.0, 0.0), complex!( 1.0, 0.0)]; + [complex!(1.0, 0.0), complex!(-1.0, 0.0)]) * + complex!(1.0/2.0_f64.sqrt(), 0.0), + num_qubits: 1, + }; + + pub static ref PAULI_X: QuantumGate<'static> = QuantumGate { + name: "X", + matrix: matrix!([complex!(0.0, 0.0), complex!(1.0, 0.0)]; + [complex!(1.0, 0.0), complex!(0.0, 0.0)]), + num_qubits: 1, + }; + + pub static ref PAULI_Y: QuantumGate<'static> = QuantumGate { + name: "Y", + matrix: matrix!([complex!(0.0, 0.0), complex!(0.0, -1.0)]; + [complex!(0.0, 1.0), complex!(0.0, 0.0)]), + num_qubits: 1, + }; + + pub static ref PAULI_Z: QuantumGate<'static> = QuantumGate { + name: "Z", + matrix: matrix!([complex!(1.0, 0.0), complex!( 0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(-1.0, 0.0)]), + num_qubits: 1, + }; + + pub static ref S_GATE: QuantumGate<'static> = QuantumGate { + name: "S", + matrix: matrix!([complex!(1.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 1.0)]), + num_qubits: 1, + }; + + pub static ref T_GATE: QuantumGate<'static> = QuantumGate { + name: "T", + matrix: matrix!([complex!(1.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(core::f64::consts::FRAC_1_SQRT_2, core::f64::consts::FRAC_1_SQRT_2)]), + num_qubits: 1, + }; + + pub static ref SDG_GATE: QuantumGate<'static> = QuantumGate { + name: "S†", + matrix: matrix!([complex!(1.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, -1.0)]), + num_qubits: 1, + }; + + pub static ref TDG_GATE: QuantumGate<'static> = QuantumGate { + name: "T†", + matrix: matrix!([complex!(1.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(core::f64::consts::FRAC_1_SQRT_2, -core::f64::consts::FRAC_1_SQRT_2)]), + num_qubits: 1, + }; + + pub static ref SX_GATE: QuantumGate<'static> = QuantumGate { + name: "√X", + matrix: matrix!([complex!(0.5, 0.5), complex!(0.5, -0.5)]; + [complex!(0.5, -0.5), complex!(0.5, 0.5)]), + num_qubits: 1, + }; + + pub static ref SXDG_GATE: QuantumGate<'static> = QuantumGate { + name: "√X†", + matrix: matrix!([complex!(0.5, -0.5), complex!(0.5, 0.5)]; + [complex!(0.5, 0.5), complex!(0.5, -0.5)]), + num_qubits: 1, + }; + + pub static ref IDENTITY: QuantumGate<'static> = QuantumGate { + name: "I", + matrix: matrix!([complex!(1.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(1.0, 0.0)]), + num_qubits: 1, + }; + + pub static ref CNOT: QuantumGate<'static> = QuantumGate { + name: "CNOT", + matrix: matrix!([complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(1.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(1.0, 0.0), complex!(0.0, 0.0)]), + num_qubits: 2, + }; + + pub static ref CZ: QuantumGate<'static> = QuantumGate { + name: "CZ", + matrix: matrix!([complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!( 0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(1.0, 0.0), complex!(0.0, 0.0), complex!( 0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(1.0, 0.0), complex!( 0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(-1.0, 0.0)]), + num_qubits: 2, + }; + + pub static ref SWAP: QuantumGate<'static> = QuantumGate { + name: "SWAP", + matrix: matrix!([complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(1.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(1.0, 0.0)]), + num_qubits: 2, + }; + + pub static ref ISWAP: QuantumGate<'static> = QuantumGate { + name: "iSWAP", + matrix: matrix!([complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 1.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 1.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(1.0, 0.0)]), + num_qubits: 2, + }; + + pub static ref SQRT_SWAP: QuantumGate<'static> = QuantumGate { + name: "√SWAP", + matrix: matrix!([complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.5, 0.5), complex!(0.5, -0.5), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.5, -0.5), complex!(0.5, 0.5), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(1.0, 0.0)]), + num_qubits: 2, + }; + + pub static ref TOFFOLI: QuantumGate<'static> = QuantumGate { + name: "CCNOT", + matrix: matrix!( + [complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(1.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(1.0, 0.0), complex!(0.0, 0.0)] + ), + num_qubits: 3, + }; + + pub static ref FREDKIN: QuantumGate<'static> = QuantumGate { + name: "CSWAP", + matrix: matrix!( + [complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(1.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(1.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0)]; + [complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(0.0, 0.0), complex!(1.0, 0.0)] + ), + num_qubits: 3, + }; +} diff --git a/src/core/kernel.rs b/src/core/kernel.rs new file mode 100644 index 0000000..7f73458 --- /dev/null +++ b/src/core/kernel.rs @@ -0,0 +1,621 @@ +use crate::maths::simd::{ + apply_single_qubit_gate_simd, apply_single_qubit_gate_simd_parallel, SimdCapability, +}; +use crate::{complex, Complex, Matrix}; +use rayon::prelude::*; +use std::collections::HashSet; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum GateType { + Diagonal, + NonDiagonal, + Controlled, +} + +#[derive(Clone)] +pub struct Kernel { + pub matrix: Matrix<Complex<f64>>, + pub targets: Vec<usize>, + pub name: String, + pub gate_type: GateType, +} + +impl Kernel { + pub fn new(name: &str, matrix: Matrix<Complex<f64>>, targets: Vec<usize>) -> Self { + let gate_type = Self::detect_gate_type(name, &matrix); + Self { + matrix, + targets, + name: name.to_string(), + gate_type, + } + } + + fn detect_gate_type(name: &str, matrix: &Matrix<Complex<f64>>) -> GateType { + let diagonal_gates = [ + "Z", "S", "T", "Sdg", "Tdg", "Rz", "P", "U1", "CZ", "CP", "CRz", + ]; + if diagonal_gates.iter().any(|&g| name.starts_with(g)) { + return GateType::Diagonal; + } + + let controlled_gates = [ + "CNOT", "CZ", "SWAP", "CRx", "CRy", "CRz", "CP", "CCNOT", "CSWAP", + ]; + if controlled_gates.iter().any(|&g| name.starts_with(g)) { + return GateType::Controlled; + } + + if matrix.rows == 2 && matrix.cols == 2 { + let is_diag = matrix.data[1].real.abs() < 1e-10 + && matrix.data[1].imaginary.abs() < 1e-10 + && matrix.data[2].real.abs() < 1e-10 + && matrix.data[2].imaginary.abs() < 1e-10; + if is_diag { + return GateType::Diagonal; + } + } + + GateType::NonDiagonal + } + + pub fn num_qubits(&self) -> usize { + self.targets.len() + } + + pub fn target_set(&self) -> HashSet<usize> { + self.targets.iter().cloned().collect() + } + + pub fn shares_qubits(&self, other: &Kernel) -> bool { + self.targets.iter().any(|t| other.targets.contains(t)) + } + + pub fn commutes_with(&self, other: &Kernel) -> bool { + if !self.shares_qubits(other) { + return true; + } + + if self.gate_type == GateType::Diagonal && other.gate_type == GateType::Diagonal + && self.targets == other.targets { + return true; + } + + false + } + + pub fn can_fuse_with(&self, other: &Kernel) -> bool { + if self.targets.len() != 1 || other.targets.len() != 1 { + return false; + } + self.targets[0] == other.targets[0] + } + + pub fn fuse(&self, other: &Kernel) -> Option<Kernel> { + if !self.can_fuse_with(other) { + return None; + } + let fused_matrix = other.matrix.dot(&self.matrix)?; + let new_type = + if self.gate_type == GateType::Diagonal && other.gate_type == GateType::Diagonal { + GateType::Diagonal + } else { + GateType::NonDiagonal + }; + Some(Kernel { + matrix: fused_matrix, + targets: self.targets.clone(), + name: format!("{}+{}", self.name, other.name), + gate_type: new_type, + }) + } +} + +pub struct KernelBatch { + kernels: Vec<Kernel>, + num_qubits: usize, +} + +impl KernelBatch { + pub fn new(num_qubits: usize) -> Self { + Self { + kernels: Vec::new(), + num_qubits, + } + } + + pub fn add(&mut self, kernel: Kernel) { + self.kernels.push(kernel); + } + + pub fn len(&self) -> usize { + self.kernels.len() + } + + pub fn is_empty(&self) -> bool { + self.kernels.is_empty() + } + + pub fn kernels(&self) -> &[Kernel] { + &self.kernels + } + + pub fn optimize(&mut self) { + if self.kernels.len() < 2 { + return; + } + + let mut optimized: Vec<Kernel> = Vec::with_capacity(self.kernels.len()); + let mut i = 0; + + while i < self.kernels.len() { + let current = &self.kernels[i]; + + if i + 1 < self.kernels.len() { + let next = &self.kernels[i + 1]; + if let Some(fused) = current.fuse(next) { + optimized.push(fused); + i += 2; + continue; + } + } + + optimized.push(current.clone()); + i += 1; + } + + self.kernels = optimized; + } + + pub fn execute(&self, state: &mut Vec<Complex<f64>>) { + for kernel in &self.kernels { + *state = apply_kernel(state, kernel, self.num_qubits); + } + } + + pub fn execute_parallel(&self, state: &mut Vec<Complex<f64>>) { + for kernel in &self.kernels { + *state = apply_kernel_parallel(state, kernel, self.num_qubits); + } + } + + pub fn execute_simd(&self, state: &mut Vec<Complex<f64>>) { + for kernel in &self.kernels { + if kernel.targets.len() == 1 { + let gate = matrix_to_2x2(&kernel.matrix); + apply_single_qubit_gate_simd(state, &gate, kernel.targets[0], self.num_qubits); + } else { + *state = apply_kernel(state, kernel, self.num_qubits); + } + } + } + + pub fn execute_simd_parallel(&self, state: &mut Vec<Complex<f64>>) { + for kernel in &self.kernels { + if kernel.targets.len() == 1 && self.num_qubits >= 10 { + let gate = matrix_to_2x2(&kernel.matrix); + apply_single_qubit_gate_simd_parallel( + state, + &gate, + kernel.targets[0], + self.num_qubits, + ); + } else if kernel.targets.len() == 1 { + let gate = matrix_to_2x2(&kernel.matrix); + apply_single_qubit_gate_simd(state, &gate, kernel.targets[0], self.num_qubits); + } else { + *state = apply_kernel_parallel(state, kernel, self.num_qubits); + } + } + } + + pub fn simd_capability(&self) -> SimdCapability { + SimdCapability::detect() + } +} + +fn matrix_to_2x2(matrix: &Matrix<Complex<f64>>) -> [[Complex<f64>; 2]; 2] { + [ + [matrix.data[0], matrix.data[1]], + [matrix.data[2], matrix.data[3]], + ] +} + +fn apply_kernel(state: &[Complex<f64>], kernel: &Kernel, num_qubits: usize) -> Vec<Complex<f64>> { + let dim = 1 << num_qubits; + let g = kernel.targets.len(); + let gate_dim = 1 << g; + + let target_bits: Vec<usize> = kernel.targets.iter().map(|&t| num_qubits - 1 - t).collect(); + + let mut non_target_mask: usize = (1 << num_qubits) - 1; + for &pos in &target_bits { + non_target_mask &= !(1 << pos); + } + + let mut new_state = vec![complex!(0.0, 0.0); dim]; + + for (i, new_val) in new_state.iter_mut().enumerate() { + let mut target_idx = 0usize; + for (k, &pos) in target_bits.iter().enumerate() { + if (i >> pos) & 1 == 1 { + target_idx |= 1 << (g - 1 - k); + } + } + + let mut sum = complex!(0.0, 0.0); + + for j in 0..gate_dim { + let gate_elem = kernel.matrix.data[target_idx * gate_dim + j]; + + if gate_elem.real.abs() < 1e-15 && gate_elem.imaginary.abs() < 1e-15 { + continue; + } + + let mut source_idx = i & non_target_mask; + for (k, &pos) in target_bits.iter().enumerate() { + if (j >> (g - 1 - k)) & 1 == 1 { + source_idx |= 1 << pos; + } + } + + sum += gate_elem * state[source_idx]; + } + + *new_val = sum; + } + + new_state +} + +fn apply_kernel_parallel( + state: &[Complex<f64>], + kernel: &Kernel, + num_qubits: usize, +) -> Vec<Complex<f64>> { + let dim = 1 << num_qubits; + let g = kernel.targets.len(); + let gate_dim = 1 << g; + + let target_bits: Vec<usize> = kernel.targets.iter().map(|&t| num_qubits - 1 - t).collect(); + + let mut non_target_mask: usize = (1 << num_qubits) - 1; + for &pos in &target_bits { + non_target_mask &= !(1 << pos); + } + + (0..dim) + .into_par_iter() + .map(|i| { + let mut target_idx = 0usize; + for (k, &pos) in target_bits.iter().enumerate() { + if (i >> pos) & 1 == 1 { + target_idx |= 1 << (g - 1 - k); + } + } + + let mut sum = complex!(0.0, 0.0); + + for j in 0..gate_dim { + let gate_elem = kernel.matrix.data[target_idx * gate_dim + j]; + + if gate_elem.real.abs() < 1e-15 && gate_elem.imaginary.abs() < 1e-15 { + continue; + } + + let mut source_idx = i & non_target_mask; + for (k, &pos) in target_bits.iter().enumerate() { + if (j >> (g - 1 - k)) & 1 == 1 { + source_idx |= 1 << pos; + } + } + + sum += gate_elem * state[source_idx]; + } + + sum + }) + .collect() +} + +pub struct KernelBuilder { + num_qubits: usize, +} + +impl KernelBuilder { + pub fn new(num_qubits: usize) -> Self { + Self { num_qubits } + } + + pub fn num_qubits(&self) -> usize { + self.num_qubits + } +} + +#[derive(Clone)] +pub struct ExecutionLayer { + pub kernels: Vec<Kernel>, +} + +impl ExecutionLayer { + pub fn new() -> Self { + Self { + kernels: Vec::new(), + } + } + + pub fn can_add(&self, kernel: &Kernel) -> bool { + !self.kernels.iter().any(|k| k.shares_qubits(kernel)) + } + + pub fn add(&mut self, kernel: Kernel) { + self.kernels.push(kernel); + } + + pub fn affected_qubits(&self) -> HashSet<usize> { + self.kernels + .iter() + .flat_map(|k| k.targets.iter().cloned()) + .collect() + } +} + +impl Default for ExecutionLayer { + fn default() -> Self { + Self::new() + } +} + +pub struct StructureAwareKernelBatch { + kernels: Vec<Kernel>, + layers: Vec<ExecutionLayer>, + num_qubits: usize, + optimised: bool, +} + +impl StructureAwareKernelBatch { + pub fn new(num_qubits: usize) -> Self { + Self { + kernels: Vec::new(), + layers: Vec::new(), + num_qubits, + optimised: false, + } + } + + pub fn add(&mut self, kernel: Kernel) { + self.kernels.push(kernel); + self.optimised = false; + } + + pub fn len(&self) -> usize { + self.kernels.len() + } + + pub fn is_empty(&self) -> bool { + self.kernels.is_empty() + } + + pub fn kernels(&self) -> &[Kernel] { + &self.kernels + } + + pub fn layers(&self) -> &[ExecutionLayer] { + &self.layers + } + + pub fn num_layers(&self) -> usize { + self.layers.len() + } + + pub fn optimise(&mut self) { + if self.optimised || self.kernels.len() < 2 { + return; + } + + self.reorder_commuting_gates(); + self.multi_pass_fusion(); + self.build_execution_layers(); + self.optimised = true; + } + + fn reorder_commuting_gates(&mut self) { + let mut changed = true; + let mut iterations = 0; + const MAX_ITERATIONS: usize = 100; + + while changed && iterations < MAX_ITERATIONS { + changed = false; + iterations += 1; + + for i in 0..self.kernels.len().saturating_sub(1) { + let current = &self.kernels[i]; + let next = &self.kernels[i + 1]; + + if current.targets.len() == 1 + && next.targets.len() == 1 + && current.targets[0] != next.targets[0] + && current.commutes_with(next) + { + for j in (i + 2)..self.kernels.len() { + let candidate = &self.kernels[j]; + + if candidate.targets.len() == 1 + && candidate.targets[0] == current.targets[0] + { + let can_move = (i + 1..j).all(|k| { + let between = &self.kernels[k]; + !between.shares_qubits(current) || current.commutes_with(between) + }); + + if can_move && current.can_fuse_with(candidate) { + let kernel_to_move = self.kernels.remove(j); + self.kernels.insert(i + 1, kernel_to_move); + changed = true; + break; + } + } + } + } + } + } + } + + fn multi_pass_fusion(&mut self) { + let mut changed = true; + let mut iterations = 0; + const MAX_ITERATIONS: usize = 50; + + while changed && iterations < MAX_ITERATIONS { + changed = false; + iterations += 1; + + let mut new_kernels: Vec<Kernel> = Vec::with_capacity(self.kernels.len()); + let mut i = 0; + + while i < self.kernels.len() { + if i + 1 < self.kernels.len() { + let current = &self.kernels[i]; + let next = &self.kernels[i + 1]; + + if let Some(fused) = current.fuse(next) { + new_kernels.push(fused); + i += 2; + changed = true; + continue; + } + } + + new_kernels.push(self.kernels[i].clone()); + i += 1; + } + + self.kernels = new_kernels; + } + } + + fn build_execution_layers(&mut self) { + self.layers.clear(); + + for kernel in &self.kernels { + let mut placed = false; + + for layer in &mut self.layers { + if layer.can_add(kernel) { + layer.add(kernel.clone()); + placed = true; + break; + } + } + + if !placed { + let mut new_layer = ExecutionLayer::new(); + new_layer.add(kernel.clone()); + self.layers.push(new_layer); + } + } + } + + pub fn execute(&self, state: &mut Vec<Complex<f64>>) { + for kernel in &self.kernels { + *state = apply_kernel(state, kernel, self.num_qubits); + } + } + + pub fn execute_parallel(&self, state: &mut Vec<Complex<f64>>) { + for kernel in &self.kernels { + *state = apply_kernel_parallel(state, kernel, self.num_qubits); + } + } + + pub fn execute_layered(&self, state: &mut Vec<Complex<f64>>) { + for layer in &self.layers { + for kernel in &layer.kernels { + *state = apply_kernel(state, kernel, self.num_qubits); + } + } + } + + pub fn execute_layered_parallel(&self, state: &mut Vec<Complex<f64>>) { + for layer in &self.layers { + for kernel in &layer.kernels { + *state = apply_kernel_parallel(state, kernel, self.num_qubits); + } + } + } + + pub fn execute_simd(&self, state: &mut Vec<Complex<f64>>) { + for kernel in &self.kernels { + if kernel.targets.len() == 1 { + let gate = matrix_to_2x2(&kernel.matrix); + apply_single_qubit_gate_simd(state, &gate, kernel.targets[0], self.num_qubits); + } else { + *state = apply_kernel(state, kernel, self.num_qubits); + } + } + } + + pub fn execute_simd_parallel(&self, state: &mut Vec<Complex<f64>>) { + for kernel in &self.kernels { + if kernel.targets.len() == 1 && self.num_qubits >= 10 { + let gate = matrix_to_2x2(&kernel.matrix); + apply_single_qubit_gate_simd_parallel( + state, + &gate, + kernel.targets[0], + self.num_qubits, + ); + } else if kernel.targets.len() == 1 { + let gate = matrix_to_2x2(&kernel.matrix); + apply_single_qubit_gate_simd(state, &gate, kernel.targets[0], self.num_qubits); + } else { + *state = apply_kernel_parallel(state, kernel, self.num_qubits); + } + } + } + + pub fn stats(&self) -> KernelStats { + let single_qubit = self.kernels.iter().filter(|k| k.targets.len() == 1).count(); + let two_qubit = self.kernels.iter().filter(|k| k.targets.len() == 2).count(); + let multi_qubit = self.kernels.iter().filter(|k| k.targets.len() > 2).count(); + let diagonal = self + .kernels + .iter() + .filter(|k| k.gate_type == GateType::Diagonal) + .count(); + + KernelStats { + total_kernels: self.kernels.len(), + single_qubit, + two_qubit, + multi_qubit, + diagonal, + execution_layers: self.layers.len(), + } + } +} + +#[derive(Debug, Clone)] +pub struct KernelStats { + pub total_kernels: usize, + pub single_qubit: usize, + pub two_qubit: usize, + pub multi_qubit: usize, + pub diagonal: usize, + pub execution_layers: usize, +} + +impl std::fmt::Display for KernelStats { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Kernels: {} (1q: {}, 2q: {}, 3q+: {}, diag: {}), Layers: {}", + self.total_kernels, + self.single_qubit, + self.two_qubit, + self.multi_qubit, + self.diagonal, + self.execution_layers + ) + } +} diff --git a/src/core/mod.rs b/src/core/mod.rs new file mode 100644 index 0000000..c38cf38 --- /dev/null +++ b/src/core/mod.rs @@ -0,0 +1,17 @@ +pub mod circuit; +pub mod classical_components; +pub mod custom_gate; +pub mod gates; +pub mod kernel; +pub mod noise; +pub mod quantum_components; +pub mod runtime; + +pub use circuit::*; +pub use classical_components::*; +pub use custom_gate::*; +pub use gates::*; +pub use kernel::*; +pub use noise::*; +pub use quantum_components::*; +pub use runtime::*; diff --git a/src/core/noise.rs b/src/core/noise.rs new file mode 100644 index 0000000..8d56953 --- /dev/null +++ b/src/core/noise.rs @@ -0,0 +1,559 @@ +use crate::{complex, Complex, Matrix}; + +#[derive(Clone, Debug)] +pub struct KrausOperator { + pub matrix: Matrix<Complex<f64>>, + pub name: String, +} + +impl KrausOperator { + pub fn new(name: &str, matrix: Matrix<Complex<f64>>) -> Self { + Self { + matrix, + name: name.to_string(), + } + } +} + +#[derive(Clone, Debug)] +pub struct NoiseChannel { + pub name: String, + pub operators: Vec<KrausOperator>, + pub num_qubits: usize, +} + +impl NoiseChannel { + pub fn new(name: &str, operators: Vec<KrausOperator>, num_qubits: usize) -> Self { + Self { + name: name.to_string(), + operators, + num_qubits, + } + } + + pub fn depolarising(p: f64) -> Self { + let sqrt_1_p = (1.0 - p).sqrt(); + let sqrt_p3 = (p / 3.0).sqrt(); + + let k0 = Matrix::new( + 2, + 2, + vec![ + complex!(sqrt_1_p, 0.0), + complex!(0.0, 0.0), + complex!(0.0, 0.0), + complex!(sqrt_1_p, 0.0), + ], + ); + + let k1 = Matrix::new( + 2, + 2, + vec![ + complex!(0.0, 0.0), + complex!(sqrt_p3, 0.0), + complex!(sqrt_p3, 0.0), + complex!(0.0, 0.0), + ], + ); + + let k2 = Matrix::new( + 2, + 2, + vec![ + complex!(0.0, 0.0), + complex!(0.0, -sqrt_p3), + complex!(0.0, sqrt_p3), + complex!(0.0, 0.0), + ], + ); + + let k3 = Matrix::new( + 2, + 2, + vec![ + complex!(sqrt_p3, 0.0), + complex!(0.0, 0.0), + complex!(0.0, 0.0), + complex!(-sqrt_p3, 0.0), + ], + ); + + Self::new( + "Depolarising", + vec![ + KrausOperator::new("K0", k0), + KrausOperator::new("K1(X)", k1), + KrausOperator::new("K2(Y)", k2), + KrausOperator::new("K3(Z)", k3), + ], + 1, + ) + } + + pub fn amplitude_damping(gamma: f64) -> Self { + let sqrt_gamma = gamma.sqrt(); + let sqrt_1_gamma = (1.0 - gamma).sqrt(); + + let k0 = Matrix::new( + 2, + 2, + vec![ + complex!(1.0, 0.0), + complex!(0.0, 0.0), + complex!(0.0, 0.0), + complex!(sqrt_1_gamma, 0.0), + ], + ); + + let k1 = Matrix::new( + 2, + 2, + vec![ + complex!(0.0, 0.0), + complex!(sqrt_gamma, 0.0), + complex!(0.0, 0.0), + complex!(0.0, 0.0), + ], + ); + + Self::new( + "AmplitudeDamping", + vec![ + KrausOperator::new("K0", k0), + KrausOperator::new("K1", k1), + ], + 1, + ) + } + + pub fn phase_damping(gamma: f64) -> Self { + let sqrt_gamma = gamma.sqrt(); + let sqrt_1_gamma = (1.0 - gamma).sqrt(); + + let k0 = Matrix::new( + 2, + 2, + vec![ + complex!(1.0, 0.0), + complex!(0.0, 0.0), + complex!(0.0, 0.0), + complex!(sqrt_1_gamma, 0.0), + ], + ); + + let k1 = Matrix::new( + 2, + 2, + vec![ + complex!(0.0, 0.0), + complex!(0.0, 0.0), + complex!(0.0, 0.0), + complex!(sqrt_gamma, 0.0), + ], + ); + + Self::new( + "PhaseDamping", + vec![ + KrausOperator::new("K0", k0), + KrausOperator::new("K1", k1), + ], + 1, + ) + } + + pub fn bit_flip(p: f64) -> Self { + let sqrt_1_p = (1.0 - p).sqrt(); + let sqrt_p = p.sqrt(); + + let k0 = Matrix::new( + 2, + 2, + vec![ + complex!(sqrt_1_p, 0.0), + complex!(0.0, 0.0), + complex!(0.0, 0.0), + complex!(sqrt_1_p, 0.0), + ], + ); + + let k1 = Matrix::new( + 2, + 2, + vec![ + complex!(0.0, 0.0), + complex!(sqrt_p, 0.0), + complex!(sqrt_p, 0.0), + complex!(0.0, 0.0), + ], + ); + + Self::new( + "BitFlip", + vec![ + KrausOperator::new("K0(I)", k0), + KrausOperator::new("K1(X)", k1), + ], + 1, + ) + } + + pub fn phase_flip(p: f64) -> Self { + let sqrt_1_p = (1.0 - p).sqrt(); + let sqrt_p = p.sqrt(); + + let k0 = Matrix::new( + 2, + 2, + vec![ + complex!(sqrt_1_p, 0.0), + complex!(0.0, 0.0), + complex!(0.0, 0.0), + complex!(sqrt_1_p, 0.0), + ], + ); + + let k1 = Matrix::new( + 2, + 2, + vec![ + complex!(sqrt_p, 0.0), + complex!(0.0, 0.0), + complex!(0.0, 0.0), + complex!(-sqrt_p, 0.0), + ], + ); + + Self::new( + "PhaseFlip", + vec![ + KrausOperator::new("K0(I)", k0), + KrausOperator::new("K1(Z)", k1), + ], + 1, + ) + } + + pub fn bit_phase_flip(p: f64) -> Self { + let sqrt_1_p = (1.0 - p).sqrt(); + let sqrt_p = p.sqrt(); + + let k0 = Matrix::new( + 2, + 2, + vec![ + complex!(sqrt_1_p, 0.0), + complex!(0.0, 0.0), + complex!(0.0, 0.0), + complex!(sqrt_1_p, 0.0), + ], + ); + + let k1 = Matrix::new( + 2, + 2, + vec![ + complex!(0.0, 0.0), + complex!(0.0, -sqrt_p), + complex!(0.0, sqrt_p), + complex!(0.0, 0.0), + ], + ); + + Self::new( + "BitPhaseFlip", + vec![ + KrausOperator::new("K0(I)", k0), + KrausOperator::new("K1(Y)", k1), + ], + 1, + ) + } + + pub fn generalised_amplitude_damping(p: f64, gamma: f64) -> Self { + let sqrt_p = p.sqrt(); + let sqrt_1_p = (1.0 - p).sqrt(); + let sqrt_gamma = gamma.sqrt(); + let sqrt_1_gamma = (1.0 - gamma).sqrt(); + + let k0 = Matrix::new( + 2, + 2, + vec![ + complex!(sqrt_p, 0.0), + complex!(0.0, 0.0), + complex!(0.0, 0.0), + complex!(sqrt_p * sqrt_1_gamma, 0.0), + ], + ); + + let k1 = Matrix::new( + 2, + 2, + vec![ + complex!(0.0, 0.0), + complex!(sqrt_p * sqrt_gamma, 0.0), + complex!(0.0, 0.0), + complex!(0.0, 0.0), + ], + ); + + let k2 = Matrix::new( + 2, + 2, + vec![ + complex!(sqrt_1_p * sqrt_1_gamma, 0.0), + complex!(0.0, 0.0), + complex!(0.0, 0.0), + complex!(sqrt_1_p, 0.0), + ], + ); + + let k3 = Matrix::new( + 2, + 2, + vec![ + complex!(0.0, 0.0), + complex!(0.0, 0.0), + complex!(sqrt_1_p * sqrt_gamma, 0.0), + complex!(0.0, 0.0), + ], + ); + + Self::new( + "GeneralisedAmplitudeDamping", + vec![ + KrausOperator::new("K0", k0), + KrausOperator::new("K1", k1), + KrausOperator::new("K2", k2), + KrausOperator::new("K3", k3), + ], + 1, + ) + } +} + +#[derive(Clone)] +pub struct DensityMatrix { + pub data: Vec<Complex<f64>>, + pub dim: usize, + pub num_qubits: usize, +} + +impl DensityMatrix { + pub fn new(num_qubits: usize) -> Self { + let dim = 1 << num_qubits; + let mut data = vec![complex!(0.0, 0.0); dim * dim]; + data[0] = complex!(1.0, 0.0); + Self { + data, + dim, + num_qubits, + } + } + + pub fn from_state_vector(state: &[Complex<f64>]) -> Self { + let dim = state.len(); + let num_qubits = (dim as f64).log2() as usize; + let mut data = vec![complex!(0.0, 0.0); dim * dim]; + + for i in 0..dim { + for j in 0..dim { + data[i * dim + j] = state[i] * state[j].get_conjugate(); + } + } + + Self { + data, + dim, + num_qubits, + } + } + + pub fn get(&self, row: usize, col: usize) -> Complex<f64> { + self.data[row * self.dim + col] + } + + pub fn set(&mut self, row: usize, col: usize, value: Complex<f64>) { + self.data[row * self.dim + col] = value; + } + + pub fn trace(&self) -> Complex<f64> { + let mut sum = complex!(0.0, 0.0); + for i in 0..self.dim { + sum += self.get(i, i); + } + sum + } + + pub fn purity(&self) -> f64 { + let mut sum = complex!(0.0, 0.0); + for i in 0..self.dim { + for j in 0..self.dim { + let rho_ij = self.get(i, j); + let rho_ji = self.get(j, i); + sum += rho_ij * rho_ji; + } + } + sum.real + } + + pub fn is_pure(&self, tolerance: f64) -> bool { + (self.purity() - 1.0).abs() < tolerance + } + + pub fn probabilities(&self) -> Vec<f64> { + (0..self.dim).map(|i| self.get(i, i).real).collect() + } + + pub fn apply_unitary(&mut self, gate: &Matrix<Complex<f64>>, targets: &[usize]) { + let g = targets.len(); + let gate_dim = 1 << g; + + let target_bits: Vec<usize> = targets + .iter() + .map(|&t| self.num_qubits - 1 - t) + .collect(); + + let mut non_target_mask: usize = (1 << self.num_qubits) - 1; + for &pos in &target_bits { + non_target_mask &= !(1 << pos); + } + + let mut new_data = vec![complex!(0.0, 0.0); self.dim * self.dim]; + + for i in 0..self.dim { + for j in 0..self.dim { + let mut sum = complex!(0.0, 0.0); + + for k in 0..gate_dim { + for l in 0..gate_dim { + let mut src_i = i & non_target_mask; + let mut src_j = j & non_target_mask; + + for (idx, &pos) in target_bits.iter().enumerate() { + if (k >> (g - 1 - idx)) & 1 == 1 { + src_i |= 1 << pos; + } + if (l >> (g - 1 - idx)) & 1 == 1 { + src_j |= 1 << pos; + } + } + + let mut tgt_i = 0usize; + let mut tgt_j = 0usize; + for (idx, &pos) in target_bits.iter().enumerate() { + if (i >> pos) & 1 == 1 { + tgt_i |= 1 << (g - 1 - idx); + } + if (j >> pos) & 1 == 1 { + tgt_j |= 1 << (g - 1 - idx); + } + } + + let u_ik = gate.data[tgt_i * gate_dim + k]; + let u_jl_dag = gate.data[tgt_j * gate_dim + l].get_conjugate(); + let rho_kl = self.get(src_i, src_j); + + sum += u_ik * rho_kl * u_jl_dag; + } + } + + new_data[i * self.dim + j] = sum; + } + } + + self.data = new_data; + } + + pub fn apply_noise_channel(&mut self, channel: &NoiseChannel, target: usize) { + if channel.num_qubits != 1 { + panic!("Only single-qubit noise channels are currently supported"); + } + + let target_bit = self.num_qubits - 1 - target; + let mut new_data = vec![complex!(0.0, 0.0); self.dim * self.dim]; + + for kraus in &channel.operators { + let k = &kraus.matrix; + + for i in 0..self.dim { + for j in 0..self.dim { + let i_target = (i >> target_bit) & 1; + let j_target = (j >> target_bit) & 1; + + for ki in 0..2 { + for kj in 0..2 { + let src_i = (i & !(1 << target_bit)) | (ki << target_bit); + let src_j = (j & !(1 << target_bit)) | (kj << target_bit); + + let k_elem = k.data[i_target * 2 + ki]; + let k_dag_elem = k.data[j_target * 2 + kj].get_conjugate(); + let rho_elem = self.get(src_i, src_j); + + new_data[i * self.dim + j] += k_elem * rho_elem * k_dag_elem; + } + } + } + } + } + + self.data = new_data; + } + + pub fn measure_probability(&self, qubit: usize, outcome: usize) -> f64 { + let target_bit = self.num_qubits - 1 - qubit; + let mut prob = 0.0; + + for i in 0..self.dim { + if (i >> target_bit) & 1 == outcome { + prob += self.get(i, i).real; + } + } + + prob + } + + pub fn fidelity_with_pure_state(&self, state: &[Complex<f64>]) -> f64 { + let mut sum = complex!(0.0, 0.0); + + for i in 0..self.dim { + for j in 0..self.dim { + sum += state[i].get_conjugate() * self.get(i, j) * state[j]; + } + } + + sum.real + } +} + +impl std::fmt::Display for DensityMatrix { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "DensityMatrix ({} qubits, {}×{}):", self.num_qubits, self.dim, self.dim)?; + writeln!(f, " Trace: {:.6}", self.trace().real)?; + writeln!(f, " Purity: {:.6}", self.purity())?; + writeln!(f, " Pure: {}", self.is_pure(1e-10))?; + writeln!(f, " Probabilities: {:?}", self.probabilities())?; + Ok(()) + } +} + +impl std::fmt::Debug for DensityMatrix { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "DensityMatrix {}×{}:", self.dim, self.dim)?; + for i in 0..self.dim { + write!(f, " [")?; + for j in 0..self.dim { + let val = self.get(i, j); + if j > 0 { + write!(f, ", ")?; + } + write!(f, "{:.4}+{:.4}i", val.real, val.imaginary)?; + } + writeln!(f, "]")?; + } + Ok(()) + } +} + diff --git a/src/core/quantum_components.rs b/src/core/quantum_components.rs new file mode 100644 index 0000000..f07b8ef --- /dev/null +++ b/src/core/quantum_components.rs @@ -0,0 +1,318 @@ +use crate::{column_vector, complex, ColumnVector, Complex, Float, Matrix, Vector, VectorMatrix}; +use core::{fmt, ops}; + +#[macro_export] +macro_rules! count { + () => { 0 }; + ($head:expr $(,$tail:expr)*) => { 1 + count!($( $tail ),*) }; +} + +#[macro_export] +macro_rules! qubit { + ($(($re:expr, $im:expr)),*) => { + { + let mut vector = Vec::new(); + $( + vector.push(complex!($re, $im)); + )* + QuantumBit::new(vector) + } + }; +} + +#[macro_export] +macro_rules! quantum_register { + ($($bit:expr),*) => { + { + const N: usize = count!($($bit),*); + let mut bits: [QuantumBit; N] = [$($bit),*]; + QuantumRegister::from(&mut bits) + } + }; +} + +pub type QuantumState = ColumnVector<Complex<f64>>; +impl QuantumState { + pub fn state_0() -> QuantumState { + column_vector![complex!(1.0, 0.0), complex!(0.0, 0.0)] + } + + pub fn state_1() -> QuantumState { + column_vector![complex!(0.0, 0.0), complex!(1.0, 0.0)] + } +} + +fn identity_matrix<T: Float>(size: usize) -> Matrix<T> { + let mut data = vec![T::zero(); size * size]; + for i in 0..size { + data[i * size + i] = T::one(); + } + Matrix::new(size, size, data) +} + +#[derive(Clone)] +pub struct QuantumBit<'a> { + state: QuantumState, + name: &'a str, +} + +#[derive(Clone)] +pub struct QuantumRegister<'a> { + state_vector: QuantumState, + name: &'a str, + qubits: Vec<QuantumBit<'a>>, +} + +#[derive(Clone)] +pub struct QuantumGate<'a> { + pub name: &'a str, + pub matrix: Matrix<Complex<f64>>, + pub num_qubits: usize, +} + +impl<'a> QuantumGate<'a> { + pub fn new(name: &'a str, matrix: Matrix<Complex<f64>>, num_qubits: usize) -> Self { + let expected_dim = 1 << num_qubits; + assert_eq!( + matrix.rows, expected_dim, + "Gate matrix rows must be 2^num_qubits" + ); + assert_eq!( + matrix.cols, expected_dim, + "Gate matrix cols must be 2^num_qubits" + ); + QuantumGate { + name, + matrix, + num_qubits, + } + } + + pub fn from_matrix(name: &'a str, matrix: Matrix<Complex<f64>>) -> Self { + assert_eq!(matrix.rows, matrix.cols, "Gate matrix must be square"); + let dim = matrix.rows; + assert!( + dim > 0 && (dim & (dim - 1)) == 0, + "Matrix dimension must be a power of 2" + ); + let num_qubits = (dim as f64).log2() as usize; + QuantumGate { + name, + matrix, + num_qubits, + } + } +} + +impl<'a> QuantumBit<'a> { + pub fn new(name: &'a str, state: QuantumState) -> QuantumBit<'a> { + QuantumBit { name, state } + } + + pub fn get_state(&self) -> QuantumState { + self.state.clone() + } + + pub fn get_name(&self) -> &'a str { + self.name + } +} + +impl<'a> QuantumRegister<'a> { + pub fn new(name: &'a str, names: &[&'a str]) -> QuantumRegister<'a> { + let mut bits: Vec<QuantumBit<'a>> = Vec::new(); + for &name in names { + bits.push(QuantumBit::new(name, QuantumState::state_0())) + } + + QuantumRegister::from(name, &mut bits) + } + + pub fn from(name: &'a str, bits: &mut [QuantumBit<'a>]) -> QuantumRegister<'a> { + let mut register = QuantumRegister { + name, + qubits: bits.to_vec(), + state_vector: ColumnVector::new(vec![]), + }; + + register.update(); + register + } + + fn update(&mut self) { + let matrices: Vec<Matrix<Complex<f64>>> = self + .qubits + .iter() + .map(|qubit| qubit.state.to_matrix()) + .collect(); + let mut new_result = matrices[0].clone(); + for matrix in &matrices[1..] { + new_result = new_result.kronecker(matrix); + } + + self.state_vector = ColumnVector::from_matrix(&new_result); + } + + pub fn get_bits(&self) -> Vec<QuantumBit<'_>> { + self.qubits.clone() + } + + pub fn get_state(&self) -> QuantumState { + self.state_vector.clone() + } + + pub fn get_name(&self) -> &'a str { + self.name + } + + pub fn num_qubits(&self) -> usize { + self.qubits.len() + } + + pub fn apply_gate(&mut self, gate: &QuantumGate, targets: &[usize]) { + let n = self.num_qubits(); + + assert_eq!( + gate.num_qubits, + targets.len(), + "Number of target qubits must match gate's qubit count" + ); + for &t in targets { + assert!( + t < n, + "Target qubit index {} out of range for {}-qubit register", + t, + n + ); + } + + let mut sorted_targets = targets.to_vec(); + sorted_targets.sort(); + for i in 1..sorted_targets.len() { + assert_ne!( + sorted_targets[i], + sorted_targets[i - 1], + "Duplicate target qubit indices are not allowed" + ); + } + + let full_operator = self.build_full_operator(gate, targets); + + self.state_vector = self + .state_vector + .mul_matrix(&full_operator) + .expect("Matrix multiplication failed during gate application"); + } + + fn build_full_operator(&self, gate: &QuantumGate, targets: &[usize]) -> Matrix<Complex<f64>> { + let n = self.num_qubits(); + let g = gate.num_qubits; + let dim = 1 << n; + + let mut contiguous = true; + for i in 1..targets.len() { + if targets[i] != targets[i - 1] + 1 { + contiguous = false; + break; + } + } + + if contiguous && g == n { + return gate.matrix.clone(); + } + + if contiguous { + return self.build_contiguous_operator(gate, targets[0]); + } + + let mut result = Matrix::new(dim, dim, vec![complex!(0.0, 0.0); dim * dim]); + + for col in 0..dim { + for row in 0..dim { + let mut target_row_bits = 0usize; + let mut target_col_bits = 0usize; + + for (i, &t) in targets.iter().enumerate() { + let qubit_pos = n - 1 - t; + if (row >> qubit_pos) & 1 == 1 { + target_row_bits |= 1 << (g - 1 - i); + } + if (col >> qubit_pos) & 1 == 1 { + target_col_bits |= 1 << (g - 1 - i); + } + } + + let mut non_target_match = true; + for q in 0..n { + if !targets.contains(&q) { + let qubit_pos = n - 1 - q; + if ((row >> qubit_pos) & 1) != ((col >> qubit_pos) & 1) { + non_target_match = false; + break; + } + } + } + + if non_target_match { + result.set(row, col, gate.matrix.get(target_row_bits, target_col_bits)); + } + } + } + + result + } + + fn build_contiguous_operator( + &self, + gate: &QuantumGate, + start_idx: usize, + ) -> Matrix<Complex<f64>> { + let n = self.num_qubits(); + let g = gate.num_qubits; + + let mut result: Option<Matrix<Complex<f64>>> = None; + + for i in 0..n { + let part: Matrix<Complex<f64>> = if i == start_idx { + gate.matrix.clone() + } else if i > start_idx && i < start_idx + g { + continue; + } else { + identity_matrix(2) + }; + + result = Some(match result { + None => part, + Some(r) => r.kronecker(&part), + }); + } + + result.unwrap_or_else(|| identity_matrix(1 << n)) + } + + pub fn apply_gates(&mut self, operations: &[(&QuantumGate, &[usize])]) { + for (gate, targets) in operations { + self.apply_gate(gate, targets); + } + } +} + +impl<'a> ops::Index<usize> for QuantumRegister<'a> { + type Output = QuantumBit<'a>; + + fn index(&self, index: usize) -> &Self::Output { + &self.qubits[index] + } +} + +impl<'a> ops::IndexMut<usize> for QuantumRegister<'a> { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.qubits[index] + } +} + +impl<'a> fmt::Display for QuantumGate<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name) + } +} diff --git a/src/core/runtime.rs b/src/core/runtime.rs new file mode 100644 index 0000000..6ea7b76 --- /dev/null +++ b/src/core/runtime.rs @@ -0,0 +1,585 @@ +use super::{ + GateOp, Kernel, KernelBatch, QuantumGate, QuantumRegister, QuantumState, + StructureAwareKernelBatch, +}; +use crate::gates::{ + cp_matrix, crx_matrix, cry_matrix, crz_matrix, p_matrix, rx_matrix, ry_matrix, rz_matrix, + u1_matrix, u2_matrix, u3_matrix, CNOT, CZ, FREDKIN, HADAMARD, PAULI_X, PAULI_Y, PAULI_Z, + SDG_GATE, SWAP, SXDG_GATE, SX_GATE, S_GATE, TDG_GATE, TOFFOLI, T_GATE, +}; +use crate::maths::simd::{apply_single_qubit_gate_simd, apply_single_qubit_gate_simd_parallel}; +use crate::maths::vector::Vector; +use crate::{complex, Complex, Matrix}; +use rayon::prelude::*; + +const PARALLEL_THRESHOLD: usize = 8; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct RuntimeConfig { + pub parallel: bool, + pub simd: bool, + pub batched: bool, + pub structure_aware: bool, + pub parallel_threshold: usize, +} + +impl RuntimeConfig { + pub fn new() -> Self { + Self { + parallel: false, + simd: false, + batched: false, + structure_aware: false, + parallel_threshold: PARALLEL_THRESHOLD, + } + } + + pub fn parallel(mut self) -> Self { + self.parallel = true; + self + } + + pub fn simd(mut self) -> Self { + self.simd = true; + self + } + + pub fn batched(mut self) -> Self { + self.batched = true; + self + } + + pub fn structure_aware(mut self) -> Self { + self.structure_aware = true; + self + } + + pub fn with_threshold(mut self, threshold: usize) -> Self { + self.parallel_threshold = threshold; + self + } + + pub fn optimal() -> Self { + Self::new().structure_aware().simd().parallel() + } + + pub fn compute(&self, num_qubits: usize, operations: &[GateOp]) -> QuantumState { + let dim = 1 << num_qubits; + let mut state: Vec<Complex<f64>> = vec![complex!(0.0, 0.0); dim]; + state[0] = complex!(1.0, 0.0); + + let use_parallel = self.parallel && num_qubits >= self.parallel_threshold; + + if self.structure_aware { + let mut batch = Runtime::build_structure_aware_batch(num_qubits, operations); + batch.optimise(); + self.execute_kernels(&mut state, batch.kernels(), num_qubits, use_parallel); + } else if self.batched { + let mut batch = Runtime::build_kernel_batch(num_qubits, operations); + batch.optimize(); + self.execute_kernels(&mut state, batch.kernels(), num_qubits, use_parallel); + } else { + let batch = Runtime::build_kernel_batch(num_qubits, operations); + self.execute_kernels(&mut state, batch.kernels(), num_qubits, use_parallel); + } + + QuantumState::new(state) + } + + fn execute_kernels( + &self, + state: &mut Vec<Complex<f64>>, + kernels: &[Kernel], + num_qubits: usize, + use_parallel: bool, + ) { + for kernel in kernels { + if self.simd && kernel.targets.len() == 1 { + let gate = matrix_to_2x2(&kernel.matrix); + if use_parallel { + apply_single_qubit_gate_simd_parallel( + state, + &gate, + kernel.targets[0], + num_qubits, + ); + } else { + apply_single_qubit_gate_simd(state, &gate, kernel.targets[0], num_qubits); + } + } else if use_parallel { + *state = apply_gate_parallel(state, &kernel.matrix, &kernel.targets, num_qubits); + } else { + *state = apply_kernel_direct(state, kernel, num_qubits); + } + } + } +} + +impl std::fmt::Display for RuntimeConfig { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut features = Vec::new(); + if self.structure_aware { + features.push("structure-aware"); + } + if self.batched && !self.structure_aware { + features.push("batched"); + } + if self.simd { + features.push("SIMD"); + } + if self.parallel { + features.push("parallel"); + } + if features.is_empty() { + features.push("basic"); + } + write!(f, "Runtime[{}]", features.join("+")) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum Runtime { + #[default] + BasicRT, + BasicRTMT, + BatchedRT, + BatchedRTMT, + SimdRT, + SimdRTMT, + StructureAwareRT, + StructureAwareMT, + WFEvolution, + WFEvolutionMT, + GPUAccelerated, + Custom(RuntimeConfig), +} + +impl Runtime { + pub fn custom() -> RuntimeConfig { + RuntimeConfig::new() + } + + pub fn optimal() -> RuntimeConfig { + RuntimeConfig::optimal() + } + + pub fn to_config(&self) -> RuntimeConfig { + match self { + Runtime::BasicRT => RuntimeConfig::new(), + Runtime::BasicRTMT => RuntimeConfig::new().parallel(), + Runtime::BatchedRT => RuntimeConfig::new().batched(), + Runtime::BatchedRTMT => RuntimeConfig::new().batched().parallel(), + Runtime::SimdRT => RuntimeConfig::new().batched().simd(), + Runtime::SimdRTMT => RuntimeConfig::new().batched().simd().parallel(), + Runtime::StructureAwareRT => RuntimeConfig::new().structure_aware().simd(), + Runtime::StructureAwareMT => RuntimeConfig::new().structure_aware().simd().parallel(), + Runtime::Custom(config) => *config, + _ => RuntimeConfig::new(), + } + } + + pub fn compute(&self, num_qubits: usize, operations: &[GateOp]) -> QuantumState { + match self { + Runtime::BasicRT => Self::compute_basic(num_qubits, operations), + Runtime::BasicRTMT => Self::compute_basic_mt(num_qubits, operations), + Runtime::Custom(config) => config.compute(num_qubits, operations), + Runtime::WFEvolution => { + unimplemented!("WFEvolution (Schrödinger equation) runtime not yet implemented") + } + Runtime::WFEvolutionMT => { + unimplemented!( + "WFEvolutionMT (multi-threaded Schrödinger) runtime not yet implemented" + ) + } + Runtime::GPUAccelerated => { + unimplemented!("GPUAccelerated runtime not yet implemented") + } + _ => self.to_config().compute(num_qubits, operations), + } + } + + pub fn build_kernel_batch(num_qubits: usize, operations: &[GateOp]) -> KernelBatch { + let mut batch = KernelBatch::new(num_qubits); + + for op in operations { + if let Some(kernel) = Self::op_to_kernel(op) { + batch.add(kernel); + } + } + + batch + } + + fn op_to_kernel(op: &GateOp) -> Option<Kernel> { + let (matrix, targets, name): (Matrix<Complex<f64>>, Vec<usize>, &str) = match op { + GateOp::H(t) => (HADAMARD.matrix.clone(), vec![*t], "H"), + GateOp::X(t) => (PAULI_X.matrix.clone(), vec![*t], "X"), + GateOp::Y(t) => (PAULI_Y.matrix.clone(), vec![*t], "Y"), + GateOp::Z(t) => (PAULI_Z.matrix.clone(), vec![*t], "Z"), + GateOp::S(t) => (S_GATE.matrix.clone(), vec![*t], "S"), + GateOp::T(t) => (T_GATE.matrix.clone(), vec![*t], "T"), + GateOp::Sdg(t) => (SDG_GATE.matrix.clone(), vec![*t], "Sdg"), + GateOp::Tdg(t) => (TDG_GATE.matrix.clone(), vec![*t], "Tdg"), + GateOp::Sx(t) => (SX_GATE.matrix.clone(), vec![*t], "Sx"), + GateOp::Sxdg(t) => (SXDG_GATE.matrix.clone(), vec![*t], "Sxdg"), + GateOp::Rx(t, theta) => (rx_matrix(*theta), vec![*t], "Rx"), + GateOp::Ry(t, theta) => (ry_matrix(*theta), vec![*t], "Ry"), + GateOp::Rz(t, theta) => (rz_matrix(*theta), vec![*t], "Rz"), + GateOp::P(t, theta) => (p_matrix(*theta), vec![*t], "P"), + GateOp::U1(t, lambda) => (u1_matrix(*lambda), vec![*t], "U1"), + GateOp::U2(t, phi, lambda) => (u2_matrix(*phi, *lambda), vec![*t], "U2"), + GateOp::U3(t, theta, phi, lambda) => (u3_matrix(*theta, *phi, *lambda), vec![*t], "U3"), + GateOp::CNOT(c, t) => (CNOT.matrix.clone(), vec![*c, *t], "CNOT"), + GateOp::CZ(c, t) => (CZ.matrix.clone(), vec![*c, *t], "CZ"), + GateOp::SWAP(a, b) => (SWAP.matrix.clone(), vec![*a, *b], "SWAP"), + GateOp::CRx(c, t, theta) => (crx_matrix(*theta), vec![*c, *t], "CRx"), + GateOp::CRy(c, t, theta) => (cry_matrix(*theta), vec![*c, *t], "CRy"), + GateOp::CRz(c, t, theta) => (crz_matrix(*theta), vec![*c, *t], "CRz"), + GateOp::CP(c, t, theta) => (cp_matrix(*theta), vec![*c, *t], "CP"), + GateOp::CCNOT(c1, c2, t) => (TOFFOLI.matrix.clone(), vec![*c1, *c2, *t], "CCNOT"), + GateOp::CSWAP(c, t1, t2) => (FREDKIN.matrix.clone(), vec![*c, *t1, *t2], "CSWAP"), + GateOp::Measure(_, _) => return None, + GateOp::Custom(gate, tgts) => { + let qg = gate.to_quantum_gate(); + (qg.matrix, tgts.clone(), "Custom") + } + }; + + Some(Kernel::new(name, matrix, targets)) + } + + pub fn build_structure_aware_batch( + num_qubits: usize, + operations: &[GateOp], + ) -> StructureAwareKernelBatch { + let mut batch = StructureAwareKernelBatch::new(num_qubits); + + for op in operations { + if let Some(kernel) = Self::op_to_kernel(op) { + batch.add(kernel); + } + } + + batch + } + + fn compute_basic(num_qubits: usize, operations: &[GateOp]) -> QuantumState { + let names: Vec<String> = (0..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, + ); + + for op in operations { + match op { + // Clifford gates + 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::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]), + + // Non-Clifford fixed gates + GateOp::T(t) => register.apply_gate(&T_GATE, &[*t]), + GateOp::Sdg(t) => register.apply_gate(&SDG_GATE, &[*t]), + GateOp::Tdg(t) => register.apply_gate(&TDG_GATE, &[*t]), + GateOp::Sx(t) => register.apply_gate(&SX_GATE, &[*t]), + GateOp::Sxdg(t) => register.apply_gate(&SXDG_GATE, &[*t]), + + // Parametric single-qubit gates (non-Clifford for most angles) + GateOp::Rx(t, theta) => { + let gate = QuantumGate { + name: "Rx", + matrix: rx_matrix(*theta), + num_qubits: 1, + }; + register.apply_gate(&gate, &[*t]); + } + GateOp::Ry(t, theta) => { + let gate = QuantumGate { + name: "Ry", + matrix: ry_matrix(*theta), + num_qubits: 1, + }; + register.apply_gate(&gate, &[*t]); + } + GateOp::Rz(t, theta) => { + let gate = QuantumGate { + name: "Rz", + matrix: rz_matrix(*theta), + num_qubits: 1, + }; + register.apply_gate(&gate, &[*t]); + } + GateOp::P(t, theta) => { + let gate = QuantumGate { + name: "P", + matrix: p_matrix(*theta), + num_qubits: 1, + }; + register.apply_gate(&gate, &[*t]); + } + GateOp::U1(t, lambda) => { + let gate = QuantumGate { + name: "U1", + matrix: u1_matrix(*lambda), + num_qubits: 1, + }; + register.apply_gate(&gate, &[*t]); + } + GateOp::U2(t, phi, lambda) => { + let gate = QuantumGate { + name: "U2", + matrix: u2_matrix(*phi, *lambda), + num_qubits: 1, + }; + register.apply_gate(&gate, &[*t]); + } + GateOp::U3(t, theta, phi, lambda) => { + let gate = QuantumGate { + name: "U3", + matrix: u3_matrix(*theta, *phi, *lambda), + num_qubits: 1, + }; + register.apply_gate(&gate, &[*t]); + } + + // Controlled parametric gates + GateOp::CRx(c, t, theta) => { + let gate = QuantumGate { + name: "CRx", + matrix: crx_matrix(*theta), + num_qubits: 2, + }; + register.apply_gate(&gate, &[*c, *t]); + } + GateOp::CRy(c, t, theta) => { + let gate = QuantumGate { + name: "CRy", + matrix: cry_matrix(*theta), + num_qubits: 2, + }; + register.apply_gate(&gate, &[*c, *t]); + } + GateOp::CRz(c, t, theta) => { + let gate = QuantumGate { + name: "CRz", + matrix: crz_matrix(*theta), + num_qubits: 2, + }; + register.apply_gate(&gate, &[*c, *t]); + } + GateOp::CP(c, t, theta) => { + let gate = QuantumGate { + name: "CP", + matrix: cp_matrix(*theta), + num_qubits: 2, + }; + register.apply_gate(&gate, &[*c, *t]); + } + + // Measurement and custom gates + GateOp::Measure(_, _) => {} + GateOp::Custom(gate, targets) => { + let quantum_gate = gate.to_quantum_gate(); + register.apply_gate(&quantum_gate, targets); + } + } + } + + register.get_state() + } + + fn compute_basic_mt(num_qubits: usize, operations: &[GateOp]) -> QuantumState { + // For small circuits, fall back to single-threaded (overhead not worth it) + if num_qubits < PARALLEL_THRESHOLD { + return Self::compute_basic(num_qubits, operations); + } + + let dim = 1 << num_qubits; + + // Initialize state to |0...0⟩ + let mut state: Vec<Complex<f64>> = vec![complex!(0.0, 0.0); dim]; + state[0] = complex!(1.0, 0.0); + + for op in operations { + let (gate_matrix, targets): (Matrix<Complex<f64>>, Vec<usize>) = match op { + // Clifford gates + GateOp::H(t) => (HADAMARD.matrix.clone(), vec![*t]), + GateOp::X(t) => (PAULI_X.matrix.clone(), vec![*t]), + GateOp::Y(t) => (PAULI_Y.matrix.clone(), vec![*t]), + GateOp::Z(t) => (PAULI_Z.matrix.clone(), vec![*t]), + GateOp::S(t) => (S_GATE.matrix.clone(), vec![*t]), + GateOp::CNOT(c, t) => (CNOT.matrix.clone(), vec![*c, *t]), + GateOp::CZ(c, t) => (CZ.matrix.clone(), vec![*c, *t]), + GateOp::SWAP(a, b) => (SWAP.matrix.clone(), vec![*a, *b]), + GateOp::CCNOT(c1, c2, t) => (TOFFOLI.matrix.clone(), vec![*c1, *c2, *t]), + GateOp::CSWAP(c, t1, t2) => (FREDKIN.matrix.clone(), vec![*c, *t1, *t2]), + + // Non-Clifford fixed gates + GateOp::T(t) => (T_GATE.matrix.clone(), vec![*t]), + GateOp::Sdg(t) => (SDG_GATE.matrix.clone(), vec![*t]), + GateOp::Tdg(t) => (TDG_GATE.matrix.clone(), vec![*t]), + GateOp::Sx(t) => (SX_GATE.matrix.clone(), vec![*t]), + GateOp::Sxdg(t) => (SXDG_GATE.matrix.clone(), vec![*t]), + + // Parametric single-qubit gates + GateOp::Rx(t, theta) => (rx_matrix(*theta), vec![*t]), + GateOp::Ry(t, theta) => (ry_matrix(*theta), vec![*t]), + GateOp::Rz(t, theta) => (rz_matrix(*theta), vec![*t]), + GateOp::P(t, theta) => (p_matrix(*theta), vec![*t]), + GateOp::U1(t, lambda) => (u1_matrix(*lambda), vec![*t]), + GateOp::U2(t, phi, lambda) => (u2_matrix(*phi, *lambda), vec![*t]), + GateOp::U3(t, theta, phi, lambda) => (u3_matrix(*theta, *phi, *lambda), vec![*t]), + + // Controlled parametric gates + GateOp::CRx(c, t, theta) => (crx_matrix(*theta), vec![*c, *t]), + GateOp::CRy(c, t, theta) => (cry_matrix(*theta), vec![*c, *t]), + GateOp::CRz(c, t, theta) => (crz_matrix(*theta), vec![*c, *t]), + GateOp::CP(c, t, theta) => (cp_matrix(*theta), vec![*c, *t]), + + // Measurement (skip) and custom gates + GateOp::Measure(_, _) => continue, + GateOp::Custom(custom_gate, tgts) => { + let quantum_gate = custom_gate.to_quantum_gate(); + state = apply_gate_parallel(&state, &quantum_gate.matrix, tgts, num_qubits); + continue; + } + }; + + state = apply_gate_parallel(&state, &gate_matrix, &targets, num_qubits); + } + + QuantumState::new(state) + } +} + +/// Apply a gate to the state vector in parallel using sparse application +/// This is O(2^n * 2^g) instead of O(2^2n) for full matrix multiplication +fn apply_gate_parallel( + state: &[Complex<f64>], + gate_matrix: &Matrix<Complex<f64>>, + targets: &[usize], + num_qubits: usize, +) -> Vec<Complex<f64>> { + let dim = 1 << num_qubits; + let g = targets.len(); + let gate_dim = 1 << g; + + // Convert target qubit indices to bit positions (from MSB) + let target_bits: Vec<usize> = targets.iter().map(|&t| num_qubits - 1 - t).collect(); + + // Create a mask for non-target qubits + let mut non_target_mask: usize = (1 << num_qubits) - 1; + for &pos in &target_bits { + non_target_mask &= !(1 << pos); + } + + // Parallel computation of new state + let new_state: Vec<Complex<f64>> = (0..dim) + .into_par_iter() + .map(|i| { + // Extract the target qubit bits from index i + let mut target_idx = 0usize; + for (k, &pos) in target_bits.iter().enumerate() { + if (i >> pos) & 1 == 1 { + target_idx |= 1 << (g - 1 - k); + } + } + + // Compute the contribution to state[i] + let mut sum = complex!(0.0, 0.0); + + // For each possible input state that could contribute + for j in 0..gate_dim { + // Get the gate matrix element + let gate_elem = gate_matrix.data[target_idx * gate_dim + j]; + + // Skip if zero (sparse optimization) + if gate_elem.real.abs() < 1e-15 && gate_elem.imaginary.abs() < 1e-15 { + continue; + } + + // Compute the source index by replacing target bits in i with bits from j + let mut source_idx = i & non_target_mask; + for (k, &pos) in target_bits.iter().enumerate() { + if (j >> (g - 1 - k)) & 1 == 1 { + source_idx |= 1 << pos; + } + } + + sum += gate_elem * state[source_idx]; + } + + sum + }) + .collect(); + + new_state +} + +fn matrix_to_2x2(matrix: &Matrix<Complex<f64>>) -> [[Complex<f64>; 2]; 2] { + [ + [matrix.data[0], matrix.data[1]], + [matrix.data[2], matrix.data[3]], + ] +} + +fn apply_kernel_direct( + state: &[Complex<f64>], + kernel: &Kernel, + num_qubits: usize, +) -> Vec<Complex<f64>> { + let dim = 1 << num_qubits; + let g = kernel.targets.len(); + let gate_dim = 1 << g; + + let target_bits: Vec<usize> = kernel.targets.iter().map(|&t| num_qubits - 1 - t).collect(); + + let mut non_target_mask: usize = (1 << num_qubits) - 1; + for &pos in &target_bits { + non_target_mask &= !(1 << pos); + } + + let mut new_state = vec![complex!(0.0, 0.0); dim]; + + for (i, new_val) in new_state.iter_mut().enumerate() { + let mut target_idx = 0usize; + for (k, &pos) in target_bits.iter().enumerate() { + if (i >> pos) & 1 == 1 { + target_idx |= 1 << (g - 1 - k); + } + } + + let mut sum = complex!(0.0, 0.0); + + for j in 0..gate_dim { + let gate_elem = kernel.matrix.data[target_idx * gate_dim + j]; + + if gate_elem.real.abs() < 1e-15 && gate_elem.imaginary.abs() < 1e-15 { + continue; + } + + let mut source_idx = i & non_target_mask; + for (k, &pos) in target_bits.iter().enumerate() { + if (j >> (g - 1 - k)) & 1 == 1 { + source_idx |= 1 << pos; + } + } + + sum += gate_elem * state[source_idx]; + } + + *new_val = sum; + } + + new_state +} |
