cqlib_core/qis/
sampling.rs1use std::collections::{HashMap, HashSet};
16
17use rand::SeedableRng;
18use rand::distr::{Distribution, weighted::WeightedIndex};
19use rand::rngs::SmallRng;
20
21use crate::circuit::gate::ClassicalDataOp;
22use crate::circuit::{Circuit, Directive, Instruction, Qubit};
23use crate::device::{ExecutionResult, Outcome};
24use crate::qis::{DensityMatrix, QisError, StabilizerState, Statevector};
25
26#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
28pub enum Simulator {
29 #[default]
31 Statevector,
32 DensityMatrix,
34 Stabilizer,
36}
37
38pub fn sample(
64 circuit: &Circuit,
65 shots: usize,
66 seed: Option<u64>,
67 simulator: Simulator,
68 qubits: Option<&[Qubit]>,
69) -> Result<ExecutionResult, QisError> {
70 if shots == 0 {
71 return Err(QisError::InvalidParameterValue(
72 "shots must be a positive integer".into(),
73 ));
74 }
75
76 if circuit
77 .operations()
78 .iter()
79 .any(|op| matches!(op.instruction, Instruction::ClassicalControl(_)))
80 {
81 return Err(QisError::UnsupportedOperation(
82 "sample does not support classical control flow".into(),
83 ));
84 }
85 let circuit = circuit.decompose()?;
86 if simulator != Simulator::DensityMatrix
87 && circuit
88 .operations()
89 .iter()
90 .any(|op| matches!(op.instruction, Instruction::Directive(Directive::Reset)))
91 {
92 return Err(QisError::UnsupportedOperation(
93 "sample with reset requires the density_matrix simulator".into(),
94 ));
95 }
96 super::state::validate_terminal_measurements(&circuit)?;
97
98 let circuit_qubits = circuit.qubits();
99 let mut selected = qubits.map(<[Qubit]>::to_vec).unwrap_or_else(|| {
100 let mut seen = HashSet::new();
101 circuit
102 .operations()
103 .iter()
104 .filter(|op| {
105 matches!(
106 op.instruction,
107 Instruction::ClassicalData(
108 ClassicalDataOp::MeasureBit { .. } | ClassicalDataOp::MeasureBits { .. }
109 )
110 )
111 })
112 .flat_map(|op| op.qubits.iter().copied())
113 .filter(|qubit| seen.insert(*qubit))
114 .collect()
115 });
116 if qubits.is_none() && selected.is_empty() {
117 selected = circuit_qubits.clone();
118 }
119 if selected.is_empty() {
120 return Err(QisError::InvalidParameterValue(
121 "sample requires at least one output qubit".into(),
122 ));
123 }
124 let qubit_positions: HashMap<_, _> = circuit_qubits
125 .iter()
126 .enumerate()
127 .map(|(position, &qubit)| (qubit, position))
128 .collect();
129 let mut seen = HashSet::new();
130 let positions = selected
131 .iter()
132 .map(|qubit| {
133 if !seen.insert(*qubit) {
134 return Err(QisError::InvalidParameterValue(
135 "qubits must not contain duplicates".into(),
136 ));
137 }
138 qubit_positions.get(qubit).copied().ok_or_else(|| {
139 QisError::InvalidParameterValue(format!(
140 "output qubit {} is not in the circuit",
141 qubit.id()
142 ))
143 })
144 })
145 .collect::<Result<Vec<_>, _>>()?;
146
147 let mut counts = HashMap::new();
148 let backend = match simulator {
149 Simulator::Statevector | Simulator::DensityMatrix => {
150 let (mut probabilities, backend) = match simulator {
151 Simulator::Statevector => (
152 Statevector::from_circuit(&circuit)?.probabilities(),
153 "statevector",
154 ),
155 _ => (
156 DensityMatrix::from_circuit(&circuit)?.probabilities(),
157 "density_matrix",
158 ),
159 };
160 for probability in &mut probabilities {
163 if (-1e-12..0.0).contains(probability) {
164 *probability = 0.0;
165 }
166 }
167 let distribution = WeightedIndex::new(probabilities)
168 .map_err(|error| QisError::InvalidParameterValue(error.to_string()))?;
169 let mut rng = seed.map_or_else(SmallRng::from_os_rng, SmallRng::seed_from_u64);
170 for _ in 0..shots {
171 let basis = distribution.sample(&mut rng);
172 let outcome = project(&positions, |position| basis & (1usize << position) != 0);
173 *counts.entry(outcome).or_insert(0) += 1;
174 }
175 backend
176 }
177 Simulator::Stabilizer => {
178 let mut state = StabilizerState::from_circuit(&circuit)?;
179 if let Some(seed) = seed {
180 state.seed_rng(seed);
181 }
182 for full in state.sample_shots(shots) {
183 let outcome = project(&positions, |position| full.is_one(position));
184 *counts.entry(outcome).or_insert(0) += 1;
185 }
186 "stabilizer"
187 }
188 };
189
190 Ok(ExecutionResult::from_counts(
191 format!("{backend}-sample"),
192 selected,
193 shots,
194 positions.len(),
195 Some(backend.into()),
196 counts,
197 ))
198}
199
200fn project(positions: &[usize], is_one: impl Fn(usize) -> bool) -> Outcome {
201 Outcome::from_indices(
202 positions.len(),
203 positions
204 .iter()
205 .enumerate()
206 .filter_map(|(bit, &position)| is_one(position).then_some(bit)),
207 )
208}