pub(crate) mod kernels;
#[cfg(test)]
mod tests;
use num_complex::Complex64;
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;
#[cfg(feature = "gpu")]
use std::sync::Arc;
use crate::backend::{Backend, dense_statevector_len, statevector_probability_len};
use crate::backend::{reduced_density, schmidt, simd};
use crate::circuit::Instruction;
#[cfg(feature = "gpu")]
use crate::circuit::{QftTextbookStep, qft_textbook_steps};
use crate::error::Result;
use crate::gates::Gate;
use crate::sim::unified_pauli::PauliTerm;
#[cfg(feature = "gpu")]
use crate::gpu::{GpuContext, GpuState};
#[cfg(feature = "parallel")]
use rayon::prelude::*;
#[cfg(feature = "parallel")]
pub(crate) use super::{MIN_PAR_ELEMS, PARALLEL_THRESHOLD_QUBITS};
#[cfg(feature = "parallel")]
const PARALLEL_INIT_QUBITS: usize = 20;
#[cfg(feature = "parallel")]
const INIT_ZERO_CHUNK: usize = 1 << 16;
#[inline(always)]
pub(super) fn rdm_block_sums(block: &[Complex64], half: usize) -> (f64, f64, Complex64) {
let (lo, hi) = block.split_at(half);
rdm_pair_sums(lo, hi)
}
#[inline(always)]
pub(super) fn rdm_pair_sums(lo: &[Complex64], hi: &[Complex64]) -> (f64, f64, Complex64) {
let mut p0 = 0.0f64;
let mut p1 = 0.0f64;
let mut r = Complex64::new(0.0, 0.0);
for (&a0, &a1) in lo.iter().zip(hi.iter()) {
p0 += a0.norm_sqr();
p1 += a1.norm_sqr();
r += a1 * a0.conj();
}
(p0, p1, r)
}
#[cfg(feature = "parallel")]
#[inline(always)]
pub(super) fn rdm_sum_add(
a: (f64, f64, Complex64),
b: (f64, f64, Complex64),
) -> (f64, f64, Complex64) {
(a.0 + b.0, a.1 + b.1, a.2 + b.2)
}
#[cfg(feature = "gpu")]
fn launch_pauli_rotation_gpu(
ctx: &GpuContext,
gpu: &mut GpuState,
theta: f64,
targets: &[usize],
axes: &[crate::sim::unified_pauli::PauliAxis],
) -> Result<()> {
use crate::gpu::kernels::dense as k;
let mut result = Ok(());
crate::circuit::pauli_rotation_lowering(theta, targets, axes, |step, tgts| {
if result.is_err() {
return;
}
result = match &step {
Gate::Cx => k::launch_apply_cx(ctx, gpu, tgts[0], tgts[1]),
_ => {
let mat = step.matrix_2x2();
if step.is_diagonal_1q() {
k::launch_apply_diagonal_1q(ctx, gpu, tgts[0], mat[0][0], mat[1][1])
} else {
k::launch_apply_gate_1q(ctx, gpu, tgts[0], mat)
}
}
};
});
result
}
#[cfg(feature = "gpu")]
fn check_device_budget(context: &GpuContext, num_qubits: usize) -> crate::error::Result<()> {
if num_qubits >= usize::BITS as usize - 4 {
return Ok(());
}
let Ok(free) = context.vram_available() else {
return Ok(());
};
let needed = (1usize << num_qubits) * crate::gpu::STATEVECTOR_BYTES_PER_AMPLITUDE;
if needed > free {
return Err(crate::error::PrismError::ResourceLimit(Box::new(
crate::error::ResourceLimit {
backend: "statevector-gpu".to_string(),
operation: format!("the {num_qubits}-qubit state on the device"),
resource: crate::error::ResourceKind::DeviceBytes,
required: needed as u128,
limit: free as u128,
env_var: None,
},
)));
}
Ok(())
}
#[inline(always)]
pub(crate) fn insert_zero_bit(val: usize, bit_pos: usize) -> usize {
let lo = val & ((1 << bit_pos) - 1);
let hi = val >> bit_pos;
(hi << (bit_pos + 1)) | lo
}
#[cfg(feature = "parallel")]
#[derive(Copy, Clone)]
pub(crate) struct SendPtr(pub(crate) *mut Complex64);
#[cfg(feature = "parallel")]
unsafe impl Send for SendPtr {}
#[cfg(feature = "parallel")]
unsafe impl Sync for SendPtr {}
#[cfg(feature = "parallel")]
impl SendPtr {
#[inline(always)]
pub(crate) unsafe fn load(self, idx: usize) -> Complex64 {
unsafe { *self.0.add(idx) }
}
#[inline(always)]
pub(crate) unsafe fn store(self, idx: usize, val: Complex64) {
unsafe {
*self.0.add(idx) = val;
}
}
#[inline(always)]
pub(crate) fn as_f64_ptr(self) -> *mut f64 {
self.0 as *mut f64
}
#[inline(always)]
pub(crate) fn as_complex_ptr(self) -> *mut Complex64 {
self.0
}
}
pub struct StatevectorBackend {
pub(crate) num_qubits: usize,
pub(crate) state: Vec<Complex64>,
pub(crate) classical_bits: Vec<bool>,
pub(crate) rng: ChaCha8Rng,
pub(crate) pending_norm: f64,
pub(crate) batch_tables: kernels::BatchTableScratch,
#[cfg(feature = "gpu")]
gpu_context: Option<Arc<GpuContext>>,
#[cfg(feature = "gpu")]
gpu_state: Option<GpuState>,
#[cfg(feature = "gpu")]
gpu_soft: bool,
}
#[inline]
fn qft_block_enabled() -> bool {
use std::sync::OnceLock;
static ENABLED: OnceLock<bool> = OnceLock::new();
*ENABLED.get_or_init(|| std::env::var_os("PRISM_NO_QFT_BLOCK").is_none())
}
impl StatevectorBackend {
pub fn new(seed: u64) -> Self {
Self {
num_qubits: 0,
state: Vec::new(),
classical_bits: Vec::new(),
rng: ChaCha8Rng::seed_from_u64(seed),
pending_norm: 1.0,
batch_tables: kernels::BatchTableScratch::default(),
#[cfg(feature = "gpu")]
gpu_context: None,
#[cfg(feature = "gpu")]
gpu_state: None,
#[cfg(feature = "gpu")]
gpu_soft: false,
}
}
#[cfg(feature = "gpu")]
pub fn with_gpu(mut self, context: Arc<GpuContext>) -> Self {
self.gpu_context = Some(context);
self
}
#[cfg(feature = "gpu")]
pub fn with_gpu_auto(mut self, context: Arc<GpuContext>) -> Self {
self.gpu_context = Some(context);
self.gpu_soft = true;
self
}
#[cfg(feature = "gpu")]
fn apply_gpu(&mut self, instruction: &Instruction) -> Result<()> {
debug_assert!(
self.gpu_state.is_some(),
"apply_gpu called without gpu_state (callers must check self.gpu_state.is_some() first)"
);
match instruction {
Instruction::Gate { gate, targets } => self.dispatch_gate_gpu(gate, targets),
Instruction::Measure {
qubit,
classical_bit,
} => self.apply_measure_gpu(*qubit, *classical_bit),
Instruction::Reset { qubit } => self.apply_reset_gpu(*qubit),
Instruction::Barrier { .. } => Ok(()),
Instruction::Save { label, .. } => {
Err(crate::backend::save_not_applied("Statevector", label))
}
Instruction::Conditional {
condition,
gate,
targets,
} => {
if condition.evaluate(&self.classical_bits) {
self.dispatch_gate_gpu(gate, targets)
} else {
Ok(())
}
}
Instruction::Region(region) => self.apply_region(region),
}
}
#[cfg(feature = "gpu")]
fn dispatch_gate_gpu(&mut self, gate: &Gate, targets: &[usize]) -> Result<()> {
use crate::gpu::kernels::dense as k;
let gpu = self
.gpu_state
.as_mut()
.expect("dispatch_gate_gpu called without gpu_state");
let ctx = gpu.context().clone();
match gate {
Gate::Rzz(theta) => k::launch_apply_rzz(&ctx, gpu, targets[0], targets[1], *theta),
Gate::Cx => k::launch_apply_cx(&ctx, gpu, targets[0], targets[1]),
Gate::Cz => k::launch_apply_cz(&ctx, gpu, targets[0], targets[1]),
Gate::Swap => k::launch_apply_swap(&ctx, gpu, targets[0], targets[1]),
Gate::Cu(mat) => {
if let Some(phase) = gate.controlled_phase() {
k::launch_apply_cu_phase(&ctx, gpu, targets[0], targets[1], phase)
} else {
k::launch_apply_cu(&ctx, gpu, targets[0], targets[1], **mat)
}
}
Gate::Mcu(data) => {
let num_ctrl = data.num_controls as usize;
let controls = &targets[..num_ctrl];
let target = targets[num_ctrl];
if let Some(phase) = gate.controlled_phase() {
k::launch_apply_mcu_phase(&ctx, gpu, controls, target, phase)
} else {
k::launch_apply_mcu(&ctx, gpu, controls, target, data.mat)
}
}
Gate::BatchPhase(data) => {
let control = targets[0];
k::launch_apply_batch_phase(&ctx, gpu, control, &data.phases)
}
Gate::BatchRzz(data) => k::launch_apply_batch_rzz(&ctx, gpu, &data.edges),
Gate::DiagonalBatch(data) => k::launch_apply_diagonal_batch(&ctx, gpu, &data.entries),
Gate::QftBlock {
start,
num,
inverse,
swaps,
big_endian,
} => {
let h = Gate::H.matrix_2x2();
for step in qft_textbook_steps(
*start as usize,
*num as usize,
*inverse,
*swaps,
*big_endian,
) {
match step {
QftTextbookStep::Hadamard(q) => k::launch_apply_gate_1q(&ctx, gpu, q, h)?,
QftTextbookStep::CPhase {
control,
target,
theta,
} => k::launch_apply_cu_phase(
&ctx,
gpu,
control,
target,
Complex64::from_polar(1.0, theta),
)?,
QftTextbookStep::Swap(a, b) => k::launch_apply_swap(&ctx, gpu, a, b)?,
}
}
Ok(())
}
Gate::PauliRot(data) => {
launch_pauli_rotation_gpu(&ctx, gpu, data.theta, targets, &data.axes)
}
Gate::MultiPauliRot(data) => {
let mut result = Ok(());
data.for_each_rotation(|theta, targets, axes| {
if result.is_ok() {
result = launch_pauli_rotation_gpu(&ctx, gpu, theta, targets, axes);
}
});
result
}
Gate::Unitary(_) => Err(crate::error::PrismError::BackendUnsupported {
backend: "statevector (device-resident)".to_string(),
operation: format!("dense multi-qubit gate `{}`", gate.name()),
}),
Gate::MultiFused(data) => {
if data.all_diagonal {
k::launch_apply_multi_fused_diagonal(&ctx, gpu, &data.gates)
} else {
k::launch_apply_multi_fused_nondiag(&ctx, gpu, &data.gates)
}
}
Gate::Fused2q(mat) => k::launch_apply_fused_2q(&ctx, gpu, targets[0], targets[1], mat),
Gate::Multi2q(data) => {
for &(q0, q1, mat) in &data.gates {
k::launch_apply_fused_2q(&ctx, gpu, q0, q1, &mat)?;
}
Ok(())
}
Gate::Id
| Gate::X
| Gate::Y
| Gate::Z
| Gate::H
| Gate::S
| Gate::Sdg
| Gate::T
| Gate::Tdg
| Gate::SX
| Gate::SXdg
| Gate::Rx(_)
| Gate::Ry(_)
| Gate::Rz(_)
| Gate::P(_)
| Gate::Fused(_) => {
let mat = gate.matrix_2x2();
if gate.is_diagonal_1q() {
k::launch_apply_diagonal_1q(&ctx, gpu, targets[0], mat[0][0], mat[1][1])
} else {
k::launch_apply_gate_1q(&ctx, gpu, targets[0], mat)
}
}
}
}
#[cfg(feature = "gpu")]
fn apply_measure_gpu(&mut self, qubit: usize, classical_bit: usize) -> Result<()> {
use crate::gpu::kernels::dense as k;
use rand::RngExt;
let gpu = self
.gpu_state
.as_mut()
.expect("apply_measure_gpu called without gpu_state");
let ctx = gpu.context().clone();
let prob_one = k::measure_prob_one(&ctx, gpu, qubit)?;
let u: f64 = self.rng.random();
let outcome = u < prob_one;
self.classical_bits[classical_bit] = outcome;
k::measure_collapse(&ctx, gpu, qubit, outcome)?;
let inv_sqrt = crate::backend::measurement_inv_norm(outcome, prob_one);
gpu.set_pending_norm(gpu.pending_norm() * inv_sqrt);
Ok(())
}
#[cfg(feature = "gpu")]
fn apply_reset_gpu(&mut self, qubit: usize) -> Result<()> {
use crate::gpu::kernels::dense as k;
use rand::RngExt;
let gpu = self
.gpu_state
.as_mut()
.expect("apply_reset_gpu called without gpu_state");
let ctx = gpu.context().clone();
let prob_one = k::measure_prob_one(&ctx, gpu, qubit)?;
let u: f64 = self.rng.random();
let outcome = u < prob_one;
let gpu = self
.gpu_state
.as_mut()
.expect("apply_reset_gpu called without gpu_state");
k::measure_collapse(&ctx, gpu, qubit, outcome)?;
let inv_norm = crate::backend::measurement_inv_norm(outcome, prob_one);
gpu.set_pending_norm(gpu.pending_norm() * inv_norm);
if outcome {
self.dispatch_gate_gpu(&Gate::X, &[qubit])?;
}
Ok(())
}
#[cfg(feature = "gpu")]
fn apply_1q_matrix_gpu(&mut self, qubit: usize, matrix: &[[Complex64; 2]; 2]) -> Result<()> {
use crate::gpu::kernels::dense as k;
let gpu = self
.gpu_state
.as_mut()
.expect("apply_1q_matrix_gpu called without gpu_state");
let ctx = gpu.context().clone();
let is_diagonal = matrix[0][1].norm() < 1e-14 && matrix[1][0].norm() < 1e-14;
if is_diagonal {
k::launch_apply_diagonal_1q(&ctx, gpu, qubit, matrix[0][0], matrix[1][1])
} else {
k::launch_apply_gate_1q(&ctx, gpu, qubit, *matrix)
}
}
pub fn state_vector(&self) -> &[Complex64] {
&self.state
}
pub(crate) fn probability_scale(&self) -> f64 {
self.pending_norm * self.pending_norm
}
pub(crate) fn copy_state_from(&mut self, source: &Self) {
self.num_qubits = source.num_qubits;
self.pending_norm = source.pending_norm;
self.classical_bits.clone_from(&source.classical_bits);
self.state.clone_from(&source.state);
}
pub(crate) fn host_probability_vector(&self) -> Vec<f64> {
let norm_sq = self.probability_scale();
let mut probs = vec![0.0_f64; self.state.len()];
#[cfg(feature = "parallel")]
if self.num_qubits >= PARALLEL_THRESHOLD_QUBITS {
let src_chunks = self.state.par_chunks(MIN_PAR_ELEMS);
let dst_chunks = probs.par_chunks_mut(MIN_PAR_ELEMS);
if norm_sq == 1.0 {
src_chunks.zip(dst_chunks).for_each(|(s, d)| {
simd::norm_sqr_to_slice(s, d);
});
} else {
src_chunks.zip(dst_chunks).for_each(|(s, d)| {
simd::norm_sqr_to_slice_scaled(s, d, norm_sq);
});
}
return probs;
}
if norm_sq == 1.0 {
simd::norm_sqr_to_slice(&self.state, &mut probs);
} else {
simd::norm_sqr_to_slice_scaled(&self.state, &mut probs, norm_sq);
}
probs
}
#[cfg(feature = "gpu")]
pub(crate) fn is_gpu_resident(&self) -> bool {
self.gpu_state.is_some()
}
#[cfg(not(feature = "gpu"))]
pub(crate) fn is_gpu_resident(&self) -> bool {
false
}
#[cfg(feature = "gpu")]
pub(crate) fn gpu_pauli_sums(&self, masks: &[(u64, u64)]) -> Option<Result<Vec<Complex64>>> {
let gpu = self.gpu_state.as_ref()?;
Some(crate::gpu::kernels::dense::pauli_sums(
gpu.context(),
gpu,
masks,
))
}
#[cfg(not(feature = "gpu"))]
pub(crate) fn gpu_pauli_sums(&self, _masks: &[(u64, u64)]) -> Option<Result<Vec<Complex64>>> {
None
}
#[cfg(feature = "gpu")]
pub(crate) fn group_moments_on_device(
&self,
groups: &[(Vec<u64>, Vec<f64>, u64, u64)],
) -> Option<Result<Vec<(f64, f64)>>> {
let gpu = self.gpu_state.as_ref()?;
crate::gpu::kernels::dense::group_moments(gpu.context(), gpu, groups).transpose()
}
#[cfg(not(feature = "gpu"))]
pub(crate) fn group_moments_on_device(
&self,
_groups: &[(Vec<u64>, Vec<f64>, u64, u64)],
) -> Option<Result<Vec<(f64, f64)>>> {
None
}
pub(crate) fn pauli_expectations_on_device(
&self,
masks: &[(usize, usize, u32)],
) -> Option<Result<Vec<f64>>> {
if !self.is_gpu_resident() {
return None;
}
let request: Vec<(u64, u64)> = masks
.iter()
.map(|&(xmask, zmask, _)| (xmask as u64, zmask as u64))
.chain(std::iter::once((0, 0)))
.collect();
let sums = match self.gpu_pauli_sums(&request)? {
Ok(sums) => sums,
Err(e) => return Some(Err(e)),
};
let norm = sums[masks.len()].re;
if norm == 0.0 {
return Some(Ok(vec![0.0; masks.len()]));
}
Some(Ok(masks
.iter()
.zip(&sums)
.map(|(&(_, _, num_y), sum)| (sum * crate::sim::i_pow(num_y)).re / norm)
.collect()))
}
#[cfg(feature = "gpu")]
pub(crate) fn gpu_state(&self) -> Option<&GpuState> {
self.gpu_state.as_ref()
}
#[cfg(feature = "gpu")]
pub(crate) fn gpu_state_mut(&mut self) -> Option<&mut GpuState> {
self.gpu_state.as_mut()
}
pub fn init_from_state(
&mut self,
state: Vec<Complex64>,
num_classical_bits: usize,
) -> crate::error::Result<()> {
#[cfg(feature = "parallel")]
crate::backend::init_thread_pool();
let dim = state.len();
if !dim.is_power_of_two() || dim < 2 {
return Err(crate::error::PrismError::InvalidParameter {
message: format!(
"state vector length must be a power of 2 and >= 2, got {}",
dim
),
});
}
self.num_qubits = dim.trailing_zeros() as usize;
self.pending_norm = 1.0;
crate::backend::init_classical_bits(&mut self.classical_bits, num_classical_bits);
#[cfg(feature = "gpu")]
if let Some(ctx) = self.gpu_context.clone() {
if !self.gpu_soft {
check_device_budget(&ctx, self.num_qubits)?;
}
match GpuState::from_host_amplitudes(ctx, &state) {
Ok(gpu) => {
self.state.clear();
self.gpu_state = Some(gpu);
return Ok(());
}
Err(e) if !self.gpu_soft => return Err(e),
Err(_) => {
self.gpu_context = None;
self.gpu_state = None;
}
}
}
self.state = state;
Ok(())
}
#[inline(always)]
pub(crate) fn dispatch_gate(&mut self, gate: &Gate, targets: &[usize]) {
match gate {
Gate::Rzz(theta) => self.apply_rzz(targets[0], targets[1], *theta),
Gate::Cx => self.apply_cx(targets[0], targets[1]),
Gate::Cz => self.apply_cz(targets[0], targets[1]),
Gate::Swap => self.apply_swap(targets[0], targets[1]),
Gate::Cu(mat) => {
if let Some(phase) = gate.controlled_phase() {
self.apply_cu_phase(targets[0], targets[1], phase);
} else {
self.apply_cu(targets[0], targets[1], **mat);
}
}
Gate::Mcu(data) => {
let num_ctrl = data.num_controls as usize;
if let Some(phase) = gate.controlled_phase() {
self.apply_mcu_phase(&targets[..num_ctrl], targets[num_ctrl], phase);
} else {
self.apply_mcu(&targets[..num_ctrl], targets[num_ctrl], data.mat);
}
}
Gate::BatchPhase(data) => {
self.apply_batch_phase(targets[0], &data.phases);
}
&Gate::QftBlock {
start,
num,
inverse,
swaps,
big_endian,
} => {
if start == 0 {
self.apply_qft_block(num as usize, inverse, swaps, big_endian);
} else {
self.apply_qft_block_textbook(
start as usize,
num as usize,
inverse,
swaps,
big_endian,
);
}
}
Gate::BatchRzz(data) => {
self.apply_batch_rzz(&data.edges);
}
Gate::DiagonalBatch(data) => {
self.apply_diagonal_batch(&data.entries);
}
Gate::PauliRot(data) => {
self.apply_pauli_rot(targets, data.theta, &data.axes);
}
Gate::MultiPauliRot(data) => {
self.apply_multi_pauli_rot(&data.rotations);
}
Gate::Unitary(data) => {
self.apply_unitary(targets, data.matrix());
}
Gate::MultiFused(data) => {
if data.all_diagonal {
self.apply_multi_1q_diagonal(&data.gates);
} else {
self.apply_multi_1q(&data.gates);
}
}
Gate::Fused2q(mat) => {
self.apply_fused_2q(targets[0], targets[1], mat);
}
Gate::Multi2q(data) => {
self.apply_multi_2q(&data.gates);
}
Gate::Id
| Gate::X
| Gate::Y
| Gate::Z
| Gate::H
| Gate::S
| Gate::Sdg
| Gate::T
| Gate::Tdg
| Gate::SX
| Gate::SXdg
| Gate::Rx(_)
| Gate::Ry(_)
| Gate::Rz(_)
| Gate::P(_)
| Gate::Fused(_) => {
let mat = gate.matrix_2x2();
if gate.is_diagonal_1q() {
self.apply_diagonal_gate(targets[0], mat[0][0], mat[1][1]);
} else {
self.apply_single_gate(targets[0], mat);
}
}
}
}
}
impl crate::backend::sealed::Sealed for StatevectorBackend {}
impl Backend for StatevectorBackend {
fn name(&self) -> &'static str {
"statevector"
}
fn as_any(&self) -> Option<&dyn std::any::Any> {
Some(self)
}
fn resolved(&self) -> crate::sim::ResolvedBackend {
crate::sim::ResolvedBackend::Statevector
}
fn placement(&self) -> crate::sim::Placement {
if self.is_gpu_resident() {
crate::sim::Placement::Device
} else {
crate::sim::Placement::Host
}
}
fn supports_qft_block(&self) -> bool {
if !qft_block_enabled() {
return false;
}
#[cfg(feature = "gpu")]
{
self.gpu_context.is_none()
}
#[cfg(not(feature = "gpu"))]
{
true
}
}
fn supports_pauli_rotation(&self) -> bool {
#[cfg(feature = "gpu")]
{
self.gpu_context.is_none()
}
#[cfg(not(feature = "gpu"))]
{
true
}
}
fn init(&mut self, num_qubits: usize, num_classical_bits: usize) -> Result<()> {
#[cfg(feature = "parallel")]
crate::backend::init_thread_pool();
self.num_qubits = num_qubits;
self.pending_norm = 1.0;
crate::backend::init_classical_bits(&mut self.classical_bits, num_classical_bits);
#[cfg(feature = "gpu")]
if let Some(ctx) = self.gpu_context.clone() {
if !self.gpu_soft {
check_device_budget(&ctx, num_qubits)?;
}
match GpuState::new(ctx, num_qubits) {
Ok(state) => {
self.state.clear();
self.gpu_state = Some(state);
return Ok(());
}
Err(e) if !self.gpu_soft => return Err(e),
Err(_) => {
self.gpu_context = None;
self.gpu_state = None;
}
}
}
crate::backend::check_state_allocation(
"statevector",
num_qubits,
crate::backend::max_statevector_qubits(),
"PRISM_MAX_SV_QUBITS",
)?;
let dim = 1usize << num_qubits;
let zero = Complex64::new(0.0, 0.0);
let reuse = self.state.len() == dim;
if !reuse {
self.state = Vec::new();
}
#[cfg(feature = "parallel")]
if num_qubits >= PARALLEL_INIT_QUBITS {
if !reuse {
self.state = Vec::with_capacity(dim);
}
let base = SendPtr(self.state.as_mut_ptr());
(0..dim / INIT_ZERO_CHUNK).into_par_iter().for_each(|c| {
unsafe {
std::ptr::write_bytes(
base.as_complex_ptr().add(c * INIT_ZERO_CHUNK),
0,
INIT_ZERO_CHUNK,
);
}
});
unsafe { self.state.set_len(dim) };
self.state[0] = Complex64::new(1.0, 0.0);
return Ok(());
}
if reuse {
self.state.fill(zero);
} else {
self.state = vec![zero; dim];
}
self.state[0] = Complex64::new(1.0, 0.0);
Ok(())
}
fn supports_initial_state(&self) -> bool {
true
}
fn init_from_amplitudes(
&mut self,
amplitudes: Vec<Complex64>,
num_classical_bits: usize,
) -> Result<()> {
crate::backend::validate_initial_amplitudes(&litudes)?;
self.init_from_state(amplitudes, num_classical_bits)
}
fn apply(&mut self, instruction: &Instruction) -> Result<()> {
#[cfg(feature = "gpu")]
if self.gpu_state.is_some() {
return self.apply_gpu(instruction);
}
match instruction {
Instruction::Gate { gate, targets } => self.dispatch_gate(gate, targets),
Instruction::Measure {
qubit,
classical_bit,
} => {
self.apply_measure(*qubit, *classical_bit);
}
Instruction::Reset { qubit } => {
self.apply_reset(*qubit);
}
Instruction::Barrier { .. } => {}
Instruction::Save { label, .. } => {
return Err(crate::backend::save_not_applied("Statevector", label));
}
Instruction::Conditional {
condition,
gate,
targets,
} => {
if condition.evaluate(&self.classical_bits) {
self.dispatch_gate(gate, targets);
}
}
Instruction::Region(region) => self.apply_region(region)?,
}
Ok(())
}
fn classical_results(&self) -> &[bool] {
&self.classical_bits
}
fn probabilities(&self) -> Result<Vec<f64>> {
#[cfg(feature = "gpu")]
if let Some(gpu) = self.gpu_state.as_ref() {
return gpu.probabilities();
}
statevector_probability_len(self.name(), self.num_qubits)?;
Ok(self.host_probability_vector())
}
fn num_qubits(&self) -> usize {
self.num_qubits
}
fn export_statevector(&self) -> crate::error::Result<Vec<Complex64>> {
#[cfg(feature = "gpu")]
if let Some(gpu) = self.gpu_state.as_ref() {
return gpu.export_statevector();
}
dense_statevector_len(self.name(), "statevector export", self.num_qubits)?;
if self.pending_norm == 1.0 {
return Ok(self.state.clone());
}
let s = Complex64::new(self.pending_norm, 0.0);
Ok(self.state.iter().map(|&c| c * s).collect())
}
fn qubit_probability(&self, qubit: usize) -> crate::error::Result<f64> {
#[cfg(feature = "gpu")]
if let Some(gpu) = self.gpu_state.as_ref() {
return crate::gpu::kernels::dense::measure_prob_one(gpu.context(), gpu, qubit);
}
Ok(self.qubit_probability_one(qubit))
}
fn reduced_density_matrix_1q(&self, qubit: usize) -> Result<[[Complex64; 2]; 2]> {
#[cfg(feature = "gpu")]
if let Some(gpu) = self.gpu_state.as_ref() {
return crate::gpu::kernels::dense::reduced_density_matrix_1q(
gpu.context(),
gpu,
qubit,
);
}
Ok(self.reduced_density_matrix_one(qubit))
}
fn supports_two_qubit_kraus(&self) -> bool {
#[cfg(feature = "gpu")]
if self.gpu_state.is_some() {
return false;
}
true
}
fn reduced_density_matrix_2q(&mut self, q0: usize, q1: usize) -> Result<[[Complex64; 4]; 4]> {
assert_ne!(q0, q1, "reduced_density_matrix_2q needs distinct qubits");
#[cfg(feature = "gpu")]
if self.gpu_state.is_some() {
return Err(crate::error::PrismError::BackendUnsupported {
backend: "statevector (device-resident)".to_string(),
operation: "reduced_density_matrix_2q".to_string(),
});
}
Ok(self.reduced_density_matrix_two(q0, q1))
}
fn supports_pauli_expectation(&self) -> bool {
true
}
fn pauli_expectations(&self, observables: &[Vec<PauliTerm>]) -> Result<Vec<f64>> {
let masks = observables
.iter()
.map(|observable| crate::sim::pauli_masks(observable, self.num_qubits))
.collect::<Result<Vec<_>>>()?;
if let Some(values) = self.pauli_expectations_on_device(&masks) {
return values;
}
Ok(crate::sim::pauli_expectations_from_masks(&self.state, &masks).0)
}
fn schmidt_values(&mut self, subsystem: &[usize]) -> Result<Vec<f64>> {
schmidt::validate_subsystem(subsystem, self.num_qubits)?;
#[cfg(feature = "gpu")]
if let Some(gpu) = self.gpu_state.as_ref() {
let state = gpu.export_statevector()?;
return schmidt::dense_schmidt_values(self.name(), &state, self.num_qubits, subsystem);
}
schmidt::dense_schmidt_values(self.name(), &self.state, self.num_qubits, subsystem)
}
fn reduced_density_matrix(&mut self, subsystem: &[usize]) -> Result<Vec<Complex64>> {
schmidt::validate_qubit_set(subsystem, self.num_qubits)?;
let dim = reduced_density::reduced_density_side(self.name(), subsystem.len())?;
#[cfg(feature = "gpu")]
let readback = self
.gpu_state
.as_ref()
.map(|gpu| gpu.export_statevector())
.transpose()?;
#[cfg(feature = "gpu")]
let state = readback.as_deref().unwrap_or(self.state.as_slice());
#[cfg(not(feature = "gpu"))]
let state = self.state.as_slice();
let mut rho = reduced_density::dense_reduced_density(state, self.num_qubits, subsystem);
reduced_density::normalize_trace(&mut rho, dim);
Ok(rho)
}
fn reset(&mut self, qubit: usize) -> Result<()> {
#[cfg(feature = "gpu")]
if self.gpu_state.is_some() {
return self.apply_reset_gpu(qubit);
}
self.apply_reset(qubit);
Ok(())
}
fn apply_1q_matrix(&mut self, qubit: usize, matrix: &[[Complex64; 2]; 2]) -> Result<()> {
#[cfg(feature = "gpu")]
if self.gpu_state.is_some() {
return self.apply_1q_matrix_gpu(qubit, matrix);
}
let is_diagonal = matrix[0][1].norm() < 1e-14 && matrix[1][0].norm() < 1e-14;
if is_diagonal {
self.apply_diagonal_gate(qubit, matrix[0][0], matrix[1][1]);
} else {
self.apply_single_gate(qubit, *matrix);
}
Ok(())
}
}