use crate::backend::density_matrix::DensityMatrixBackend;
use crate::backend::mps::MpsBackend;
use crate::backend::product::ProductStateBackend;
use crate::backend::sparse::{MAX_SPARSE_INDEX_QUBITS, SparseBackend};
use crate::backend::stabilizer::StabilizerBackend;
use crate::backend::statevector::StatevectorBackend;
use crate::backend::tensornetwork::TensorNetworkBackend;
use crate::backend::{
Backend, DM_QUBIT_CAP_ENV, check_state_allocation, max_density_matrix_qubits,
max_statevector_qubits,
};
use crate::circuit::{Circuit, Instruction};
use crate::error::{PrismError, Result};
use crate::gates::Gate;
use crate::sim::unified_pauli::SpdTruncation;
#[cfg(any(feature = "gpu", feature = "distributed"))]
use std::sync::Arc;
#[cfg(feature = "gpu")]
use crate::gpu::GpuContext;
#[cfg(feature = "distributed")]
use crate::backend::distributed_statevector::DistributedStatevectorBackend;
#[cfg(feature = "distributed")]
use crate::distributed::DistributedContext;
use super::StartState;
use super::metadata::ResolvedBackend;
use super::{RunOutcome, try_backend_probabilities};
pub(super) const AUTO_MPS_BOND_DIM: usize = 256;
const MPS_GATE_CUT_OVERHEAD: f64 = 256.0;
pub(super) fn mps_apply_cost(circuit: &Circuit, max_bond: usize) -> f64 {
let n = circuit.num_qubits;
if n < 2 {
return 0.0;
}
let limit: Vec<f64> = (0..n - 1)
.map(|cut| {
2f64.powi((cut + 1).min(n - cut - 1) as i32)
.min(max_bond as f64)
})
.collect();
let mut bond = vec![1.0f64; n - 1];
let mut base = vec![1.0f64; n - 1];
let mut block_rank = vec![1.0f64; n - 1];
let mut qubit_block = vec![0usize; n];
let mut cut_block = vec![0usize; n - 1];
let mut next_block = 1usize;
let mut cost = 0.0;
for instruction in &circuit.instructions {
let Instruction::Gate { gate, targets } = instruction else {
continue;
};
if targets.len() < 2 {
continue;
}
let lo = *targets.iter().min().expect("two or more targets");
let hi = *targets.iter().max().expect("two or more targets");
let rank = match gate {
Gate::Cx | Gate::Cz | Gate::Rzz(_) | Gate::Cu(_) | Gate::Mcu(_) => 2.0,
_ => 4.0,
};
let block = qubit_block[lo];
let continues = targets.len() == 2
&& block != 0
&& qubit_block[hi] == block
&& cut_block[lo..hi].iter().all(|&b| b == block);
let block = if continues {
block
} else {
next_block += 1;
next_block - 1
};
for cut in lo..hi {
if continues {
block_rank[cut] = (block_rank[cut] * rank).min(4.0);
} else {
base[cut] = bond[cut];
block_rank[cut] = rank;
}
bond[cut] = (base[cut] * block_rank[cut]).min(limit[cut]);
cut_block[cut] = block;
cost += MPS_GATE_CUT_OVERHEAD + bond[cut].powi(3);
}
for &q in targets.iter() {
qubit_block[q] = if targets.len() == 2 { block } else { 0 };
}
}
cost
}
pub(super) const MAX_AUTO_T_COUNT_SHOTS: usize = 40;
pub(super) const MAX_STABILIZER_RANK_QUBITS: usize = 25;
pub(super) const MIN_QUBITS_FOR_SPD_AUTO: usize = 12;
pub(super) const AUTO_SPD_AMPLITUDES_PER_TERM: usize = 64;
pub(super) const MIN_FACTORED_STABILIZER_QUBITS: usize = 24;
pub(super) const MIN_BLOCK_FOR_FACTORED_STAB: usize = 2;
const AUTO_SPARSE_MAX_SUPPORT: usize = 128;
#[inline]
pub(super) fn stabilizer_rank_budget(num_qubits: usize) -> usize {
let log2n = if num_qubits >= 2 {
(num_qubits as f64).log2().ceil() as usize * 2
} else {
0
};
num_qubits.saturating_sub(log2n)
}
#[inline]
pub(super) fn auto_spd_work_budget(circuit: &Circuit) -> usize {
circuit.instructions.len().saturating_mul(
1usize
.checked_shl(circuit.num_qubits as u32)
.unwrap_or(usize::MAX),
) / AUTO_SPD_AMPLITUDES_PER_TERM
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum BackendKind {
Auto,
Statevector,
Stabilizer,
Sparse,
Mps {
max_bond_dim: usize,
},
ProductState,
TensorNetwork,
TensorNetworkBounded {
tolerance: f64,
},
Factored,
StabilizerRank,
FactoredStabilizer,
DensityMatrix,
StochasticPauli {
num_samples: usize,
},
DeterministicPauli {
truncation: SpdTruncation,
},
PauliPath {
epsilon: f64,
max_terms: usize,
},
#[cfg(feature = "gpu")]
AutoGpu {
context: Arc<GpuContext>,
},
#[cfg(feature = "gpu")]
StatevectorGpu {
context: Arc<GpuContext>,
},
#[cfg(feature = "gpu")]
DensityMatrixGpu {
context: Arc<GpuContext>,
},
#[cfg(feature = "gpu")]
StabilizerGpu {
context: Arc<GpuContext>,
},
#[cfg(feature = "distributed")]
StatevectorDistributed {
context: Arc<DistributedContext>,
},
}
impl BackendKind {
#[inline]
pub(crate) fn is_auto(&self) -> bool {
match self {
BackendKind::Auto => true,
#[cfg(feature = "gpu")]
BackendKind::AutoGpu { .. } => true,
_ => false,
}
}
pub fn supports_noisy_per_shot(&self) -> bool {
!matches!(
self,
BackendKind::StabilizerRank
| BackendKind::StochasticPauli { .. }
| BackendKind::DeterministicPauli { .. }
| BackendKind::PauliPath { .. }
) && !self.is_density_matrix()
}
pub(crate) fn is_density_matrix(&self) -> bool {
match self {
BackendKind::DensityMatrix => true,
#[cfg(feature = "gpu")]
BackendKind::DensityMatrixGpu { .. } => true,
_ => false,
}
}
pub fn supports_general_noise(&self) -> bool {
match self {
BackendKind::Auto
| BackendKind::Statevector
| BackendKind::Sparse
| BackendKind::Mps { .. }
| BackendKind::ProductState
| BackendKind::Factored
| BackendKind::TensorNetwork
| BackendKind::TensorNetworkBounded { .. }
| BackendKind::DensityMatrix => true,
#[cfg(feature = "gpu")]
BackendKind::AutoGpu { .. }
| BackendKind::StatevectorGpu { .. }
| BackendKind::DensityMatrixGpu { .. } => true,
_ => false,
}
}
pub(crate) fn is_stabilizer_family(&self) -> bool {
matches!(
self,
BackendKind::Stabilizer | BackendKind::FactoredStabilizer
) || {
#[cfg(feature = "gpu")]
{
matches!(self, BackendKind::StabilizerGpu { .. })
}
#[cfg(not(feature = "gpu"))]
{
false
}
}
}
pub(crate) fn general_noise_backend_names() -> &'static str {
#[cfg(feature = "gpu")]
{
"Auto, Statevector, StatevectorGpu, Sparse, Mps, ProductState, Factored, TensorNetwork, DensityMatrix, or DensityMatrixGpu"
}
#[cfg(not(feature = "gpu"))]
{
"Auto, Statevector, Sparse, Mps, ProductState, Factored, TensorNetwork, or DensityMatrix"
}
}
}
pub(super) fn validate_explicit_backend(kind: &BackendKind, circuit: &Circuit) -> Result<()> {
if kind.is_stabilizer_family() && !circuit.is_clifford_only() {
return Err(PrismError::IncompatibleBackend {
backend: "stabilizer".into(),
reason: "circuit contains non-Clifford gates".into(),
});
}
match kind {
BackendKind::ProductState if circuit.has_entangling_gates() => {
return Err(PrismError::IncompatibleBackend {
backend: "productstate".into(),
reason: "circuit contains entangling gates".into(),
});
}
BackendKind::TensorNetworkBounded { tolerance }
if !(tolerance.is_finite() && *tolerance > 0.0) =>
{
return Err(PrismError::InvalidParameter {
message: format!(
"tensor-network tolerance must be a finite fraction above zero, got {tolerance}; \
TensorNetwork is the exact contraction"
),
});
}
BackendKind::StabilizerRank if !circuit.has_t_gates() => {
return Err(PrismError::IncompatibleBackend {
backend: "stabilizer_rank".into(),
reason: "circuit has no T gates; use Stabilizer instead".into(),
});
}
BackendKind::DensityMatrix => {
check_state_allocation(
"density_matrix",
circuit.num_qubits,
max_density_matrix_qubits(),
DM_QUBIT_CAP_ENV,
)?;
}
_ => {}
}
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Family {
ProductState,
Stabilizer,
Sparse,
Mps,
Factored,
FactoredStabilizer,
TensorNetwork,
Statevector,
DensityMatrix,
}
fn select_auto_backend_choice(circuit: &Circuit, has_partial_independence: bool) -> Family {
if !circuit.has_entangling_gates() {
Family::ProductState
} else if circuit.is_clifford_only() {
Family::Stabilizer
} else if circuit.num_qubits > max_statevector_qubits() {
if circuit.num_qubits <= MAX_SPARSE_INDEX_QUBITS
&& (circuit.is_sparse_friendly()
|| sparse_support_fits(circuit, AUTO_SPARSE_MAX_SUPPORT))
{
Family::Sparse
} else {
Family::Mps
}
} else if has_partial_independence {
Family::Factored
} else {
Family::Statevector
}
}
fn sparse_support_fits(circuit: &Circuit, cap: usize) -> bool {
if !circuit.has_terminal_measurements_only() {
return false;
}
let mut probe = SparseBackend::new(0);
if probe.init(circuit.num_qubits, 0).is_err() {
return false;
}
circuit.instructions.iter().all(|inst| match inst {
Instruction::Gate { .. } => probe.apply(inst).is_ok() && probe.entry_count() <= cap,
Instruction::Measure { .. } | Instruction::Barrier { .. } => true,
_ => false,
})
}
pub(super) fn auto_selects_cpu_statevector(
circuit: &Circuit,
has_partial_independence: bool,
) -> bool {
matches!(
select_auto_backend_choice(circuit, has_partial_independence),
Family::Statevector
)
}
#[derive(Clone)]
pub(super) enum Accel {
Cpu,
#[cfg(feature = "gpu")]
Gpu {
context: Arc<GpuContext>,
soft: bool,
},
}
#[cfg(feature = "gpu")]
struct GpuCapability {
min_qubits: fn() -> usize,
fits: fn(&Arc<GpuContext>, usize) -> bool,
}
#[cfg(feature = "gpu")]
const GPU_CAPABLE_FAMILIES: [Family; 3] = [
Family::Statevector,
Family::Stabilizer,
Family::DensityMatrix,
];
#[cfg(feature = "gpu")]
fn gpu_capability(family: Family) -> Option<GpuCapability> {
match family {
Family::Statevector => Some(GpuCapability {
min_qubits: crate::gpu::min_qubits,
fits: |ctx, n| ctx.fits_statevector_with_scratch(n).unwrap_or(false),
}),
Family::Stabilizer => Some(GpuCapability {
min_qubits: crate::gpu::stabilizer_min_qubits,
fits: |ctx, n| ctx.fits_tableau(n).unwrap_or(false),
}),
Family::DensityMatrix => Some(GpuCapability {
min_qubits: || 0,
fits: |ctx, n| ctx.fits_statevector_with_scratch(2 * n).unwrap_or(false),
}),
Family::ProductState
| Family::Sparse
| Family::Mps
| Family::Factored
| Family::FactoredStabilizer
| Family::TensorNetwork => None,
}
}
#[cfg(feature = "gpu")]
pub(super) fn accel_for(kind: &BackendKind, family: Family, num_qubits: usize) -> Accel {
let Some((context, soft)) = gpu_request(kind, family) else {
return Accel::Cpu;
};
let Some(cap) = gpu_capability(family) else {
return Accel::Cpu;
};
if num_qubits < (cap.min_qubits)() {
return Accel::Cpu;
}
if soft && !(cap.fits)(context, num_qubits) {
return Accel::Cpu;
}
Accel::Gpu {
context: context.clone(),
soft,
}
}
#[cfg(feature = "gpu")]
fn gpu_request(kind: &BackendKind, family: Family) -> Option<(&Arc<GpuContext>, bool)> {
match (kind, family) {
(BackendKind::AutoGpu { context }, _) => Some((context, true)),
(BackendKind::StatevectorGpu { context }, Family::Statevector) => Some((context, false)),
(BackendKind::StabilizerGpu { context }, Family::Stabilizer) => Some((context, false)),
(BackendKind::DensityMatrixGpu { context }, Family::DensityMatrix) => {
Some((context, false))
}
_ => None,
}
}
#[cfg(feature = "gpu")]
pub(super) fn may_resolve_to_gpu(kind: &BackendKind, num_qubits: usize) -> bool {
GPU_CAPABLE_FAMILIES.into_iter().any(|family| {
gpu_request(kind, family).is_some()
&& gpu_capability(family).is_some_and(|cap| num_qubits >= (cap.min_qubits)())
})
}
#[cfg(not(feature = "gpu"))]
pub(super) fn accel_for(_kind: &BackendKind, _family: Family, _num_qubits: usize) -> Accel {
Accel::Cpu
}
pub(super) fn build_statevector(accel: &Accel, seed: u64) -> StatevectorBackend {
match accel {
Accel::Cpu => StatevectorBackend::new(seed),
#[cfg(feature = "gpu")]
Accel::Gpu {
context,
soft: true,
} => StatevectorBackend::new(seed).with_gpu_auto(context.clone()),
#[cfg(feature = "gpu")]
Accel::Gpu {
context,
soft: false,
} => StatevectorBackend::new(seed).with_gpu(context.clone()),
}
}
pub(super) fn build_density_matrix(accel: &Accel, seed: u64) -> DensityMatrixBackend {
match accel {
Accel::Cpu => DensityMatrixBackend::new(seed),
#[cfg(feature = "gpu")]
Accel::Gpu { context, .. } => DensityMatrixBackend::new(seed).with_gpu(context.clone()),
}
}
fn stabilizer_can_run_lazy(circuit: &Circuit) -> bool {
circuit.instructions.iter().all(|inst| {
matches!(
inst,
Instruction::Gate { .. }
| Instruction::Conditional { .. }
| Instruction::Barrier { .. }
)
})
}
fn build_stabilizer(accel: &Accel, lazy: bool, seed: u64) -> StabilizerBackend {
let stab = if lazy {
StabilizerBackend::new_lazy(seed)
} else {
StabilizerBackend::new(seed)
};
match accel {
Accel::Cpu => stab,
#[cfg(feature = "gpu")]
Accel::Gpu {
context,
soft: true,
} => stab.with_gpu_auto(context.clone()),
#[cfg(feature = "gpu")]
Accel::Gpu {
context,
soft: false,
} => stab.with_gpu(context.clone()),
}
}
#[derive(Clone)]
pub(super) enum BackendPlan {
ProductState,
Sparse,
TensorNetwork {
tolerance: Option<f64>,
},
Factored,
FactoredStabilizer,
Mps {
max_bond_dim: usize,
},
Stabilizer {
accel: Accel,
lazy: bool,
},
Statevector {
accel: Accel,
},
DensityMatrix {
accel: Accel,
},
#[cfg(feature = "distributed")]
Distributed(Arc<DistributedContext>),
}
fn same_accel(a: &Accel, b: &Accel) -> bool {
match (a, b) {
(Accel::Cpu, Accel::Cpu) => true,
#[cfg(feature = "gpu")]
(
Accel::Gpu {
context: cx,
soft: sx,
},
Accel::Gpu {
context: cy,
soft: sy,
},
) => Arc::ptr_eq(cx, cy) && sx == sy,
#[cfg(feature = "gpu")]
_ => false,
}
}
pub(super) fn same_plan(a: &BackendPlan, b: &BackendPlan) -> bool {
match (a, b) {
(BackendPlan::ProductState, BackendPlan::ProductState)
| (BackendPlan::Sparse, BackendPlan::Sparse)
| (BackendPlan::Factored, BackendPlan::Factored)
| (BackendPlan::FactoredStabilizer, BackendPlan::FactoredStabilizer) => true,
(
BackendPlan::TensorNetwork { tolerance: x },
BackendPlan::TensorNetwork { tolerance: y },
) => x == y,
(BackendPlan::Mps { max_bond_dim: x }, BackendPlan::Mps { max_bond_dim: y }) => x == y,
(
BackendPlan::Stabilizer {
accel: ax,
lazy: lx,
},
BackendPlan::Stabilizer {
accel: ay,
lazy: ly,
},
) => same_accel(ax, ay) && lx == ly,
(BackendPlan::Statevector { accel: x }, BackendPlan::Statevector { accel: y })
| (BackendPlan::DensityMatrix { accel: x }, BackendPlan::DensityMatrix { accel: y }) => {
same_accel(x, y)
}
_ => false,
}
}
impl BackendPlan {
pub(super) fn resolved(&self) -> ResolvedBackend {
match self {
BackendPlan::ProductState => ResolvedBackend::ProductState,
BackendPlan::Sparse => ResolvedBackend::Sparse,
BackendPlan::TensorNetwork { .. } => ResolvedBackend::TensorNetwork,
BackendPlan::Factored => ResolvedBackend::Factored,
BackendPlan::FactoredStabilizer => ResolvedBackend::FactoredStabilizer,
BackendPlan::Mps { .. } => ResolvedBackend::Mps,
BackendPlan::Stabilizer { .. } => ResolvedBackend::Stabilizer,
BackendPlan::Statevector { .. } => ResolvedBackend::Statevector,
BackendPlan::DensityMatrix { .. } => ResolvedBackend::DensityMatrix,
#[cfg(feature = "distributed")]
BackendPlan::Distributed(_) => ResolvedBackend::Distributed,
}
}
pub(super) fn build(&self, seed: u64) -> Box<dyn Backend + Send> {
match self {
BackendPlan::ProductState => Box::new(ProductStateBackend::new(seed)),
BackendPlan::Sparse => Box::new(SparseBackend::new(seed)),
BackendPlan::TensorNetwork { tolerance } => Box::new(match *tolerance {
Some(tolerance) => TensorNetworkBackend::with_tolerance(seed, tolerance),
None => TensorNetworkBackend::new(seed),
}),
BackendPlan::Factored => Box::new(crate::backend::factored::FactoredBackend::new(seed)),
BackendPlan::FactoredStabilizer => {
Box::new(crate::backend::factored_stabilizer::FactoredStabilizerBackend::new(seed))
}
BackendPlan::Mps { max_bond_dim } => Box::new(MpsBackend::new(seed, *max_bond_dim)),
BackendPlan::Stabilizer { accel, lazy } => {
Box::new(build_stabilizer(accel, *lazy, seed))
}
BackendPlan::Statevector { accel } => Box::new(build_statevector(accel, seed)),
BackendPlan::DensityMatrix { accel } => Box::new(build_density_matrix(accel, seed)),
#[cfg(feature = "distributed")]
BackendPlan::Distributed(context) => {
Box::new(DistributedStatevectorBackend::new(context.clone(), seed))
}
}
}
pub(super) fn is_host_statevector(&self) -> bool {
matches!(self, BackendPlan::Statevector { accel: Accel::Cpu })
}
pub(super) fn is_gpu(&self) -> bool {
match self {
BackendPlan::Stabilizer { accel, .. }
| BackendPlan::Statevector { accel }
| BackendPlan::DensityMatrix { accel } => !matches!(accel, Accel::Cpu),
_ => false,
}
}
#[cfg(test)]
pub(super) fn family(&self) -> Family {
match self {
BackendPlan::ProductState => Family::ProductState,
BackendPlan::Sparse => Family::Sparse,
BackendPlan::TensorNetwork { .. } => Family::TensorNetwork,
BackendPlan::Factored => Family::Factored,
BackendPlan::FactoredStabilizer => Family::FactoredStabilizer,
BackendPlan::Mps { .. } => Family::Mps,
BackendPlan::Stabilizer { .. } => Family::Stabilizer,
BackendPlan::Statevector { .. } => Family::Statevector,
BackendPlan::DensityMatrix { .. } => Family::DensityMatrix,
#[cfg(feature = "distributed")]
BackendPlan::Distributed(_) => Family::Statevector,
}
}
#[cfg(test)]
pub(super) fn accel(&self) -> &Accel {
match self {
BackendPlan::Stabilizer { accel, .. }
| BackendPlan::Statevector { accel }
| BackendPlan::DensityMatrix { accel } => accel,
_ => &Accel::Cpu,
}
}
}
pub(super) enum ExecutionPlan {
Backend(BackendPlan),
StabilizerRank,
StochasticPauli { num_samples: usize },
DeterministicPauli { truncation: SpdTruncation },
PauliPath,
}
pub(super) fn approximate_route_name(
kind: &BackendKind,
circuit: &Circuit,
sampling: bool,
) -> Option<&'static str> {
let mps_is_exact = circuit.is_sparse_friendly();
match kind {
BackendKind::Mps { .. } => {
if mps_is_exact {
return None;
}
return Some("Mps");
}
BackendKind::TensorNetworkBounded { .. } => return Some("TensorNetworkBounded"),
BackendKind::StochasticPauli { .. } => return Some("StochasticPauli"),
BackendKind::DeterministicPauli { truncation } => {
return truncation.can_discard().then_some("DeterministicPauli");
}
BackendKind::PauliPath { epsilon, max_terms } => {
let truncation = SpdTruncation::Threshold {
epsilon: *epsilon,
max_terms: *max_terms,
};
return truncation.can_discard().then_some("PauliPath");
}
_ => {}
}
if !kind.is_auto() {
return None;
}
if sampling && crate::sim::auto_samples_by_stabilizer_rank(circuit) {
return None;
}
let (_, has_partial_independence) = crate::sim::analyze_independence(circuit);
match select_auto_backend_choice(circuit, has_partial_independence) {
Family::Mps if !mps_is_exact => Some("Mps"),
_ => None,
}
}
pub(super) fn plan_for_family(
kind: &BackendKind,
family: Family,
num_qubits: usize,
) -> BackendPlan {
match family {
Family::ProductState => BackendPlan::ProductState,
Family::Sparse => BackendPlan::Sparse,
Family::TensorNetwork => BackendPlan::TensorNetwork {
tolerance: match kind {
BackendKind::TensorNetworkBounded { tolerance } => Some(*tolerance),
_ => None,
},
},
Family::Factored => BackendPlan::Factored,
Family::FactoredStabilizer => BackendPlan::FactoredStabilizer,
Family::Mps => BackendPlan::Mps {
max_bond_dim: AUTO_MPS_BOND_DIM,
},
Family::Stabilizer => BackendPlan::Stabilizer {
accel: accel_for(kind, Family::Stabilizer, num_qubits),
lazy: false,
},
Family::Statevector => BackendPlan::Statevector {
accel: accel_for(kind, Family::Statevector, num_qubits),
},
Family::DensityMatrix => BackendPlan::DensityMatrix {
accel: accel_for(kind, Family::DensityMatrix, num_qubits),
},
}
}
pub(super) fn resolve(
kind: &BackendKind,
circuit: &Circuit,
has_partial_independence: bool,
) -> ExecutionPlan {
let family = match kind {
BackendKind::Auto => select_auto_backend_choice(circuit, has_partial_independence),
#[cfg(feature = "gpu")]
BackendKind::AutoGpu { .. } => {
select_auto_backend_choice(circuit, has_partial_independence)
}
BackendKind::Statevector => Family::Statevector,
BackendKind::Stabilizer => Family::Stabilizer,
BackendKind::Sparse => Family::Sparse,
BackendKind::Mps { max_bond_dim } => {
return ExecutionPlan::Backend(BackendPlan::Mps {
max_bond_dim: *max_bond_dim,
});
}
BackendKind::ProductState => Family::ProductState,
BackendKind::TensorNetwork | BackendKind::TensorNetworkBounded { .. } => {
Family::TensorNetwork
}
BackendKind::Factored => Family::Factored,
BackendKind::FactoredStabilizer => Family::FactoredStabilizer,
BackendKind::DensityMatrix => Family::DensityMatrix,
BackendKind::StabilizerRank => return ExecutionPlan::StabilizerRank,
BackendKind::StochasticPauli { num_samples } => {
return ExecutionPlan::StochasticPauli {
num_samples: *num_samples,
};
}
BackendKind::PauliPath { .. } => return ExecutionPlan::PauliPath,
BackendKind::DeterministicPauli { truncation } => {
return ExecutionPlan::DeterministicPauli {
truncation: *truncation,
};
}
#[cfg(feature = "gpu")]
BackendKind::StatevectorGpu { .. } => Family::Statevector,
#[cfg(feature = "gpu")]
BackendKind::StabilizerGpu { .. } => Family::Stabilizer,
#[cfg(feature = "gpu")]
BackendKind::DensityMatrixGpu { .. } => Family::DensityMatrix,
#[cfg(feature = "distributed")]
BackendKind::StatevectorDistributed { context } => {
return ExecutionPlan::Backend(BackendPlan::Distributed(context.clone()));
}
};
let mut plan = plan_for_family(kind, family, circuit.num_qubits);
if let BackendPlan::Stabilizer { lazy, .. } = &mut plan {
*lazy = stabilizer_can_run_lazy(circuit);
}
ExecutionPlan::Backend(plan)
}
pub(super) fn initial_state_plan(
kind: &BackendKind,
num_qubits: usize,
start: StartState<'_>,
) -> Result<BackendPlan> {
if let StartState::DensityMatrix(_) = start {
return match kind {
BackendKind::DensityMatrix => {
Ok(plan_for_family(kind, Family::DensityMatrix, num_qubits))
}
#[cfg(feature = "gpu")]
BackendKind::DensityMatrixGpu { .. } => {
Ok(plan_for_family(kind, Family::DensityMatrix, num_qubits))
}
other => Err(PrismError::IncompatibleBackend {
backend: format!("{other:?}"),
reason: "a start density matrix runs on the density matrix, which holds a \
mixture; every other representation holds a pure state, so select \
`BackendKind::DensityMatrix` or its device sibling"
.into(),
}),
};
}
match kind {
BackendKind::Auto | BackendKind::Statevector => {
Ok(plan_for_family(kind, Family::Statevector, num_qubits))
}
#[cfg(feature = "gpu")]
BackendKind::AutoGpu { .. } | BackendKind::StatevectorGpu { .. } => {
Ok(plan_for_family(kind, Family::Statevector, num_qubits))
}
BackendKind::DensityMatrix => Ok(plan_for_family(kind, Family::DensityMatrix, num_qubits)),
#[cfg(feature = "gpu")]
BackendKind::DensityMatrixGpu { .. } => {
Ok(plan_for_family(kind, Family::DensityMatrix, num_qubits))
}
#[cfg(feature = "distributed")]
BackendKind::StatevectorDistributed { context } => {
Ok(BackendPlan::Distributed(context.clone()))
}
other => Err(PrismError::IncompatibleBackend {
backend: format!("{other:?}"),
reason: "a start state other than |0...0> runs on the statevector or the density \
matrix; every other representation is derived from that start"
.into(),
}),
}
}
pub(super) fn resolve_backend(
kind: &BackendKind,
circuit: &Circuit,
has_partial_independence: bool,
) -> BackendPlan {
match resolve(kind, circuit, has_partial_independence) {
ExecutionPlan::Backend(plan) => plan,
_ => unreachable!("non-backend dispatch should be handled by caller"),
}
}
#[inline]
pub(super) fn min_clifford_prefix_gates(num_qubits: usize) -> usize {
(num_qubits * 2).max(16)
}
pub(super) fn has_temporal_clifford_opportunity(kind: &BackendKind, circuit: &Circuit) -> bool {
if !kind.is_auto() {
return false;
}
if circuit.num_qubits > max_statevector_qubits() {
return false;
}
if circuit.is_clifford_only() {
return false;
}
let min_gates = min_clifford_prefix_gates(circuit.num_qubits);
let mut prefix_gates = 0;
for inst in &circuit.instructions {
match inst {
Instruction::Gate { gate, .. } => {
if !gate.is_clifford() {
break;
}
prefix_gates += 1;
}
Instruction::Measure { .. }
| Instruction::Reset { .. }
| Instruction::Conditional { .. }
| Instruction::Region(_) => break,
Instruction::Barrier { .. } | Instruction::Save { .. } => {}
}
}
prefix_gates >= min_gates && prefix_gates < circuit.instructions.len()
}
pub(super) struct TemporalCliffordPlan {
prefix: Circuit,
fused_tail: Circuit,
tail_num_classical_bits: usize,
tail_accel: Accel,
}
pub(super) fn plan_temporal_clifford(
kind: &BackendKind,
circuit: &Circuit,
) -> Option<TemporalCliffordPlan> {
if !kind.is_auto() {
return None;
}
if circuit.num_qubits > max_statevector_qubits() {
return None;
}
if circuit.is_clifford_only() {
return None;
}
let (prefix, tail) = circuit.clifford_prefix_split()?;
if prefix.gate_count() < min_clifford_prefix_gates(circuit.num_qubits) {
return None;
}
let tail_accel = accel_for(kind, Family::Statevector, circuit.num_qubits);
let tail_num_classical_bits = tail.num_classical_bits;
let fused_tail = match &tail_accel {
Accel::Cpu => crate::circuit::fusion::fuse_circuit(&tail, true).into_owned(),
#[cfg(feature = "gpu")]
Accel::Gpu { .. } => {
let expanded = crate::circuit::expand_qft_blocks(&tail);
let expanded = crate::circuit::expand_pauli_rotations(&expanded).into_owned();
crate::circuit::fusion::fuse_circuit(&expanded, true).into_owned()
}
};
Some(TemporalCliffordPlan {
prefix,
fused_tail,
tail_num_classical_bits,
tail_accel,
})
}
pub(super) fn run_temporal_clifford(
plan: &TemporalCliffordPlan,
seed: u64,
want_probabilities: bool,
) -> Result<RunOutcome> {
let mut stab = StabilizerBackend::new(seed);
stab.init(plan.prefix.num_qubits, plan.prefix.num_classical_bits)?;
stab.enable_lazy_destab();
for inst in &plan.prefix.instructions {
stab.apply(inst)?;
}
let state = stab.export_statevector()?;
let mut sv = build_statevector(&plan.tail_accel, seed);
sv.init_from_state(state, plan.tail_num_classical_bits)?;
for inst in &plan.fused_tail.instructions {
sv.apply(inst)?;
}
let probabilities = if want_probabilities {
try_backend_probabilities(&sv)?
} else {
None
};
Ok(RunOutcome {
classical_bits: sv.classical_results().to_vec(),
probabilities,
metadata: crate::sim::backend_metadata(&sv),
saves: Vec::new(),
})
}
#[cfg(all(test, feature = "gpu"))]
mod gpu_crossover_tests {
use super::*;
use crate::gates::Gate;
fn stub_kind() -> BackendKind {
BackendKind::StatevectorGpu {
context: GpuContext::stub_for_tests(),
}
}
fn run_query(kind: BackendKind, circuit: &Circuit, seed: u64) -> Result<RunOutcome> {
crate::sim::simulate(circuit).backend(kind).seed(seed).run()
}
#[test]
fn builder_gpu_wraps_statevector_gpu_variant() {
let ctx = GpuContext::stub_for_tests();
let mut circuit = Circuit::new(4, 0);
circuit.add_gate(Gate::H, &[0]);
circuit.add_gate(Gate::Cx, &[0, 1]);
let direct = crate::sim::simulate(&circuit)
.gpu(ctx.clone())
.seed(42)
.run()
.expect("builder GPU shortcut must honor crossover and route to CPU");
let manual = crate::sim::simulate(&circuit)
.backend(stub_kind())
.seed(42)
.run()
.expect("manual variant reference");
let dp = direct.probabilities.expect("direct probs").to_vec();
let mp = manual.probabilities.expect("manual probs").to_vec();
assert_eq!(dp, mp);
}
#[test]
fn small_circuit_routes_to_cpu() {
let mut circuit = Circuit::new(4, 0);
circuit.add_gate(Gate::H, &[0]);
circuit.add_gate(Gate::Cx, &[0, 1]);
circuit.add_gate(Gate::H, &[2]);
circuit.add_gate(Gate::Cx, &[2, 3]);
let result = run_query(stub_kind(), &circuit, 42)
.expect("stub context must not be touched for a 4q circuit");
let probs = result
.probabilities
.expect("probabilities missing")
.to_vec();
let mut expected = [0.0_f64; 16];
expected[0b0000] = 0.25;
expected[0b0011] = 0.25;
expected[0b1100] = 0.25;
expected[0b1111] = 0.25;
for (i, (p, e)) in probs.iter().zip(&expected).enumerate() {
assert!((p - e).abs() < 1e-10, "p[{i}] = {p}, expected {e}");
}
}
#[test]
fn decomposable_16q_circuit_runs_per_block_on_cpu() {
let circuit = crate::circuits::independent_bell_pairs(8);
assert_eq!(circuit.num_qubits, 16);
let cpu = run_query(BackendKind::Statevector, &circuit, 42).expect("cpu baseline");
let gpu = run_query(stub_kind(), &circuit, 42).expect("stub must stay out of the way");
let cpu_p = cpu.probabilities.expect("cpu probs").to_vec();
let gpu_p = gpu.probabilities.expect("gpu probs").to_vec();
assert_eq!(cpu_p.len(), gpu_p.len());
for (i, (c, g)) in cpu_p.iter().zip(gpu_p.iter()).enumerate() {
assert!(
(c - g).abs() < 1e-10,
"prob[{i}] cpu={c}, gpu={g}, diff={}",
(c - g).abs()
);
}
}
fn stabilizer_stub_kind() -> BackendKind {
BackendKind::StabilizerGpu {
context: GpuContext::stub_for_tests(),
}
}
#[test]
fn stabilizer_gpu_small_circuit_routes_to_cpu() {
let mut circuit = Circuit::new(4, 4);
circuit.add_gate(Gate::H, &[0]);
circuit.add_gate(Gate::Cx, &[0, 1]);
circuit.add_gate(Gate::Cx, &[1, 2]);
circuit.add_gate(Gate::Cx, &[2, 3]);
circuit.add_measure(0, 0);
circuit.add_measure(1, 1);
circuit.add_measure(2, 2);
circuit.add_measure(3, 3);
let cpu_run = run_query(BackendKind::Stabilizer, &circuit, 42).expect("cpu baseline");
let gpu_run = run_query(stabilizer_stub_kind(), &circuit, 42)
.expect("stub must stay out of the way for small circuits");
assert_eq!(cpu_run.classical_bits, gpu_run.classical_bits);
}
#[test]
fn stabilizer_gpu_rejects_non_clifford_at_dispatch() {
let mut circuit = Circuit::new(2, 0);
circuit.add_gate(Gate::T, &[0]);
let err = run_query(stabilizer_stub_kind(), &circuit, 42).unwrap_err();
assert!(matches!(err, PrismError::IncompatibleBackend { .. }));
}
fn auto_gpu_stub_kind() -> BackendKind {
BackendKind::AutoGpu {
context: GpuContext::stub_for_tests(),
}
}
fn assert_probs_match(a: &RunOutcome, b: &RunOutcome) {
let ap = a.probabilities.as_ref().expect("probs a").to_vec();
let bp = b.probabilities.as_ref().expect("probs b").to_vec();
assert_eq!(ap.len(), bp.len());
for (i, (x, y)) in ap.iter().zip(bp.iter()).enumerate() {
assert!((x - y).abs() < 1e-10, "prob[{i}]: {x} vs {y}");
}
}
#[test]
fn auto_gpu_small_clifford_routes_to_cpu() {
let mut circuit = Circuit::new(4, 0);
circuit.add_gate(Gate::H, &[0]);
circuit.add_gate(Gate::Cx, &[0, 1]);
circuit.add_gate(Gate::Cx, &[1, 2]);
circuit.add_gate(Gate::Cx, &[2, 3]);
let cpu = run_query(BackendKind::Auto, &circuit, 42).expect("cpu auto baseline");
let gpu = run_query(auto_gpu_stub_kind(), &circuit, 42)
.expect("stub must stay out of the way below the stabilizer crossover");
assert_probs_match(&cpu, &gpu);
}
#[test]
fn auto_gpu_decomposable_16q_runs_per_block_on_cpu() {
let circuit = crate::circuits::independent_bell_pairs(8);
assert_eq!(circuit.num_qubits, 16);
let cpu = run_query(BackendKind::Auto, &circuit, 42).expect("cpu auto baseline");
let gpu = run_query(auto_gpu_stub_kind(), &circuit, 42).expect("stub stays out of the way");
assert_probs_match(&cpu, &gpu);
}
#[test]
fn auto_gpu_large_block_falls_back_to_cpu_on_stub() {
let mut circuit = Circuit::new(16, 0);
for q in 0..16 {
circuit.add_gate(Gate::Rx(0.3), &[q]);
}
for q in 0..15 {
circuit.add_gate(Gate::Cx, &[q, q + 1]);
}
let cpu = run_query(BackendKind::Auto, &circuit, 42).expect("cpu auto baseline");
let gpu = run_query(auto_gpu_stub_kind(), &circuit, 42)
.expect("stub VRAM query fails closed, so AutoGpu must fall back to CPU without error");
assert_probs_match(&cpu, &gpu);
let explicit = BackendKind::StatevectorGpu {
context: GpuContext::stub_for_tests(),
};
assert!(matches!(
run_query(explicit, &circuit, 42).unwrap_err(),
PrismError::BackendUnsupported { .. }
));
}
}
#[cfg(all(test, feature = "gpu"))]
mod accel_tests {
use super::*;
fn stub() -> Arc<GpuContext> {
GpuContext::stub_for_tests()
}
fn is_cpu(accel: &Accel) -> bool {
matches!(accel, Accel::Cpu)
}
#[test]
fn auto_gpu_statevector_below_crossover_is_cpu() {
let kind = BackendKind::AutoGpu { context: stub() };
let n = crate::gpu::min_qubits() - 1;
assert!(is_cpu(&accel_for(&kind, Family::Statevector, n)));
}
#[test]
fn auto_gpu_statevector_fits_fails_closed_on_stub() {
let kind = BackendKind::AutoGpu { context: stub() };
let n = crate::gpu::min_qubits() + 2;
assert!(is_cpu(&accel_for(&kind, Family::Statevector, n)));
}
#[test]
fn auto_gpu_cpu_only_families_stay_cpu() {
let kind = BackendKind::AutoGpu { context: stub() };
for family in [
Family::ProductState,
Family::Sparse,
Family::Mps,
Family::Factored,
Family::FactoredStabilizer,
Family::TensorNetwork,
] {
assert!(is_cpu(&accel_for(&kind, family, 1 << 10)), "{family:?}");
}
}
#[test]
fn explicit_statevector_gpu_is_hard_at_crossover_and_cpu_below() {
let kind = BackendKind::StatevectorGpu { context: stub() };
let n = crate::gpu::min_qubits();
assert!(matches!(
accel_for(&kind, Family::Statevector, n),
Accel::Gpu { soft: false, .. }
));
assert!(is_cpu(&accel_for(&kind, Family::Statevector, n - 1)));
}
#[test]
fn explicit_stabilizer_gpu_is_hard_at_crossover_and_cpu_below() {
let kind = BackendKind::StabilizerGpu { context: stub() };
let n = crate::gpu::stabilizer_min_qubits();
assert!(matches!(
accel_for(&kind, Family::Stabilizer, n),
Accel::Gpu { soft: false, .. }
));
assert!(is_cpu(&accel_for(&kind, Family::Stabilizer, n - 1)));
}
#[test]
fn explicit_kind_other_family_is_cpu() {
let sv = BackendKind::StatevectorGpu { context: stub() };
assert!(is_cpu(&accel_for(&sv, Family::Stabilizer, 1 << 20)));
let stab = BackendKind::StabilizerGpu { context: stub() };
assert!(is_cpu(&accel_for(&stab, Family::Statevector, 1 << 20)));
}
#[test]
fn cpu_kinds_are_cpu_everywhere() {
for family in [Family::Statevector, Family::Stabilizer] {
assert!(is_cpu(&accel_for(&BackendKind::Auto, family, 1 << 20)));
assert!(is_cpu(&accel_for(
&BackendKind::Statevector,
family,
1 << 20
)));
}
}
#[test]
fn init_from_state_soft_falls_back_to_host_on_stub() {
use num_complex::Complex64;
let mut sv = StatevectorBackend::new(42).with_gpu_auto(stub());
let amp = Complex64::new(std::f64::consts::FRAC_1_SQRT_2, 0.0);
let state = vec![amp, Complex64::new(0.0, 0.0), Complex64::new(0.0, 0.0), amp];
sv.init_from_state(state.clone(), 0).unwrap();
let exported = sv.export_statevector().unwrap();
for (e, s) in exported.iter().zip(&state) {
assert!((e - s).norm() < 1e-12);
}
}
#[test]
fn init_from_state_hard_errors_on_stub() {
use num_complex::Complex64;
let mut sv = StatevectorBackend::new(42).with_gpu(stub());
let err = sv
.init_from_state(vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)], 0)
.unwrap_err();
assert!(matches!(err, PrismError::BackendUnsupported { .. }));
}
}
#[cfg(test)]
mod dispatch_matrix_tests {
use super::*;
use crate::gates::Gate;
fn product(n: usize) -> Circuit {
let mut c = Circuit::new(n, 0);
for q in 0..n {
c.add_gate(Gate::Rx(0.3), &[q]);
}
c
}
fn clifford(n: usize) -> Circuit {
let mut c = Circuit::new(n, 0);
c.add_gate(Gate::H, &[0]);
for q in 0..n - 1 {
c.add_gate(Gate::Cx, &[q, q + 1]);
}
c
}
fn dense(n: usize) -> Circuit {
let mut c = Circuit::new(n, 0);
for q in 0..n {
c.add_gate(Gate::Rx(0.3), &[q]);
}
for q in 0..n - 1 {
c.add_gate(Gate::Cx, &[q, q + 1]);
}
c
}
fn oversize_qubits() -> Option<usize> {
let cap = max_statevector_qubits();
if cap >= usize::BITS as usize {
eprintln!(
"SKIP: statevector qubit cap disabled on this host; skipping oversize checks"
);
return None;
}
Some(cap + 1)
}
fn oversize_sparse(n: usize) -> Circuit {
let mut c = Circuit::new(n, 0);
for q in 0..n {
c.add_gate(Gate::T, &[q]);
}
for q in 0..n - 1 {
c.add_gate(Gate::Cx, &[q, q + 1]);
}
c
}
fn oversize_dense(n: usize) -> Circuit {
let mut c = Circuit::new(n, 0);
for q in 0..n {
c.add_gate(Gate::Rx(0.3), &[q]);
}
for q in 0..n - 1 {
c.add_gate(Gate::Cx, &[q, q + 1]);
}
c
}
fn resolved(kind: &BackendKind, circuit: &Circuit, hpi: bool) -> BackendPlan {
match resolve(kind, circuit, hpi) {
ExecutionPlan::Backend(plan) => plan,
_ => panic!("expected a backend plan"),
}
}
fn assert_cpu_family(kind: &BackendKind, circuit: &Circuit, hpi: bool, family: Family) {
let plan = resolved(kind, circuit, hpi);
assert_eq!(plan.family(), family, "kind {kind:?}");
assert!(
matches!(plan.accel(), Accel::Cpu),
"kind {kind:?} family {family:?} must resolve to the host"
);
}
fn auto_kinds() -> Vec<BackendKind> {
#[cfg(feature = "gpu")]
{
vec![
BackendKind::Auto,
BackendKind::AutoGpu {
context: GpuContext::stub_for_tests(),
},
]
}
#[cfg(not(feature = "gpu"))]
{
vec![BackendKind::Auto]
}
}
#[test]
fn auto_family_matrix() {
let oversize = oversize_qubits();
for kind in auto_kinds() {
assert_cpu_family(&kind, &product(6), false, Family::ProductState);
assert_cpu_family(&kind, &clifford(6), false, Family::Stabilizer);
assert_cpu_family(&kind, &clifford(16), false, Family::Stabilizer);
assert_cpu_family(&kind, &dense(8), false, Family::Statevector);
assert_cpu_family(&kind, &dense(16), false, Family::Statevector);
assert_cpu_family(&kind, &dense(8), true, Family::Factored);
if let Some(n) = oversize {
assert_cpu_family(&kind, &oversize_sparse(n), false, Family::Sparse);
assert_cpu_family(&kind, &oversize_dense(n), false, Family::Mps);
let w_state = crate::circuits::w_state_circuit(n);
assert_cpu_family(&kind, &w_state, false, Family::Sparse);
}
}
}
#[test]
fn sparse_support_probe_bounds_every_gate_not_the_final_state() {
let w_state = crate::circuits::w_state_circuit(64);
assert!(sparse_support_fits(&w_state, 126));
assert!(!sparse_support_fits(&w_state, 125));
assert!(!sparse_support_fits(
&oversize_dense(40),
AUTO_SPARSE_MAX_SUPPORT
));
let mut measured = Circuit::new(8, 1);
measured.instructions = crate::circuits::w_state_circuit(8).instructions;
measured.add_measure(0, 0);
assert!(sparse_support_fits(&measured, AUTO_SPARSE_MAX_SUPPORT));
measured.add_gate(Gate::H, &[1]);
assert!(!sparse_support_fits(&measured, AUTO_SPARSE_MAX_SUPPORT));
}
#[test]
fn auto_oversize_mps_uses_auto_bond_dim() {
let Some(n) = oversize_qubits() else { return };
for kind in auto_kinds() {
let plan = resolved(&kind, &oversize_dense(n), false);
assert!(matches!(
plan,
BackendPlan::Mps {
max_bond_dim: AUTO_MPS_BOND_DIM
}
));
}
}
#[test]
fn explicit_cpu_kind_matrix() {
let circuit = dense(6);
let cases = [
(BackendKind::Statevector, Family::Statevector),
(BackendKind::Stabilizer, Family::Stabilizer),
(BackendKind::Sparse, Family::Sparse),
(BackendKind::ProductState, Family::ProductState),
(BackendKind::TensorNetwork, Family::TensorNetwork),
(BackendKind::Factored, Family::Factored),
(BackendKind::FactoredStabilizer, Family::FactoredStabilizer),
];
for (kind, family) in cases {
assert_cpu_family(&kind, &circuit, false, family);
}
let plan = resolved(&BackendKind::Mps { max_bond_dim: 77 }, &circuit, false);
assert!(matches!(plan, BackendPlan::Mps { max_bond_dim: 77 }));
}
#[test]
fn non_backend_kinds_resolve_to_their_engines() {
let circuit = dense(6);
assert!(matches!(
resolve(&BackendKind::StabilizerRank, &circuit, false),
ExecutionPlan::StabilizerRank
));
assert!(matches!(
resolve(
&BackendKind::StochasticPauli { num_samples: 9 },
&circuit,
false
),
ExecutionPlan::StochasticPauli { num_samples: 9 }
));
assert!(matches!(
resolve(
&BackendKind::DeterministicPauli {
truncation: SpdTruncation::Threshold {
epsilon: 0.5,
max_terms: 3
}
},
&circuit,
false
),
ExecutionPlan::DeterministicPauli {
truncation: SpdTruncation::Threshold {
epsilon: e,
max_terms: 3
}
} if e == 0.5
));
}
#[test]
fn density_matrix_is_explicit_only() {
for circuit in [product(6), clifford(6), dense(6)] {
for hpi in [false, true] {
assert_ne!(
resolved(&BackendKind::Auto, &circuit, hpi).family(),
Family::DensityMatrix
);
}
}
let plan = resolved(&BackendKind::DensityMatrix, &dense(6), false);
assert_eq!(plan.family(), Family::DensityMatrix);
assert!(plan.build(42).supports_fused_gates());
assert!(matches!(plan.accel(), Accel::Cpu));
}
#[test]
fn density_matrix_rejects_over_cap() {
let cap = max_density_matrix_qubits();
if cap >= usize::BITS as usize {
eprintln!("SKIP: density-matrix cap disabled on this host");
return;
}
let circuit = dense(cap + 1);
let err = validate_explicit_backend(&BackendKind::DensityMatrix, &circuit).unwrap_err();
assert!(matches!(err, PrismError::ResourceLimit(_)));
assert!(validate_explicit_backend(&BackendKind::DensityMatrix, &dense(cap)).is_ok());
}
#[cfg(feature = "gpu")]
#[test]
fn explicit_gpu_kind_matrix() {
let sv_kind = BackendKind::StatevectorGpu {
context: GpuContext::stub_for_tests(),
};
let large = dense(crate::gpu::min_qubits());
let plan = resolved(&sv_kind, &large, false);
assert_eq!(plan.family(), Family::Statevector);
assert!(matches!(plan.accel(), Accel::Gpu { soft: false, .. }));
assert_cpu_family(
&sv_kind,
&dense(crate::gpu::min_qubits() - 1),
false,
Family::Statevector,
);
let stab_kind = BackendKind::StabilizerGpu {
context: GpuContext::stub_for_tests(),
};
let huge = Circuit::new(crate::gpu::stabilizer_min_qubits(), 0);
let plan = resolved(&stab_kind, &huge, false);
assert_eq!(plan.family(), Family::Stabilizer);
assert!(matches!(plan.accel(), Accel::Gpu { soft: false, .. }));
assert_cpu_family(&stab_kind, &clifford(8), false, Family::Stabilizer);
}
#[test]
fn mps_apply_cost_merges_a_pair_block_and_breaks_it_on_a_neighbour() {
let overhead = MPS_GATE_CUT_OVERHEAD;
let mut block = Circuit::new(8, 0);
for _ in 0..3 {
block.add_gate(Gate::Cx, &[3, 4]);
block.add_gate(Gate::Ry(0.4), &[4]);
}
let cost = mps_apply_cost(&block, 256);
assert_eq!(cost, 3.0 * overhead + 8.0 + 64.0 + 64.0);
let mut broken = Circuit::new(8, 0);
broken.add_gate(Gate::Cx, &[3, 4]);
broken.add_gate(Gate::Cx, &[3, 4]);
broken.add_gate(Gate::Cx, &[2, 3]);
broken.add_gate(Gate::Cx, &[3, 4]);
let cost = mps_apply_cost(&broken, 256);
assert_eq!(cost, 4.0 * overhead + 8.0 + 64.0 + 8.0 + 512.0);
}
}