use std::collections::{HashMap, HashSet};
use rand::SeedableRng;
use rand::distr::{Distribution, weighted::WeightedIndex};
use rand::rngs::SmallRng;
use crate::circuit::gate::ClassicalDataOp;
use crate::circuit::{Circuit, Directive, Instruction, Qubit};
use crate::device::{ExecutionResult, Outcome};
use crate::qis::{DensityMatrix, QisError, StabilizerState, Statevector};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Simulator {
#[default]
Statevector,
DensityMatrix,
Stabilizer,
}
pub fn sample(
circuit: &Circuit,
shots: usize,
seed: Option<u64>,
simulator: Simulator,
qubits: Option<&[Qubit]>,
) -> Result<ExecutionResult, QisError> {
if shots == 0 {
return Err(QisError::InvalidParameterValue(
"shots must be a positive integer".into(),
));
}
if circuit
.operations()
.iter()
.any(|op| matches!(op.instruction, Instruction::ClassicalControl(_)))
{
return Err(QisError::UnsupportedOperation(
"sample does not support classical control flow".into(),
));
}
let circuit = circuit.decompose()?;
if simulator != Simulator::DensityMatrix
&& circuit
.operations()
.iter()
.any(|op| matches!(op.instruction, Instruction::Directive(Directive::Reset)))
{
return Err(QisError::UnsupportedOperation(
"sample with reset requires the density_matrix simulator".into(),
));
}
super::state::validate_terminal_measurements(&circuit)?;
let circuit_qubits = circuit.qubits();
let mut selected = qubits.map(<[Qubit]>::to_vec).unwrap_or_else(|| {
let mut seen = HashSet::new();
circuit
.operations()
.iter()
.filter(|op| {
matches!(
op.instruction,
Instruction::ClassicalData(
ClassicalDataOp::MeasureBit { .. } | ClassicalDataOp::MeasureBits { .. }
)
)
})
.flat_map(|op| op.qubits.iter().copied())
.filter(|qubit| seen.insert(*qubit))
.collect()
});
if qubits.is_none() && selected.is_empty() {
selected = circuit_qubits.clone();
}
if selected.is_empty() {
return Err(QisError::InvalidParameterValue(
"sample requires at least one output qubit".into(),
));
}
let qubit_positions: HashMap<_, _> = circuit_qubits
.iter()
.enumerate()
.map(|(position, &qubit)| (qubit, position))
.collect();
let mut seen = HashSet::new();
let positions = selected
.iter()
.map(|qubit| {
if !seen.insert(*qubit) {
return Err(QisError::InvalidParameterValue(
"qubits must not contain duplicates".into(),
));
}
qubit_positions.get(qubit).copied().ok_or_else(|| {
QisError::InvalidParameterValue(format!(
"output qubit {} is not in the circuit",
qubit.id()
))
})
})
.collect::<Result<Vec<_>, _>>()?;
let mut counts = HashMap::new();
let backend = match simulator {
Simulator::Statevector | Simulator::DensityMatrix => {
let (mut probabilities, backend) = match simulator {
Simulator::Statevector => (
Statevector::from_circuit(&circuit)?.probabilities(),
"statevector",
),
_ => (
DensityMatrix::from_circuit(&circuit)?.probabilities(),
"density_matrix",
),
};
for probability in &mut probabilities {
if (-1e-12..0.0).contains(probability) {
*probability = 0.0;
}
}
let distribution = WeightedIndex::new(probabilities)
.map_err(|error| QisError::InvalidParameterValue(error.to_string()))?;
let mut rng = seed.map_or_else(SmallRng::from_os_rng, SmallRng::seed_from_u64);
for _ in 0..shots {
let basis = distribution.sample(&mut rng);
let outcome = project(&positions, |position| basis & (1usize << position) != 0);
*counts.entry(outcome).or_insert(0) += 1;
}
backend
}
Simulator::Stabilizer => {
let mut state = StabilizerState::from_circuit(&circuit)?;
if let Some(seed) = seed {
state.seed_rng(seed);
}
for full in state.sample_shots(shots) {
let outcome = project(&positions, |position| full.is_one(position));
*counts.entry(outcome).or_insert(0) += 1;
}
"stabilizer"
}
};
Ok(ExecutionResult::from_counts(
format!("{backend}-sample"),
selected,
shots,
positions.len(),
Some(backend.into()),
counts,
))
}
fn project(positions: &[usize], is_one: impl Fn(usize) -> bool) -> Outcome {
Outcome::from_indices(
positions.len(),
positions
.iter()
.enumerate()
.filter_map(|(bit, &position)| is_one(position).then_some(bit)),
)
}