use crate::error::{QuantRS2Error, QuantRS2Result};
use crate::gate::GateOp;
use scirs2_core::ndarray::Array1;
use scirs2_core::Complex64;
#[must_use]
pub fn zero_state(num_qubits: usize) -> Array1<Complex64> {
let dim = 1usize << num_qubits;
let mut state = Array1::zeros(dim);
state[0] = Complex64::new(1.0, 0.0);
state
}
pub fn apply_gate(state: &mut Array1<Complex64>, gate: &dyn GateOp) -> QuantRS2Result<()> {
let qubits = gate.qubits();
let matrix = gate.matrix()?;
match qubits.len() {
1 => apply_one_qubit(state, qubits[0].0 as usize, &matrix),
2 => apply_two_qubit(state, qubits[0].0 as usize, qubits[1].0 as usize, &matrix),
k => Err(QuantRS2Error::UnsupportedOperation(format!(
"state-vector simulator only supports 1- and 2-qubit gates, got {k}-qubit gate '{}'",
gate.name()
))),
}
}
pub fn simulate(num_qubits: usize, gates: &[Box<dyn GateOp>]) -> QuantRS2Result<Array1<Complex64>> {
let mut state = zero_state(num_qubits);
for gate in gates {
apply_gate(&mut state, gate.as_ref())?;
}
Ok(state)
}
fn apply_one_qubit(
state: &mut Array1<Complex64>,
target: usize,
matrix: &[Complex64],
) -> QuantRS2Result<()> {
if matrix.len() != 4 {
return Err(QuantRS2Error::InvalidInput(format!(
"one-qubit gate matrix must have 4 entries, got {}",
matrix.len()
)));
}
let dim = state.len();
let bit = 1usize << target;
if bit >= dim {
return Err(QuantRS2Error::InvalidInput(format!(
"qubit index {target} out of range for {dim}-amplitude state"
)));
}
let mut idx = 0;
while idx < dim {
if idx & bit == 0 {
let i0 = idx;
let i1 = idx | bit;
let a = state[i0];
let b = state[i1];
state[i0] = matrix[0] * a + matrix[1] * b;
state[i1] = matrix[2] * a + matrix[3] * b;
}
idx += 1;
}
Ok(())
}
fn apply_two_qubit(
state: &mut Array1<Complex64>,
q_first: usize,
q_second: usize,
matrix: &[Complex64],
) -> QuantRS2Result<()> {
if matrix.len() != 16 {
return Err(QuantRS2Error::InvalidInput(format!(
"two-qubit gate matrix must have 16 entries, got {}",
matrix.len()
)));
}
if q_first == q_second {
return Err(QuantRS2Error::InvalidInput(
"two-qubit gate requires two distinct qubits".to_string(),
));
}
let dim = state.len();
let bit_first = 1usize << q_first;
let bit_second = 1usize << q_second;
if bit_first >= dim || bit_second >= dim {
return Err(QuantRS2Error::InvalidInput(format!(
"qubit index ({q_first},{q_second}) out of range for {dim}-amplitude state"
)));
}
let mut idx = 0;
while idx < dim {
if idx & bit_first == 0 && idx & bit_second == 0 {
let i00 = idx;
let i01 = idx | bit_second;
let i10 = idx | bit_first;
let i11 = idx | bit_first | bit_second;
let amps = [state[i00], state[i01], state[i10], state[i11]];
for (row, target_idx) in [i00, i01, i10, i11].into_iter().enumerate() {
let mut acc = Complex64::new(0.0, 0.0);
for (col, amp) in amps.iter().enumerate() {
acc += matrix[row * 4 + col] * *amp;
}
state[target_idx] = acc;
}
}
idx += 1;
}
Ok(())
}
#[must_use]
pub fn probability_one(state: &Array1<Complex64>, target: usize) -> f64 {
let bit = 1usize << target;
state
.iter()
.enumerate()
.filter(|(i, _)| i & bit != 0)
.map(|(_, a)| a.norm_sqr())
.sum()
}
#[must_use]
pub fn expectation_z(state: &Array1<Complex64>, target: usize) -> f64 {
let p1 = probability_one(state, target);
1.0 - 2.0 * p1
}
#[must_use]
pub fn probabilities(state: &Array1<Complex64>) -> Vec<f64> {
state.iter().map(scirs2_core::Complex::norm_sqr).collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::gate::multi::CNOT;
use crate::gate::single::{Hadamard, PauliX, RotationY};
use crate::qubit::QubitId;
#[test]
fn test_pauli_x_flips_qubit() {
let mut state = zero_state(1);
let x = PauliX { target: QubitId(0) };
apply_gate(&mut state, &x).expect("apply X");
assert!((state[1].norm() - 1.0).abs() < 1e-12);
assert!(state[0].norm() < 1e-12);
assert!((expectation_z(&state, 0) + 1.0).abs() < 1e-12);
}
#[test]
fn test_hadamard_superposition() {
let mut state = zero_state(1);
let h = Hadamard { target: QubitId(0) };
apply_gate(&mut state, &h).expect("apply H");
assert!((probability_one(&state, 0) - 0.5).abs() < 1e-12);
assert!(expectation_z(&state, 0).abs() < 1e-12);
}
#[test]
fn test_bell_state_entanglement() {
let gates: Vec<Box<dyn GateOp>> = vec![
Box::new(Hadamard { target: QubitId(0) }),
Box::new(CNOT {
control: QubitId(0),
target: QubitId(1),
}),
];
let state = simulate(2, &gates).expect("simulate bell");
let probs = probabilities(&state);
assert!((probs[0b00] - 0.5).abs() < 1e-12);
assert!((probs[0b11] - 0.5).abs() < 1e-12);
assert!(probs[0b01] < 1e-12);
assert!(probs[0b10] < 1e-12);
}
#[test]
fn test_rotation_y_expectation_is_continuous() {
let theta = 0.7;
let mut state = zero_state(1);
let ry = RotationY {
target: QubitId(0),
theta,
};
apply_gate(&mut state, &ry).expect("apply RY");
assert!((expectation_z(&state, 0) - theta.cos()).abs() < 1e-10);
}
#[test]
fn test_two_qubit_gate_on_high_index_qubits() {
let mut state = zero_state(2);
state[0] = Complex64::new(0.0, 0.0);
state[0b10] = Complex64::new(1.0, 0.0);
let cnot = CNOT {
control: QubitId(1),
target: QubitId(0),
};
apply_gate(&mut state, &cnot).expect("apply CNOT");
assert!((state[0b11].norm() - 1.0).abs() < 1e-12);
}
}