use super::aligned_buffer::AlignedBuffer;
use crate::circuit::Measurement;
use crate::circuit::circuit_impl::Circuit;
use crate::circuit::circuit_param::CircuitParam;
use crate::circuit::error::CircuitError;
use crate::circuit::gate::StandardGate;
use crate::circuit::gate::directive::Directive;
use crate::circuit::gate::instruction::Instruction;
use crate::device::{ExecutionResult, Outcome};
use crate::qis::error::QisError;
use crate::qis::observable::Observable;
use crate::qis::pauli::{PauliString, Phase};
use num_complex::Complex64;
use rand::{Rng, SeedableRng, rngs::SmallRng};
use rayon::prelude::*;
use smallvec::SmallVec;
use std::collections::HashMap;
use std::f64::consts::FRAC_1_SQRT_2;
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2,fma")]
#[inline]
unsafe fn cmul2_avx2(
a_ptr: *const f64,
u_re: std::arch::x86_64::__m256d,
u_im: std::arch::x86_64::__m256d,
) -> std::arch::x86_64::__m256d {
use std::arch::x86_64::*;
unsafe {
let a = _mm256_load_pd(a_ptr); let a_perm = _mm256_permute_pd(a, 0b0101); let term1 = _mm256_mul_pd(a, u_re); _mm256_addsub_pd(term1, _mm256_mul_pd(a_perm, u_im))
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2,fma")]
unsafe fn sqg_kernel_avx2(
lower: &mut [Complex64],
upper: &mut [Complex64],
u00: Complex64,
u01: Complex64,
u10: Complex64,
u11: Complex64,
) {
use std::arch::x86_64::*;
unsafe {
let v00_re = _mm256_set1_pd(u00.re);
let v00_im = _mm256_set1_pd(u00.im);
let v01_re = _mm256_set1_pd(u01.re);
let v01_im = _mm256_set1_pd(u01.im);
let v10_re = _mm256_set1_pd(u10.re);
let v10_im = _mm256_set1_pd(u10.im);
let v11_re = _mm256_set1_pd(u11.re);
let v11_im = _mm256_set1_pd(u11.im);
let n = lower.len(); let mut i = 0;
while i + 2 <= n {
let a_ptr = lower.as_ptr().add(i) as *const f64;
let b_ptr = upper.as_ptr().add(i) as *const f64;
let ua = cmul2_avx2(a_ptr, v00_re, v00_im);
let ub = cmul2_avx2(b_ptr, v01_re, v01_im);
let new_alpha = _mm256_add_pd(ua, ub);
let wa = cmul2_avx2(a_ptr, v10_re, v10_im);
let wb = cmul2_avx2(b_ptr, v11_re, v11_im);
let new_beta = _mm256_add_pd(wa, wb);
_mm256_store_pd(lower.as_mut_ptr().add(i) as *mut f64, new_alpha);
_mm256_store_pd(upper.as_mut_ptr().add(i) as *mut f64, new_beta);
i += 2;
}
while i < n {
let a = lower[i];
let b = upper[i];
lower[i] = u00 * a + u01 * b;
upper[i] = u10 * a + u11 * b;
i += 1;
}
}
}
#[inline(always)]
fn sqg_kernel_scalar(
lower: &mut [Complex64],
upper: &mut [Complex64],
u00: Complex64,
u01: Complex64,
u10: Complex64,
u11: Complex64,
) {
lower
.iter_mut()
.zip(upper.iter_mut())
.for_each(|(alpha, beta)| {
let a = *alpha;
let b = *beta;
*alpha = u00 * a + u01 * b;
*beta = u10 * a + u11 * b;
});
}
#[inline]
#[allow(unreachable_code)] fn sqg_kernel(
lower: &mut [Complex64],
upper: &mut [Complex64],
u00: Complex64,
u01: Complex64,
u10: Complex64,
u11: Complex64,
) {
#[cfg(target_arch = "x86_64")]
if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
return unsafe { sqg_kernel_avx2(lower, upper, u00, u01, u10, u11) };
}
sqg_kernel_scalar(lower, upper, u00, u01, u10, u11);
}
const PARALLEL_THRESHOLD: usize = 14;
macro_rules! with_maybe_par {
($num_qubits:expr, $data:expr, $chunk_size:expr, $body:expr) => {{
use rayon::prelude::*;
use std::iter::Iterator;
if $num_qubits < PARALLEL_THRESHOLD {
$data.chunks_exact_mut($chunk_size).for_each($body);
} else {
$data.par_chunks_exact_mut($chunk_size).for_each($body);
}
}};
}
#[derive(Debug, Clone)]
pub struct Statevector {
data: AlignedBuffer<Complex64>,
qubit_map: super::QubitMap,
pub num_qubits: usize,
}
impl Statevector {
pub fn data(&self) -> &[Complex64] {
&self.data
}
pub fn data_mut(&mut self) -> &mut [Complex64] {
&mut self.data
}
pub fn new(num_qubits: usize) -> Self {
let size = 1 << num_qubits;
let mut data = AlignedBuffer::<Complex64>::new_zeroed(size);
data[0] = Complex64::new(1.0, 0.0); Statevector {
data,
num_qubits,
qubit_map: None,
}
}
pub fn from_state(num_qubits: usize, initial_state: Vec<Complex64>) -> Result<Self, QisError> {
let size = 1 << num_qubits;
if initial_state.len() != size {
return Err(QisError::InvalidStateDimension(initial_state.len()));
}
let norm: f64 = initial_state.iter().map(|c| c.norm_sqr()).sum();
let tolerance = 1e-10 * (size.max(1) as f64);
if (norm - 1.0).abs() >= tolerance {
return Err(QisError::NotNormalized);
}
let mut data = AlignedBuffer::<Complex64>::new_zeroed(size);
data.as_mut_slice().copy_from_slice(&initial_state);
Ok(Statevector {
data,
num_qubits,
qubit_map: None,
})
}
pub fn from_circuit(circuit: &Circuit) -> Result<Self, QisError> {
let mut sv = Statevector::new(circuit.num_qubits());
sv.apply_circuit(circuit)?;
Ok(sv)
}
pub fn apply_circuit(&mut self, circuit: &Circuit) -> Result<(), QisError> {
if circuit.num_qubits() != self.num_qubits {
return Err(QisError::InvalidStateDimension(circuit.num_qubits()));
}
if circuit
.operations()
.iter()
.any(|op| matches!(op.instruction, Instruction::ClassicalControl(_)))
{
return Err(QisError::UnsupportedOperation(
"classical control flow is not supported in statevector simulation".to_string(),
));
}
let circuit = circuit.decompose()?;
super::validate_terminal_measurements(&circuit)?;
let sv = self;
let qubit_map = super::prepare_qubit_map(&circuit.qubits(), &sv.qubit_map)?;
let parameter_values: Vec<Option<f64>> = circuit
.parameters()
.iter()
.map(|p| p.evaluate(&None).ok())
.collect();
for op in circuit.operations() {
let params: Vec<f64> = op
.params
.iter()
.map(|p| match p {
CircuitParam::Fixed(v) => Ok(*v),
CircuitParam::Index(idx) => parameter_values
.get(*idx as usize)
.copied()
.flatten()
.ok_or(QisError::CircuitError(CircuitError::SymbolicParameterError)),
})
.collect::<Result<Vec<_>, QisError>>()?;
let qubit_indices: Result<Vec<usize>, QisError> =
op.qubits
.iter()
.map(|q| {
qubit_map.get(q).copied().ok_or_else(|| {
QisError::CircuitError(CircuitError::QubitNotFound(q.id()))
})
})
.collect();
let qubit_indices = qubit_indices?;
match &op.instruction {
Instruction::Standard(gate) => {
sv.apply_standard_gate(*gate, &qubit_indices, ¶ms)?;
}
Instruction::McGate(mc_gate) => {
let num_controls = mc_gate.num_ctrl_qubits();
let base_gate = mc_gate.base_gate();
if num_controls == 1 {
let control = qubit_indices[0];
let target = qubit_indices[1];
match base_gate {
StandardGate::X => sv.apply_cx(control, target)?,
StandardGate::Y => sv.apply_cy(control, target)?,
StandardGate::Z => sv.apply_cz(control, target)?,
StandardGate::RX => {
let theta = params[0];
sv.apply_crx(control, target, theta)?;
}
StandardGate::RY => {
let theta = params[0];
sv.apply_cry(control, target, theta)?;
}
StandardGate::RZ => {
let theta = params[0];
sv.apply_crz(control, target, theta)?;
}
_ => {
let matrix = mc_gate.matrix(¶ms).map_err(|_| {
QisError::CircuitError(CircuitError::NoMatrixRepresentation)
})?;
sv.apply_unitary_gate(&qubit_indices, &matrix)?;
}
}
} else if num_controls == 2 && *base_gate == StandardGate::X {
let c0 = qubit_indices[0];
let c1 = qubit_indices[1];
let target = qubit_indices[2];
sv.apply_ccx(c0, c1, target)?;
} else {
let matrix = mc_gate.matrix(¶ms).map_err(|_| {
QisError::CircuitError(CircuitError::NoMatrixRepresentation)
})?;
sv.apply_unitary_gate(&qubit_indices, &matrix)?;
}
}
Instruction::UnitaryGate(u_gate) => {
if let Some(matrix) = u_gate.matrix() {
sv.apply_unitary_gate(&qubit_indices, matrix)?;
} else {
return Err(QisError::CircuitError(CircuitError::NoMatrixRepresentation));
}
}
Instruction::CircuitGate(_) => {
return Err(QisError::CircuitError(CircuitError::InvalidOperation(
"CircuitGate should have been decomposed".to_string(),
)));
}
Instruction::Directive(directive) => match directive {
Directive::Barrier => {}
Directive::Measure => {
return Err(QisError::UnsupportedOperation(
"legacy Measure directive is not supported by statevector circuit evolution; use Circuit::measure as an output declaration".to_string(),
));
}
Directive::Reset => {
let qubit = qubit_indices[0];
sv.reset(qubit)?;
}
},
Instruction::Delay => {
continue;
}
Instruction::ClassicalControl(_) => {
return Err(QisError::UnsupportedOperation(
"classical control flow is not supported in statevector simulation"
.to_string(),
));
}
Instruction::ClassicalData(_) => {}
}
}
sv.qubit_map = Some(qubit_map);
Ok(())
}
pub fn apply_standard_gate(
&mut self,
gate: StandardGate,
qubits: &[usize],
params: &[f64],
) -> Result<(), QisError> {
if qubits.len() != gate.num_qubits() {
return Err(QisError::InvalidParameterValue(format!(
"Gate {:?} requires {} qubits, got {}",
gate,
gate.num_qubits(),
qubits.len()
)));
}
if params.len() != gate.num_params() {
return Err(QisError::InvalidParameterValue(format!(
"Gate {:?} requires {} parameters, got {}",
gate,
gate.num_params(),
params.len()
)));
}
match gate {
StandardGate::I => { }
StandardGate::X => self.apply_x(qubits[0])?,
StandardGate::Y => self.apply_y(qubits[0])?,
StandardGate::Z => self.apply_z(qubits[0])?,
StandardGate::H => self.apply_h(qubits[0])?,
StandardGate::S => self.apply_s(qubits[0])?,
StandardGate::SDG => self.apply_sdg(qubits[0])?,
StandardGate::T => self.apply_t(qubits[0])?,
StandardGate::TDG => self.apply_tdg(qubits[0])?,
StandardGate::RX => self.apply_rx(qubits[0], params[0])?,
StandardGate::RY => self.apply_ry(qubits[0], params[0])?,
StandardGate::RZ => self.apply_rz(qubits[0], params[0])?,
StandardGate::Phase => self.apply_phase(qubits[0], params[0])?,
StandardGate::X2P => self.apply_x2p(qubits[0])?,
StandardGate::X2M => self.apply_x2m(qubits[0])?,
StandardGate::Y2P => self.apply_y2p(qubits[0])?,
StandardGate::Y2M => self.apply_y2m(qubits[0])?,
StandardGate::RXY => self.apply_rxy(qubits[0], params[0], params[1])?,
StandardGate::XY => self.apply_xy(qubits[0], params[0])?,
StandardGate::XY2P => self.apply_xy2p(qubits[0], params[0])?,
StandardGate::XY2M => self.apply_xy2m(qubits[0], params[0])?,
StandardGate::U => {
self.apply_u(qubits[0], params[0], params[1], params[2])?;
}
StandardGate::GPhase => self.apply_gphase(params[0])?,
StandardGate::CX => self.apply_cx(qubits[0], qubits[1])?,
StandardGate::CY => self.apply_cy(qubits[0], qubits[1])?,
StandardGate::CZ => self.apply_cz(qubits[0], qubits[1])?,
StandardGate::SWAP => self.apply_swap(qubits[0], qubits[1])?,
StandardGate::RXX => self.apply_rxx(qubits[0], qubits[1], params[0])?,
StandardGate::RYY => self.apply_ryy(qubits[0], qubits[1], params[0])?,
StandardGate::RZZ => self.apply_rzz(qubits[0], qubits[1], params[0])?,
StandardGate::RZX => self.apply_rzx(qubits[0], qubits[1], params[0])?,
StandardGate::CRX => self.apply_crx(qubits[0], qubits[1], params[0])?,
StandardGate::CRY => self.apply_cry(qubits[0], qubits[1], params[0])?,
StandardGate::CRZ => self.apply_crz(qubits[0], qubits[1], params[0])?,
StandardGate::CCX => self.apply_ccx(qubits[0], qubits[1], qubits[2])?,
StandardGate::FSIM => self.apply_fsim(qubits[0], qubits[1], params[0], params[1])?,
}
Ok(())
}
pub fn probabilities(&self) -> Vec<f64> {
if self.num_qubits < PARALLEL_THRESHOLD {
self.data.iter().map(|c| c.norm_sqr()).collect()
} else {
self.data.par_iter().map(|c| c.norm_sqr()).collect()
}
}
fn validate_qubit(&self, qubit: usize) -> Result<(), QisError> {
if self.num_qubits == 0 {
return Err(QisError::InvalidParameterValue(
"no valid qubit indices in a zero-qubit system".to_string(),
));
}
if qubit >= self.num_qubits {
return Err(QisError::IndexOutOfBounds {
index: qubit,
max: self.num_qubits.saturating_sub(1),
});
}
Ok(())
}
fn validate_two_qubits(&self, q0: usize, q1: usize) -> Result<(), QisError> {
self.validate_qubit(q0)?;
self.validate_qubit(q1)?;
if q0 == q1 {
return Err(QisError::InvalidParameterValue(
"Qubit indices must be distinct".to_string(),
));
}
Ok(())
}
fn validate_qubits(&self, qubits: &[usize]) -> Result<(), QisError> {
for (i, &q) in qubits.iter().enumerate() {
self.validate_qubit(q)?;
for &other in &qubits[..i] {
if q == other {
return Err(QisError::InvalidParameterValue(format!(
"Duplicate qubit index {} in gate operation",
q
)));
}
}
}
Ok(())
}
pub fn apply_single_qubit_gate(
&mut self,
qubit: usize,
matrix: [[Complex64; 2]; 2],
) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let dist = 1 << qubit;
let u00 = matrix[0][0];
let u01 = matrix[0][1];
let u10 = matrix[1][0];
let u11 = matrix[1][1];
let kernel = |chunk: &mut [Complex64]| {
let (lower, upper) = chunk.split_at_mut(dist);
sqg_kernel(lower, upper, u00, u01, u10, u11);
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
Ok(())
}
pub fn apply_two_qubit_gate(
&mut self,
q0: usize,
q1: usize,
matrix: [[Complex64; 4]; 4],
) -> Result<(), QisError> {
self.validate_two_qubits(q0, q1)?;
let (q_min, q_max) = if q0 < q1 { (q0, q1) } else { (q1, q0) };
let dist_max = 1 << q_max;
let dist_min = 1 << q_min;
let m00 = matrix[0][0];
let m01 = matrix[0][1];
let m02 = matrix[0][2];
let m03 = matrix[0][3];
let m10 = matrix[1][0];
let m11 = matrix[1][1];
let m12 = matrix[1][2];
let m13 = matrix[1][3];
let m20 = matrix[2][0];
let m21 = matrix[2][1];
let m22 = matrix[2][2];
let m23 = matrix[2][3];
let m30 = matrix[3][0];
let m31 = matrix[3][1];
let m32 = matrix[3][2];
let m33 = matrix[3][3];
let kernel = |chunk: &mut [Complex64]| {
let (part0, part1) = chunk.split_at_mut(dist_max);
part0
.chunks_exact_mut(2 * dist_min)
.zip(part1.chunks_exact_mut(2 * dist_min))
.for_each(|(sub_chunk0, sub_chunk1)| {
let (v00_slice, v01_slice) = sub_chunk0.split_at_mut(dist_min);
let (v10_slice, v11_slice) = sub_chunk1.split_at_mut(dist_min);
for (((c00, c01), c10), c11) in v00_slice
.iter_mut()
.zip(v01_slice.iter_mut())
.zip(v10_slice.iter_mut())
.zip(v11_slice.iter_mut())
{
let a = *c00; let b = *c01; let c = *c10; let d = *c11;
let (in0, in1, in2, in3) = if q0 < q1 {
(a, c, b, d)
} else {
(a, b, c, d)
};
let out0 = m00 * in0 + m01 * in1 + m02 * in2 + m03 * in3;
let out1 = m10 * in0 + m11 * in1 + m12 * in2 + m13 * in3;
let out2 = m20 * in0 + m21 * in1 + m22 * in2 + m23 * in3;
let out3 = m30 * in0 + m31 * in1 + m32 * in2 + m33 * in3;
if q0 < q1 {
*c00 = out0;
*c10 = out1; *c01 = out2; *c11 = out3;
} else {
*c00 = out0;
*c01 = out1;
*c10 = out2;
*c11 = out3;
}
}
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist_max, kernel);
Ok(())
}
pub fn apply_unitary_gate(
&mut self,
qubits: &[usize],
matrix: &ndarray::Array2<Complex64>,
) -> Result<(), QisError> {
let num_target_qubits = qubits.len();
let gate_dim = 1 << num_target_qubits;
if num_target_qubits == 0 || num_target_qubits > self.num_qubits {
return Err(QisError::InvalidParameterValue(format!(
"Invalid number of target qubits: {} (must be 1 to {})",
num_target_qubits, self.num_qubits
)));
}
for (i, &q) in qubits.iter().enumerate() {
self.validate_qubit(q)?;
for &other in &qubits[..i] {
if q == other {
return Err(QisError::InvalidParameterValue(format!(
"Duplicate qubit index {} in apply_unitary",
q
)));
}
}
}
if matrix.shape() != [gate_dim, gate_dim] {
return Err(QisError::InvalidParameterValue(format!(
"Matrix dimensions {}x{} don't match expected {}x{} for {} qubits",
matrix.nrows(),
matrix.ncols(),
gate_dim,
gate_dim,
num_target_qubits
)));
}
let mut gate_offsets: Vec<usize> = vec![0; gate_dim];
#[allow(clippy::needless_range_loop)]
for gate_idx in 0..gate_dim {
let mut offset = 0;
for (i, &qubit) in qubits.iter().enumerate() {
let bit_pos = num_target_qubits - 1 - i;
if (gate_idx >> bit_pos) & 1 == 1 {
offset |= 1 << qubit;
}
}
gate_offsets[gate_idx] = offset;
}
let mut sorted_qubits: Vec<usize> = qubits.to_vec();
sorted_qubits.sort_unstable();
let max_qubit = *sorted_qubits.last().unwrap();
let chunk_size = 1 << (max_qubit + 1);
let matrix_rows: Vec<Vec<Complex64>> = (0..gate_dim)
.map(|r| (0..gate_dim).map(|c| matrix[[r, c]]).collect())
.collect();
let kernel = |chunk: &mut [Complex64]| {
let mut input_buf = vec![Complex64::default(); gate_dim];
let mut output_buf = vec![Complex64::default(); gate_dim];
let num_blocks = chunk.len() >> num_target_qubits;
for block_idx in 0..num_blocks {
let mut base = block_idx;
for &q in &sorted_qubits {
let mask = (1 << q) - 1;
let left = (base & !mask) << 1;
let right = base & mask;
base = left | right;
}
for (gate_idx, &offset) in gate_offsets.iter().enumerate() {
input_buf[gate_idx] = chunk[base + offset];
}
for row in 0..gate_dim {
let mut sum = Complex64::default();
for (matrix_value, input_value) in matrix_rows[row].iter().zip(input_buf.iter())
{
sum += matrix_value * input_value;
}
output_buf[row] = sum;
}
for (gate_idx, &offset) in gate_offsets.iter().enumerate() {
chunk[base + offset] = output_buf[gate_idx];
}
}
};
with_maybe_par!(self.num_qubits, self.data, chunk_size, kernel);
Ok(())
}
pub fn apply_pauli_rotation(
&mut self,
pauli: &PauliString,
theta: f64,
) -> Result<(), QisError> {
if pauli.num_qubits != self.num_qubits {
return Err(QisError::QubitMismatch {
expected: self.num_qubits,
actual: pauli.num_qubits,
});
}
if matches!(pauli.phase, Phase::I | Phase::MinusI) {
return Err(QisError::NotHermitian);
}
if theta.abs() < 1e-15 {
return Ok(());
}
let x_mask = pauli.x_mask();
let z_mask = pauli.z_mask();
let base_factor = pauli.phase.to_complex() * pauli.y_phase();
let half = theta * 0.5;
let cosine = half.cos();
let minus_i_sine = Complex64::new(0.0, -half.sin());
#[inline(always)]
fn source_phase(index: usize, z_mask: usize, base: Complex64) -> Complex64 {
if (index & z_mask).count_ones() & 1 == 0 {
base
} else {
-base
}
}
if x_mask == 0 {
if self.num_qubits < PARALLEL_THRESHOLD {
self.data
.iter_mut()
.enumerate()
.for_each(|(index, amplitude)| {
let phase = source_phase(index, z_mask, base_factor);
*amplitude *= Complex64::new(cosine, 0.0) + minus_i_sine * phase;
});
} else {
self.data
.par_iter_mut()
.enumerate()
.for_each(|(index, amplitude)| {
let phase = source_phase(index, z_mask, base_factor);
*amplitude *= Complex64::new(cosine, 0.0) + minus_i_sine * phase;
});
}
return Ok(());
}
let highest_bit = usize::BITS as usize - 1 - x_mask.leading_zeros() as usize;
let dist = 1usize << highest_bit;
let chunk_size = 2 * dist;
let low_flip_mask = x_mask ^ dist;
let apply_chunk = |chunk_index: usize, chunk: &mut [Complex64]| {
let base_index = chunk_index * chunk_size;
for lower_index in 0..dist {
let upper_index = dist + (lower_index ^ low_flip_mask);
let source_lower = base_index + lower_index;
let source_upper = base_index + upper_index;
let lower_phase = source_phase(source_lower, z_mask, base_factor);
let upper_phase = source_phase(source_upper, z_mask, base_factor);
let lower = chunk[lower_index];
let upper = chunk[upper_index];
chunk[lower_index] = cosine * lower + minus_i_sine * upper_phase * upper;
chunk[upper_index] = cosine * upper + minus_i_sine * lower_phase * lower;
}
};
if self.num_qubits < PARALLEL_THRESHOLD {
self.data
.chunks_mut(chunk_size)
.enumerate()
.for_each(|(chunk_index, chunk)| apply_chunk(chunk_index, chunk));
} else {
self.data
.par_chunks_mut(chunk_size)
.enumerate()
.for_each(|(chunk_index, chunk)| apply_chunk(chunk_index, chunk));
}
Ok(())
}
pub fn apply_x(&mut self, qubit: usize) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let dist = 1 << qubit;
let kernel = |chunk: &mut [Complex64]| {
let (lower, upper) = chunk.split_at_mut(dist);
lower.iter_mut().zip(upper.iter_mut()).for_each(|(a, b)| {
std::mem::swap(a, b);
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
Ok(())
}
pub fn apply_y(&mut self, qubit: usize) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let dist = 1 << qubit;
let kernel = |chunk: &mut [Complex64]| {
let (lower, upper) = chunk.split_at_mut(dist);
lower
.iter_mut()
.zip(upper.iter_mut())
.for_each(|(alpha, beta)| {
let a = *alpha;
let b = *beta;
*alpha = Complex64::new(b.im, -b.re);
*beta = Complex64::new(-a.im, a.re);
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
Ok(())
}
pub fn apply_z(&mut self, qubit: usize) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let dist = 1 << qubit;
let kernel = |chunk: &mut [Complex64]| {
let (_lower, upper) = chunk.split_at_mut(dist);
upper.iter_mut().for_each(|val| {
*val = -*val;
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
Ok(())
}
pub fn apply_h(&mut self, qubit: usize) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let dist = 1 << qubit;
let k = FRAC_1_SQRT_2;
let kernel = |chunk: &mut [Complex64]| {
let (lower, upper) = chunk.split_at_mut(dist);
lower
.iter_mut()
.zip(upper.iter_mut())
.for_each(|(alpha, beta)| {
let a = *alpha;
let b = *beta;
*alpha = k * (a + b);
*beta = k * (a - b);
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
Ok(())
}
pub fn apply_u(
&mut self,
qubit: usize,
theta: f64,
phi: f64,
lambda: f64,
) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
if theta.abs() < 1e-15 {
return self.apply_phase(qubit, lambda + phi);
}
let dist = 1 << qubit;
let half_theta = theta * 0.5;
let c = half_theta.cos();
let s = half_theta.sin();
let phase_phi = Complex64::from_polar(1.0, phi); let phase_lambda = Complex64::from_polar(1.0, lambda); let phase_phi_lambda = Complex64::from_polar(1.0, phi + lambda);
let u00 = Complex64::new(c, 0.0);
let u01 = -phase_lambda * s;
let u10 = phase_phi * s;
let u11 = phase_phi_lambda * c;
let kernel = |chunk: &mut [Complex64]| {
let (lower, upper) = chunk.split_at_mut(dist);
lower
.iter_mut()
.zip(upper.iter_mut())
.for_each(|(alpha, beta)| {
let a = *alpha;
let b = *beta;
*alpha = u00 * a + u01 * b;
*beta = u10 * a + u11 * b;
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
Ok(())
}
pub fn apply_s(&mut self, qubit: usize) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let dist = 1 << qubit;
let kernel = |chunk: &mut [Complex64]| {
let (_lower, upper) = chunk.split_at_mut(dist);
upper.iter_mut().for_each(|val| {
*val = Complex64::new(-val.im, val.re);
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
Ok(())
}
pub fn apply_sdg(&mut self, qubit: usize) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let dist = 1 << qubit;
let kernel = |chunk: &mut [Complex64]| {
let (_lower, upper) = chunk.split_at_mut(dist);
upper.iter_mut().for_each(|val| {
*val = Complex64::new(val.im, -val.re);
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
Ok(())
}
pub fn apply_t(&mut self, qubit: usize) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let dist = 1 << qubit;
let phase = Complex64::new(FRAC_1_SQRT_2, FRAC_1_SQRT_2);
let kernel = |chunk: &mut [Complex64]| {
let (_lower, upper) = chunk.split_at_mut(dist);
upper.iter_mut().for_each(|val| {
*val *= phase;
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
Ok(())
}
pub fn apply_tdg(&mut self, qubit: usize) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let dist = 1 << qubit;
let phase = Complex64::new(FRAC_1_SQRT_2, -FRAC_1_SQRT_2);
let kernel = |chunk: &mut [Complex64]| {
let (_lower, upper) = chunk.split_at_mut(dist);
upper.iter_mut().for_each(|val| {
*val *= phase;
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
Ok(())
}
pub fn apply_phase(&mut self, qubit: usize, theta: f64) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
if theta.abs() < 1e-15 {
return Ok(());
}
let dist = 1 << qubit;
let phase = Complex64::from_polar(1.0, theta);
let kernel = |chunk: &mut [Complex64]| {
let (_lower, upper) = chunk.split_at_mut(dist);
upper.iter_mut().for_each(|val| {
*val *= phase;
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
Ok(())
}
pub fn apply_rx(&mut self, qubit: usize, theta: f64) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
if theta.abs() < 1e-15 {
return Ok(());
}
let dist = 1 << qubit;
let half_theta = theta * 0.5;
let c = half_theta.cos();
let s = half_theta.sin();
let kernel = |chunk: &mut [Complex64]| {
let (lower, upper) = chunk.split_at_mut(dist);
lower
.iter_mut()
.zip(upper.iter_mut())
.for_each(|(alpha, beta)| {
let a = *alpha;
let b = *beta;
let bs_im = b.im * s;
let bs_re = b.re * s;
let as_im = a.im * s;
let as_re = a.re * s;
*alpha = Complex64::new(
a.re * c + bs_im, a.im * c - bs_re, );
*beta = Complex64::new(
b.re * c + as_im, b.im * c - as_re, );
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
Ok(())
}
pub fn apply_ry(&mut self, qubit: usize, theta: f64) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
if theta.abs() < 1e-15 {
return Ok(());
}
let dist = 1 << qubit;
let half_theta = theta * 0.5;
let c = half_theta.cos();
let s = half_theta.sin();
let kernel = |chunk: &mut [Complex64]| {
let (lower, upper) = chunk.split_at_mut(dist);
lower
.iter_mut()
.zip(upper.iter_mut())
.for_each(|(alpha, beta)| {
let a = *alpha;
let b = *beta;
*alpha = a * c - b * s;
*beta = a * s + b * c;
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
Ok(())
}
pub fn apply_rz(&mut self, qubit: usize, theta: f64) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
if theta.abs() < 1e-15 {
return Ok(());
}
let dist = 1 << qubit;
let half_theta = theta * 0.5;
let phase_0 = Complex64::from_polar(1.0, -half_theta); let phase_1 = Complex64::from_polar(1.0, half_theta);
let kernel = |chunk: &mut [Complex64]| {
let (lower, upper) = chunk.split_at_mut(dist);
lower.iter_mut().zip(upper.iter_mut()).for_each(|(a, b)| {
*a *= phase_0;
*b *= phase_1;
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
Ok(())
}
pub fn apply_x2p(&mut self, qubit: usize) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let dist = 1 << qubit;
let k = FRAC_1_SQRT_2;
let kernel = |chunk: &mut [Complex64]| {
let (lower, upper) = chunk.split_at_mut(dist);
lower
.iter_mut()
.zip(upper.iter_mut())
.for_each(|(alpha, beta)| {
let a = *alpha;
let b = *beta;
*alpha = Complex64::new(k * (a.re + b.im), k * (a.im - b.re));
*beta = Complex64::new(k * (b.re + a.im), k * (b.im - a.re));
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
Ok(())
}
pub fn apply_x2m(&mut self, qubit: usize) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let dist = 1 << qubit;
let k = FRAC_1_SQRT_2;
let kernel = |chunk: &mut [Complex64]| {
let (lower, upper) = chunk.split_at_mut(dist);
lower
.iter_mut()
.zip(upper.iter_mut())
.for_each(|(alpha, beta)| {
let a = *alpha;
let b = *beta;
*alpha = Complex64::new(k * (a.re - b.im), k * (a.im + b.re));
*beta = Complex64::new(k * (b.re - a.im), k * (b.im + a.re));
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
Ok(())
}
pub fn apply_y2p(&mut self, qubit: usize) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let dist = 1 << qubit;
let k = FRAC_1_SQRT_2;
let kernel = |chunk: &mut [Complex64]| {
let (lower, upper) = chunk.split_at_mut(dist);
lower
.iter_mut()
.zip(upper.iter_mut())
.for_each(|(alpha, beta)| {
let a = *alpha;
let b = *beta;
*alpha = k * (a - b);
*beta = k * (a + b);
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
Ok(())
}
pub fn apply_y2m(&mut self, qubit: usize) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let dist = 1 << qubit;
let k = FRAC_1_SQRT_2;
let kernel = |chunk: &mut [Complex64]| {
let (lower, upper) = chunk.split_at_mut(dist);
lower
.iter_mut()
.zip(upper.iter_mut())
.for_each(|(alpha, beta)| {
let a = *alpha;
let b = *beta;
*alpha = k * (a + b);
*beta = k * (b - a);
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
Ok(())
}
pub fn apply_xy2p(&mut self, qubit: usize, theta: f64) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let dist = 1 << qubit;
let k = FRAC_1_SQRT_2;
let phase = Complex64::from_polar(1.0, theta);
let phase_conj = phase.conj(); let neg_i = Complex64::new(0.0, -1.0);
let coef_01 = neg_i * phase_conj * k;
let coef_10 = neg_i * phase * k;
let k_complex = Complex64::new(k, 0.0);
let kernel = |chunk: &mut [Complex64]| {
let (lower, upper) = chunk.split_at_mut(dist);
lower
.iter_mut()
.zip(upper.iter_mut())
.for_each(|(alpha, beta)| {
let a = *alpha;
let b = *beta;
*alpha = k_complex * a + coef_01 * b;
*beta = coef_10 * a + k_complex * b;
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
Ok(())
}
pub fn apply_xy2m(&mut self, qubit: usize, theta: f64) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let dist = 1 << qubit;
let k = FRAC_1_SQRT_2;
let phase = Complex64::from_polar(1.0, theta);
let phase_conj = phase.conj();
let pos_i = Complex64::new(0.0, 1.0);
let coef_01 = pos_i * phase_conj * k;
let coef_10 = pos_i * phase * k;
let k_complex = Complex64::new(k, 0.0);
let kernel = |chunk: &mut [Complex64]| {
let (lower, upper) = chunk.split_at_mut(dist);
lower
.iter_mut()
.zip(upper.iter_mut())
.for_each(|(alpha, beta)| {
let a = *alpha;
let b = *beta;
*alpha = k_complex * a + coef_01 * b;
*beta = coef_10 * a + k_complex * b;
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
Ok(())
}
pub fn apply_xy(&mut self, qubit: usize, theta: f64) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let dist = 1 << qubit;
let phase = Complex64::from_polar(1.0, theta);
let phase_conj = phase.conj();
let neg_i = Complex64::new(0.0, -1.0);
let u01 = neg_i * phase_conj;
let u10 = neg_i * phase;
let kernel = |chunk: &mut [Complex64]| {
let (lower, upper) = chunk.split_at_mut(dist);
lower
.iter_mut()
.zip(upper.iter_mut())
.for_each(|(alpha, beta)| {
let a = *alpha;
let b = *beta;
*alpha = u01 * b;
*beta = u10 * a;
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
Ok(())
}
pub fn apply_rxy(&mut self, qubit: usize, theta: f64, phi: f64) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
if theta.abs() < 1e-15 {
return Ok(());
}
let dist = 1 << qubit;
let half_theta = theta * 0.5;
let c = half_theta.cos();
let s = half_theta.sin();
let (sin_phi, cos_phi) = phi.sin_cos();
let u01 = Complex64::new(-s * sin_phi, -s * cos_phi);
let u10 = Complex64::new(s * sin_phi, -s * cos_phi);
let kernel = |chunk: &mut [Complex64]| {
let (lower, upper) = chunk.split_at_mut(dist);
lower
.iter_mut()
.zip(upper.iter_mut())
.for_each(|(alpha, beta)| {
let a = *alpha;
let b = *beta;
*alpha = a * c + b * u01;
*beta = a * u10 + b * c;
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
Ok(())
}
pub fn apply_swap(&mut self, q0: usize, q1: usize) -> Result<(), QisError> {
self.validate_two_qubits(q0, q1)?;
let (q_min, q_max) = if q0 < q1 { (q0, q1) } else { (q1, q0) };
let dist_max = 1 << q_max;
let dist_min = 1 << q_min;
let kernel = |chunk: &mut [Complex64]| {
let (part0, part1) = chunk.split_at_mut(dist_max);
part0
.chunks_exact_mut(2 * dist_min)
.zip(part1.chunks_exact_mut(2 * dist_min))
.for_each(|(sub_chunk0, sub_chunk1)| {
let (_, v01_slice) = sub_chunk0.split_at_mut(dist_min);
let (v10_slice, _) = sub_chunk1.split_at_mut(dist_min);
v01_slice
.iter_mut()
.zip(v10_slice.iter_mut())
.for_each(|(a, b)| {
std::mem::swap(a, b);
});
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist_max, kernel);
Ok(())
}
pub fn apply_cx(&mut self, control: usize, target: usize) -> Result<(), QisError> {
self.validate_two_qubits(control, target)?;
let (q_min, q_max) = if control < target {
(control, target)
} else {
(target, control)
};
let dist_max = 1 << q_max;
let dist_min = 1 << q_min;
let kernel = |chunk: &mut [Complex64]| {
let (part0, part1) = chunk.split_at_mut(dist_max);
part0
.chunks_exact_mut(2 * dist_min)
.zip(part1.chunks_exact_mut(2 * dist_min))
.for_each(|(sub_chunk0, sub_chunk1)| {
let (_v00, v01) = sub_chunk0.split_at_mut(dist_min);
let (v10, v11) = sub_chunk1.split_at_mut(dist_min);
if control < target {
v01.iter_mut()
.zip(v11.iter_mut())
.for_each(|(a, b)| std::mem::swap(a, b));
} else {
v10.iter_mut()
.zip(v11.iter_mut())
.for_each(|(a, b)| std::mem::swap(a, b));
}
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist_max, kernel);
Ok(())
}
pub fn apply_cy(&mut self, control: usize, target: usize) -> Result<(), QisError> {
self.validate_two_qubits(control, target)?;
let (q_min, q_max) = if control < target {
(control, target)
} else {
(target, control)
};
let dist_max = 1 << q_max;
let dist_min = 1 << q_min;
let kernel = |chunk: &mut [Complex64]| {
let (part0, part1) = chunk.split_at_mut(dist_max);
part0
.chunks_exact_mut(2 * dist_min)
.zip(part1.chunks_exact_mut(2 * dist_min))
.for_each(|(sub_chunk0, sub_chunk1)| {
let (block_t0, block_t1) = if control < target {
let (_, c1_t0) = sub_chunk0.split_at_mut(dist_min);
let (_, c1_t1) = sub_chunk1.split_at_mut(dist_min);
(c1_t0, c1_t1)
} else {
let (t0, t1) = sub_chunk1.split_at_mut(dist_min);
(t0, t1)
};
block_t0
.iter_mut()
.zip(block_t1.iter_mut())
.for_each(|(alpha, beta)| {
let a = *alpha; let b = *beta;
*alpha = Complex64::new(b.im, -b.re);
*beta = Complex64::new(-a.im, a.re);
});
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist_max, kernel);
Ok(())
}
pub fn apply_cz(&mut self, q0: usize, q1: usize) -> Result<(), QisError> {
self.validate_two_qubits(q0, q1)?;
let (q_min, q_max) = if q0 < q1 { (q0, q1) } else { (q1, q0) };
let dist_max = 1 << q_max;
let dist_min = 1 << q_min;
let kernel = |chunk: &mut [Complex64]| {
let (_, part1) = chunk.split_at_mut(dist_max);
part1.chunks_exact_mut(2 * dist_min).for_each(|sub_chunk1| {
let (_, v11) = sub_chunk1.split_at_mut(dist_min);
for val in v11.iter_mut() {
*val = -*val;
}
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * dist_max, kernel);
Ok(())
}
pub fn apply_ccx(&mut self, c0: usize, c1: usize, target: usize) -> Result<(), QisError> {
self.validate_qubits(&[c0, c1, target])?;
let mut qubits = [(c0, 0), (c1, 0), (target, 1)];
qubits.sort_by_key(|(q, _)| *q);
let (q0, k0) = qubits[0]; let (q1, k1) = qubits[1]; let (q2, k2) = qubits[2];
let d0 = 1 << q0;
let d1 = 1 << q1;
let d2 = 1 << q2;
match (k0, k1, k2) {
(0, 0, 1) => {
let kernel = |chunk: &mut [Complex64]| {
let (part_t0, part_t1) = chunk.split_at_mut(d2);
part_t0
.chunks_exact_mut(2 * d1)
.zip(part_t1.chunks_exact_mut(2 * d1))
.for_each(|(sub_t0, sub_t1)| {
let (_, high_m_t0) = sub_t0.split_at_mut(d1);
let (_, high_m_t1) = sub_t1.split_at_mut(d1);
high_m_t0
.chunks_exact_mut(2 * d0)
.zip(high_m_t1.chunks_exact_mut(2 * d0))
.for_each(|(leaf_t0, leaf_t1)| {
let (_, valid_t0) = leaf_t0.split_at_mut(d0);
let (_, valid_t1) = leaf_t1.split_at_mut(d0);
valid_t0
.iter_mut()
.zip(valid_t1.iter_mut())
.for_each(|(a, b)| std::mem::swap(a, b));
});
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * d2, kernel);
}
(0, 1, 0) => {
let kernel = |chunk: &mut [Complex64]| {
let (_, part_c1) = chunk.split_at_mut(d2);
part_c1.chunks_exact_mut(2 * d1).for_each(|sub| {
let (low_t0, high_t1) = sub.split_at_mut(d1);
low_t0
.chunks_exact_mut(2 * d0)
.zip(high_t1.chunks_exact_mut(2 * d0))
.for_each(|(leaf_t0, leaf_t1)| {
let (_, valid_t0) = leaf_t0.split_at_mut(d0);
let (_, valid_t1) = leaf_t1.split_at_mut(d0);
valid_t0
.iter_mut()
.zip(valid_t1.iter_mut())
.for_each(|(a, b)| std::mem::swap(a, b));
});
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * d2, kernel);
}
(1, 0, 0) => {
let kernel = |chunk: &mut [Complex64]| {
let (_, part_max1) = chunk.split_at_mut(d2);
part_max1.chunks_exact_mut(2 * d1).for_each(|sub| {
let (_, part_mid1) = sub.split_at_mut(d1);
part_mid1.chunks_exact_mut(2 * d0).for_each(|leaf| {
let (target0, target1) = leaf.split_at_mut(d0);
target0
.iter_mut()
.zip(target1.iter_mut())
.for_each(|(a, b)| std::mem::swap(a, b));
});
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * d2, kernel);
}
_ => unreachable!("Should cover all permutations"),
}
Ok(())
}
pub fn apply_rxx(&mut self, q0: usize, q1: usize, theta: f64) -> Result<(), QisError> {
self.validate_two_qubits(q0, q1)?;
if theta.abs() < 1e-15 {
return Ok(());
}
let (q_min, q_max) = if q0 < q1 { (q0, q1) } else { (q1, q0) };
let d_max = 1 << q_max;
let d_min = 1 << q_min;
let half_theta = theta * 0.5;
let c = half_theta.cos();
let s = half_theta.sin();
let kernel = |chunk: &mut [Complex64]| {
let (part0, part1) = chunk.split_at_mut(d_max);
part0
.chunks_exact_mut(2 * d_min)
.zip(part1.chunks_exact_mut(2 * d_min))
.for_each(|(sub0, sub1)| {
let (v00, v01) = sub0.split_at_mut(d_min);
let (v10, v11) = sub1.split_at_mut(d_min);
for (((a, b), c_amp), d) in v00
.iter_mut()
.zip(v01.iter_mut())
.zip(v10.iter_mut())
.zip(v11.iter_mut())
{
let a_in = *a;
let b_in = *b;
let c_in = *c_amp;
let d_in = *d;
*a = c * a_in + Complex64::new(0.0, -s) * d_in;
*b = c * b_in + Complex64::new(0.0, -s) * c_in;
*c_amp = Complex64::new(0.0, -s) * b_in + c * c_in;
*d = Complex64::new(0.0, -s) * a_in + c * d_in;
}
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * d_max, kernel);
Ok(())
}
pub fn apply_ryy(&mut self, q0: usize, q1: usize, theta: f64) -> Result<(), QisError> {
self.validate_two_qubits(q0, q1)?;
if theta.abs() < 1e-15 {
return Ok(());
}
let (q_min, q_max) = if q0 < q1 { (q0, q1) } else { (q1, q0) };
let d_max = 1 << q_max;
let d_min = 1 << q_min;
let half_theta = theta * 0.5;
let c = half_theta.cos();
let s = half_theta.sin();
let kernel = |chunk: &mut [Complex64]| {
let (part0, part1) = chunk.split_at_mut(d_max);
part0
.chunks_exact_mut(2 * d_min)
.zip(part1.chunks_exact_mut(2 * d_min))
.for_each(|(sub0, sub1)| {
let (v00, v01) = sub0.split_at_mut(d_min);
let (v10, v11) = sub1.split_at_mut(d_min);
for (((a, b), c_amp), d) in v00
.iter_mut()
.zip(v01.iter_mut())
.zip(v10.iter_mut())
.zip(v11.iter_mut())
{
let a_in = *a;
let b_in = *b;
let c_in = *c_amp;
let d_in = *d;
*a = c * a_in + Complex64::new(0.0, s) * d_in;
*b = c * b_in + Complex64::new(0.0, -s) * c_in;
*c_amp = Complex64::new(0.0, -s) * b_in + c * c_in;
*d = Complex64::new(0.0, s) * a_in + c * d_in;
}
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * d_max, kernel);
Ok(())
}
pub fn apply_rzz(&mut self, q0: usize, q1: usize, theta: f64) -> Result<(), QisError> {
self.validate_two_qubits(q0, q1)?;
if theta.abs() < 1e-15 {
return Ok(());
}
let (q_min, q_max) = if q0 < q1 { (q0, q1) } else { (q1, q0) };
let d_max = 1 << q_max;
let d_min = 1 << q_min;
let half_theta = theta * 0.5;
let phase_even = Complex64::from_polar(1.0, -half_theta);
let phase_odd = Complex64::from_polar(1.0, half_theta);
let kernel = |chunk: &mut [Complex64]| {
let (part0, part1) = chunk.split_at_mut(d_max);
part0
.chunks_exact_mut(2 * d_min)
.zip(part1.chunks_exact_mut(2 * d_min))
.for_each(|(sub0, sub1)| {
let (v00, v01) = sub0.split_at_mut(d_min);
let (v10, v11) = sub1.split_at_mut(d_min);
v00.iter_mut().for_each(|a| *a *= phase_even);
v11.iter_mut().for_each(|d| *d *= phase_even);
v01.iter_mut().for_each(|b| *b *= phase_odd);
v10.iter_mut().for_each(|c| *c *= phase_odd);
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * d_max, kernel);
Ok(())
}
pub fn apply_rzx(&mut self, q0: usize, q1: usize, theta: f64) -> Result<(), QisError> {
self.validate_two_qubits(q0, q1)?;
if theta.abs() < 1e-15 {
return Ok(());
}
let (q_min, q_max) = if q0 < q1 { (q0, q1) } else { (q1, q0) };
let d_max = 1 << q_max;
let d_min = 1 << q_min;
let half_theta = theta * 0.5;
let c = half_theta.cos();
let s = half_theta.sin();
let kernel = |chunk: &mut [Complex64]| {
let (part0, part1) = chunk.split_at_mut(d_max);
part0
.chunks_exact_mut(2 * d_min)
.zip(part1.chunks_exact_mut(2 * d_min))
.for_each(|(sub0, sub1)| {
let (v00, v01) = sub0.split_at_mut(d_min);
let (v10, v11) = sub1.split_at_mut(d_min);
for (((a, b), c_amp), d) in v00
.iter_mut()
.zip(v01.iter_mut())
.zip(v10.iter_mut())
.zip(v11.iter_mut())
{
let a_in = *a;
let b_in = *b;
let c_in = *c_amp;
let d_in = *d;
if q0 < q1 {
*a = c * a_in + Complex64::new(0.0, -s) * c_in;
*b = c * b_in + Complex64::new(0.0, s) * d_in;
*c_amp = Complex64::new(0.0, -s) * a_in + c * c_in;
*d = Complex64::new(0.0, s) * b_in + c * d_in;
} else {
*a = c * a_in + Complex64::new(0.0, -s) * b_in;
*b = Complex64::new(0.0, -s) * a_in + c * b_in;
*c_amp = c * c_in + Complex64::new(0.0, s) * d_in;
*d = Complex64::new(0.0, s) * c_in + c * d_in;
}
}
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * d_max, kernel);
Ok(())
}
pub fn apply_crx(&mut self, control: usize, target: usize, theta: f64) -> Result<(), QisError> {
self.validate_two_qubits(control, target)?;
if theta.abs() < 1e-15 {
return Ok(());
}
let (q_min, q_max) = if control < target {
(control, target)
} else {
(target, control)
};
let d_max = 1 << q_max;
let d_min = 1 << q_min;
let half_theta = theta * 0.5;
let c = half_theta.cos();
let s = half_theta.sin();
let kernel = |chunk: &mut [Complex64]| {
let (part0, part1) = chunk.split_at_mut(d_max);
if control < target {
part0
.chunks_exact_mut(2 * d_min)
.zip(part1.chunks_exact_mut(2 * d_min))
.for_each(|(sub0, sub1)| {
let (_, target0_c1) = sub0.split_at_mut(d_min);
let (_, target1_c1) = sub1.split_at_mut(d_min);
for (a, b) in target0_c1.iter_mut().zip(target1_c1.iter_mut()) {
let a_in = *a;
let b_in = *b;
*a = Complex64::new(
c * a_in.re + s * b_in.im,
c * a_in.im - s * b_in.re,
);
*b = Complex64::new(
s * a_in.im + c * b_in.re,
-s * a_in.re + c * b_in.im,
);
}
});
} else {
part0
.chunks_exact_mut(2 * d_min)
.zip(part1.chunks_exact_mut(2 * d_min))
.for_each(|(_, sub1)| {
let (target0_ctl1, target1_ctl1) = sub1.split_at_mut(d_min);
for (a, b) in target0_ctl1.iter_mut().zip(target1_ctl1.iter_mut()) {
let a_in = *a;
let b_in = *b;
*a = Complex64::new(
c * a_in.re + s * b_in.im,
c * a_in.im - s * b_in.re,
);
*b = Complex64::new(
s * a_in.im + c * b_in.re,
-s * a_in.re + c * b_in.im,
);
}
});
}
};
with_maybe_par!(self.num_qubits, self.data, 2 * d_max, kernel);
Ok(())
}
pub fn apply_cry(&mut self, control: usize, target: usize, theta: f64) -> Result<(), QisError> {
self.validate_two_qubits(control, target)?;
if theta.abs() < 1e-15 {
return Ok(());
}
let (q_min, q_max) = if control < target {
(control, target)
} else {
(target, control)
};
let d_max = 1 << q_max;
let d_min = 1 << q_min;
let half_theta = theta * 0.5;
let c = half_theta.cos();
let s = half_theta.sin();
let kernel = |chunk: &mut [Complex64]| {
let (part0, part1) = chunk.split_at_mut(d_max);
if control < target {
part0
.chunks_exact_mut(2 * d_min)
.zip(part1.chunks_exact_mut(2 * d_min))
.for_each(|(sub0, sub1)| {
let (_, target0_c1) = sub0.split_at_mut(d_min);
let (_, target1_c1) = sub1.split_at_mut(d_min);
for (a, b) in target0_c1.iter_mut().zip(target1_c1.iter_mut()) {
let a_in = *a;
let b_in = *b;
*a = c * a_in - s * b_in;
*b = s * a_in + c * b_in;
}
});
} else {
part0
.chunks_exact_mut(2 * d_min)
.zip(part1.chunks_exact_mut(2 * d_min))
.for_each(|(_, sub1)| {
let (target0_ctl1, target1_ctl1) = sub1.split_at_mut(d_min);
for (a, b) in target0_ctl1.iter_mut().zip(target1_ctl1.iter_mut()) {
let a_in = *a;
let b_in = *b;
*a = c * a_in - s * b_in;
*b = s * a_in + c * b_in;
}
});
}
};
with_maybe_par!(self.num_qubits, self.data, 2 * d_max, kernel);
Ok(())
}
pub fn apply_crz(&mut self, control: usize, target: usize, theta: f64) -> Result<(), QisError> {
self.validate_two_qubits(control, target)?;
if theta.abs() < 1e-15 {
return Ok(());
}
let (q_min, q_max) = if control < target {
(control, target)
} else {
(target, control)
};
let d_max = 1 << q_max;
let d_min = 1 << q_min;
let half_theta = theta * 0.5;
let phase_0 = Complex64::from_polar(1.0, -half_theta);
let phase_1 = Complex64::from_polar(1.0, half_theta);
let kernel = |chunk: &mut [Complex64]| {
let (part0, part1) = chunk.split_at_mut(d_max);
if control < target {
part0
.chunks_exact_mut(2 * d_min)
.zip(part1.chunks_exact_mut(2 * d_min))
.for_each(|(sub0, sub1)| {
let (_, target0_c1) = sub0.split_at_mut(d_min);
let (_, target1_c1) = sub1.split_at_mut(d_min);
for (a, b) in target0_c1.iter_mut().zip(target1_c1.iter_mut()) {
*a *= phase_0;
*b *= phase_1;
}
});
} else {
part0
.chunks_exact_mut(2 * d_min)
.zip(part1.chunks_exact_mut(2 * d_min))
.for_each(|(_, sub1)| {
let (target0_ctl1, target1_ctl1) = sub1.split_at_mut(d_min);
for (a, b) in target0_ctl1.iter_mut().zip(target1_ctl1.iter_mut()) {
*a *= phase_0;
*b *= phase_1;
}
});
}
};
with_maybe_par!(self.num_qubits, self.data, 2 * d_max, kernel);
Ok(())
}
pub fn apply_fsim(
&mut self,
q0: usize,
q1: usize,
theta: f64,
phi: f64,
) -> Result<(), QisError> {
self.validate_two_qubits(q0, q1)?;
let (q_min, q_max) = if q0 < q1 { (q0, q1) } else { (q1, q0) };
let d_max = 1 << q_max;
let d_min = 1 << q_min;
let c = theta.cos();
let s = theta.sin();
let phase_11 = Complex64::from_polar(1.0, -phi);
let kernel = |chunk: &mut [Complex64]| {
let (part0, part1) = chunk.split_at_mut(d_max);
part0
.chunks_exact_mut(2 * d_min)
.zip(part1.chunks_exact_mut(2 * d_min))
.for_each(|(sub0, sub1)| {
let (_, v01) = sub0.split_at_mut(d_min);
let (v10, v11) = sub1.split_at_mut(d_min);
v11.iter_mut().for_each(|d| *d *= phase_11);
for (b, c_amp) in v01.iter_mut().zip(v10.iter_mut()) {
let b_in = *b;
let c_in = *c_amp;
*b = c * b_in + Complex64::new(0.0, -s) * c_in;
*c_amp = Complex64::new(0.0, -s) * b_in + c * c_in;
}
});
};
with_maybe_par!(self.num_qubits, self.data, 2 * d_max, kernel);
Ok(())
}
pub fn apply_gphase(&mut self, phi: f64) -> Result<(), QisError> {
if phi.abs() < 1e-15 {
return Ok(());
}
let phase = Complex64::from_polar(1.0, phi);
if self.num_qubits < PARALLEL_THRESHOLD {
self.data.iter_mut().for_each(|a| *a *= phase);
} else {
self.data.par_iter_mut().for_each(|a| *a *= phase);
}
Ok(())
}
pub fn expectation(&self, h: &dyn Observable) -> Result<f64, QisError> {
h.expectation_statevector(self)
}
pub fn measure(&mut self, qubit: usize) -> Result<bool, QisError> {
let mut rng = rand::rng();
self.measure_with_rng(qubit, &mut rng)
}
pub fn measure_with_rng(&mut self, qubit: usize, rng: &mut impl Rng) -> Result<bool, QisError> {
self.validate_qubit(qubit)?;
let n = self.num_qubits;
let size = 1 << n;
let mask = 1 << qubit;
let p1: f64 = (0..size)
.filter(|i| i & mask != 0)
.map(|i| self.data[i].norm_sqr())
.sum();
let outcome = rng.random::<f64>() < p1;
let norm = if outcome {
p1.sqrt()
} else {
(1.0 - p1).sqrt()
};
for i in 0..size {
let keep = if outcome {
(i & mask) != 0
} else {
(i & mask) == 0
};
if keep {
self.data[i] /= norm;
} else {
self.data[i] = Complex64::new(0.0, 0.0);
}
}
Ok(outcome)
}
pub fn reset(&mut self, qubit: usize) -> Result<(), QisError> {
if self.measure(qubit)? {
self.apply_x(qubit)?;
}
Ok(())
}
fn reset_from(&mut self, source: &Statevector) {
debug_assert_eq!(self.num_qubits, source.num_qubits);
self.qubit_map.clone_from(&source.qubit_map);
self.data
.as_mut_slice()
.copy_from_slice(source.data.as_slice());
}
pub fn measure_all(&mut self) -> Outcome {
self.measure_all_with_rng(&mut rand::rng())
}
pub fn measure_all_with_rng(&mut self, rng: &mut impl Rng) -> Outcome {
let num_chunks = self.num_qubits.div_ceil(64);
let mut chunks = SmallVec::from_elem(0u64, num_chunks);
for q in 0..self.num_qubits {
if self.measure_with_rng(q, rng).unwrap() {
chunks[q / 64] |= 1u64 << (q % 64);
}
}
Outcome::new(chunks)
}
pub fn sample_shots(&self, shots: usize) -> Vec<Outcome> {
self.sample_shots_with_seed(shots, None)
}
pub fn sample_shots_with_seed(&self, shots: usize, seed: Option<u64>) -> Vec<Outcome> {
if let Some(seed) = seed {
let mut rng = SmallRng::seed_from_u64(seed);
let seeds: Vec<u64> = (0..shots).map(|_| rng.random()).collect();
return seeds
.into_par_iter()
.map_with(self.clone(), |work, seed| {
work.reset_from(self);
work.measure_all_with_rng(&mut SmallRng::seed_from_u64(seed))
})
.collect();
}
(0..shots)
.into_par_iter()
.map_with(self.clone(), |work, _| {
work.reset_from(self);
work.measure_all()
})
.collect()
}
pub fn sample(
&self,
measurement: &Measurement,
shots: usize,
) -> Result<ExecutionResult, QisError> {
self.sample_with_seed(measurement, shots, None)
}
pub fn sample_with_seed(
&self,
measurement: &Measurement,
shots: usize,
seed: Option<u64>,
) -> Result<ExecutionResult, QisError> {
let projection = super::resolve_measurement(measurement, &self.qubit_map, self.num_qubits)?;
let mut counts = HashMap::new();
for full in self.sample_shots_with_seed(shots, seed) {
*counts.entry(projection.project(&full)).or_insert(0usize) += 1;
}
Ok(ExecutionResult::from_counts(
"statevector-sample".to_string(),
measurement.qubits().to_vec(),
shots,
measurement.width(),
Some("statevector".to_string()),
counts,
))
}
pub fn probs(&self, measurement: &Measurement) -> Result<HashMap<Outcome, f64>, QisError> {
let projection = super::resolve_measurement(measurement, &self.qubit_map, self.num_qubits)?;
let mut marginal = HashMap::new();
for (basis, prob) in self.probabilities().into_iter().enumerate() {
if prob == 0.0 {
continue;
}
*marginal
.entry(projection.project_basis(basis))
.or_insert(0.0) += prob;
}
Ok(marginal)
}
}
#[cfg(test)]
#[path = "./statevector_test.rs"]
mod statevector_test;