use crate::error::{Result, SimulatorError};
use scirs2_core::ndarray::*;
use scirs2_core::random::thread_rng;
use scirs2_core::{Complex64, Float};
use super::functions::{QubitIndex, StateIndex};
#[derive(Clone)]
pub struct QulacsStateVector {
state: Array1<Complex64>,
pub(super) num_qubits: usize,
pub(super) dim: StateIndex,
}
impl QulacsStateVector {
pub fn new(num_qubits: usize) -> Result<Self> {
if num_qubits == 0 {
return Err(SimulatorError::InvalidQubitCount(
"Number of qubits must be positive".to_string(),
));
}
if num_qubits > 30 {
return Err(SimulatorError::InvalidQubitCount(format!(
"Number of qubits ({}) exceeds maximum (30)",
num_qubits
)));
}
let dim = 1 << num_qubits;
let mut state = Array1::<Complex64>::zeros(dim);
state[0] = Complex64::new(1.0, 0.0);
Ok(Self {
state,
num_qubits,
dim,
})
}
pub fn from_amplitudes(amplitudes: Array1<Complex64>) -> Result<Self> {
let dim = amplitudes.len();
if dim == 0 || (dim & (dim - 1)) != 0 {
return Err(SimulatorError::InvalidState(
"Dimension must be a power of 2".to_string(),
));
}
let num_qubits = dim.trailing_zeros() as usize;
Ok(Self {
state: amplitudes,
num_qubits,
dim,
})
}
#[inline]
pub fn num_qubits(&self) -> usize {
self.num_qubits
}
#[inline]
pub fn dim(&self) -> StateIndex {
self.dim
}
#[inline]
pub fn amplitudes(&self) -> &Array1<Complex64> {
&self.state
}
#[inline]
pub fn amplitudes_mut(&mut self) -> &mut Array1<Complex64> {
&mut self.state
}
pub fn norm_squared(&self) -> f64 {
self.state.iter().map(|amp| amp.norm_sqr()).sum()
}
pub fn normalize(&mut self) -> Result<()> {
let norm = self.norm_squared().sqrt();
if norm < 1e-15 {
return Err(SimulatorError::InvalidState(
"Cannot normalize zero state".to_string(),
));
}
let scale = 1.0 / norm;
self.state.mapv_inplace(|amp| amp * scale);
Ok(())
}
pub fn inner_product(&self, other: &Self) -> Result<Complex64> {
if self.dim != other.dim {
return Err(SimulatorError::InvalidOperation(
"State vectors must have the same dimension".to_string(),
));
}
let result: Complex64 = self
.state
.iter()
.zip(other.state.iter())
.map(|(a, b)| a.conj() * b)
.sum();
Ok(result)
}
pub fn reset(&mut self) {
self.state.fill(Complex64::new(0.0, 0.0));
self.state[0] = Complex64::new(1.0, 0.0);
}
pub fn probability_one(&self, target: QubitIndex) -> Result<f64> {
if target >= self.num_qubits {
return Err(SimulatorError::InvalidQubitIndex {
index: target,
num_qubits: self.num_qubits,
});
}
let mask = 1usize << target;
let mut prob_one = 0.0;
for i in 0..self.dim {
if (i & mask) != 0 {
prob_one += self.state[i].norm_sqr();
}
}
Ok(prob_one)
}
pub fn probability_zero(&self, target: QubitIndex) -> Result<f64> {
Ok(1.0 - self.probability_one(target)?)
}
pub fn measure(&mut self, target: QubitIndex) -> Result<bool> {
if target >= self.num_qubits {
return Err(SimulatorError::InvalidQubitIndex {
index: target,
num_qubits: self.num_qubits,
});
}
let prob_one = self.probability_one(target)?;
use scirs2_core::random::prelude::*;
let mut rng = thread_rng();
let random_value: f64 = rng.random::<f64>();
let outcome = random_value < prob_one;
self.collapse_to(target, outcome)?;
Ok(outcome)
}
pub(super) fn collapse_to(&mut self, target: QubitIndex, outcome: bool) -> Result<()> {
let mask = 1usize << target;
let mut norm_sqr = 0.0;
for i in 0..self.dim {
let qubit_value = (i & mask) != 0;
if qubit_value != outcome {
self.state[i] = Complex64::new(0.0, 0.0);
} else {
norm_sqr += self.state[i].norm_sqr();
}
}
if norm_sqr < 1e-15 {
return Err(SimulatorError::InvalidState(
"Cannot collapse to zero-probability outcome".to_string(),
));
}
let norm = norm_sqr.sqrt();
let scale = 1.0 / norm;
for i in 0..self.dim {
if ((i & mask) != 0) == outcome {
self.state[i] *= scale;
}
}
Ok(())
}
pub fn sample(&self, shots: usize) -> Result<Vec<Vec<bool>>> {
if shots == 0 {
return Ok(Vec::new());
}
let mut rng = thread_rng();
let mut results = Vec::with_capacity(shots);
let mut cumulative_probs = Vec::with_capacity(self.dim);
let mut cumsum = 0.0;
for i in 0..self.dim {
cumsum += self.state[i].norm_sqr();
cumulative_probs.push(cumsum);
}
for _ in 0..shots {
let random_value: f64 = rng.random::<f64>();
let outcome_index = cumulative_probs
.binary_search_by(|&prob| {
if prob < random_value {
std::cmp::Ordering::Less
} else {
std::cmp::Ordering::Greater
}
})
.unwrap_or_else(|x| x);
let mut bitstring = Vec::with_capacity(self.num_qubits);
for q in 0..self.num_qubits {
bitstring.push((outcome_index & (1 << q)) != 0);
}
results.push(bitstring);
}
Ok(results)
}
pub fn get_counts(&self, shots: usize) -> Result<std::collections::HashMap<Vec<bool>, usize>> {
use std::collections::HashMap;
let samples = self.sample(shots)?;
let mut counts = HashMap::new();
for bitstring in samples {
*counts.entry(bitstring).or_insert(0) += 1;
}
Ok(counts)
}
pub fn sample_qubits(&self, qubits: &[QubitIndex], shots: usize) -> Result<Vec<Vec<bool>>> {
for &q in qubits {
if q >= self.num_qubits {
return Err(SimulatorError::InvalidQubitIndex {
index: q,
num_qubits: self.num_qubits,
});
}
}
let full_samples = self.sample(shots)?;
let results: Vec<Vec<bool>> = full_samples
.into_iter()
.map(|bitstring| qubits.iter().map(|&q| bitstring[q]).collect())
.collect();
Ok(results)
}
}