1use std::collections::HashMap;
22
23use scirs2_core::Complex64;
24
25use super::variational_algorithms::{ParameterizedQuantumCircuit, QuantumGate};
26use crate::{CircuitResult, DeviceError, DeviceResult};
27
28const MAX_SIMULATED_QUBITS: usize = 26;
32
33pub fn simulate_statevector(circuit: &ParameterizedQuantumCircuit) -> DeviceResult<Vec<Complex64>> {
40 let num_qubits = circuit.num_qubits();
41 if num_qubits > MAX_SIMULATED_QUBITS {
42 return Err(DeviceError::InvalidInput(format!(
43 "Local state-vector simulation supports at most {MAX_SIMULATED_QUBITS} qubits, \
44 but circuit has {num_qubits}"
45 )));
46 }
47
48 let dim = 1usize << num_qubits;
49 let mut state = vec![Complex64::new(0.0, 0.0); dim];
50 state[0] = Complex64::new(1.0, 0.0);
51
52 for gate in circuit.gates() {
53 apply_gate(&mut state, num_qubits, gate)?;
54 }
55
56 Ok(state)
57}
58
59fn apply_gate(state: &mut [Complex64], num_qubits: usize, gate: &QuantumGate) -> DeviceResult<()> {
61 match *gate {
62 QuantumGate::H(q) => {
63 let s = std::f64::consts::FRAC_1_SQRT_2;
64 apply_single_qubit(
65 state,
66 num_qubits,
67 q,
68 [
69 Complex64::new(s, 0.0),
70 Complex64::new(s, 0.0),
71 Complex64::new(s, 0.0),
72 Complex64::new(-s, 0.0),
73 ],
74 )
75 }
76 QuantumGate::X(q) => apply_single_qubit(
77 state,
78 num_qubits,
79 q,
80 [
81 Complex64::new(0.0, 0.0),
82 Complex64::new(1.0, 0.0),
83 Complex64::new(1.0, 0.0),
84 Complex64::new(0.0, 0.0),
85 ],
86 ),
87 QuantumGate::Y(q) => apply_single_qubit(
88 state,
89 num_qubits,
90 q,
91 [
92 Complex64::new(0.0, 0.0),
93 Complex64::new(0.0, -1.0),
94 Complex64::new(0.0, 1.0),
95 Complex64::new(0.0, 0.0),
96 ],
97 ),
98 QuantumGate::Z(q) => apply_single_qubit(
99 state,
100 num_qubits,
101 q,
102 [
103 Complex64::new(1.0, 0.0),
104 Complex64::new(0.0, 0.0),
105 Complex64::new(0.0, 0.0),
106 Complex64::new(-1.0, 0.0),
107 ],
108 ),
109 QuantumGate::SDagger(q) => apply_single_qubit(
110 state,
111 num_qubits,
112 q,
113 [
114 Complex64::new(1.0, 0.0),
115 Complex64::new(0.0, 0.0),
116 Complex64::new(0.0, 0.0),
117 Complex64::new(0.0, -1.0),
118 ],
119 ),
120 QuantumGate::RX(q, theta) => {
121 let c = (theta / 2.0).cos();
122 let s = (theta / 2.0).sin();
123 apply_single_qubit(
124 state,
125 num_qubits,
126 q,
127 [
128 Complex64::new(c, 0.0),
129 Complex64::new(0.0, -s),
130 Complex64::new(0.0, -s),
131 Complex64::new(c, 0.0),
132 ],
133 )
134 }
135 QuantumGate::RY(q, theta) => {
136 let c = (theta / 2.0).cos();
137 let s = (theta / 2.0).sin();
138 apply_single_qubit(
139 state,
140 num_qubits,
141 q,
142 [
143 Complex64::new(c, 0.0),
144 Complex64::new(-s, 0.0),
145 Complex64::new(s, 0.0),
146 Complex64::new(c, 0.0),
147 ],
148 )
149 }
150 QuantumGate::RZ(q, theta) => {
151 let phase_neg = Complex64::from_polar(1.0, -theta / 2.0);
152 let phase_pos = Complex64::from_polar(1.0, theta / 2.0);
153 apply_single_qubit(
154 state,
155 num_qubits,
156 q,
157 [
158 phase_neg,
159 Complex64::new(0.0, 0.0),
160 Complex64::new(0.0, 0.0),
161 phase_pos,
162 ],
163 )
164 }
165 QuantumGate::CNOT(control, target) => {
166 apply_controlled_x(state, num_qubits, control, target)
167 }
168 QuantumGate::CZ(control, target) => apply_controlled_z(state, num_qubits, control, target),
169 }
170}
171
172fn apply_single_qubit(
174 state: &mut [Complex64],
175 num_qubits: usize,
176 q: usize,
177 matrix: [Complex64; 4],
178) -> DeviceResult<()> {
179 if q >= num_qubits {
180 return Err(DeviceError::InvalidInput(format!(
181 "Gate targets qubit {q} but circuit only has {num_qubits} qubits"
182 )));
183 }
184 let bit = 1usize << q;
185 let dim = state.len();
186 for base in 0..dim {
187 if base & bit != 0 {
188 continue;
189 }
190 let i0 = base;
191 let i1 = base | bit;
192 let a0 = state[i0];
193 let a1 = state[i1];
194 state[i0] = matrix[0] * a0 + matrix[1] * a1;
195 state[i1] = matrix[2] * a0 + matrix[3] * a1;
196 }
197 Ok(())
198}
199
200fn apply_controlled_x(
202 state: &mut [Complex64],
203 num_qubits: usize,
204 control: usize,
205 target: usize,
206) -> DeviceResult<()> {
207 validate_two_qubit(num_qubits, control, target)?;
208 let control_bit = 1usize << control;
209 let target_bit = 1usize << target;
210 let dim = state.len();
211 for base in 0..dim {
212 if (base & control_bit != 0) && (base & target_bit == 0) {
215 let partner = base | target_bit;
216 state.swap(base, partner);
217 }
218 }
219 Ok(())
220}
221
222fn apply_controlled_z(
224 state: &mut [Complex64],
225 num_qubits: usize,
226 control: usize,
227 target: usize,
228) -> DeviceResult<()> {
229 validate_two_qubit(num_qubits, control, target)?;
230 let control_bit = 1usize << control;
231 let target_bit = 1usize << target;
232 let dim = state.len();
233 for (idx, amp) in state.iter_mut().enumerate().take(dim) {
234 if (idx & control_bit != 0) && (idx & target_bit != 0) {
235 *amp = -*amp;
236 }
237 }
238 Ok(())
239}
240
241fn validate_two_qubit(num_qubits: usize, control: usize, target: usize) -> DeviceResult<()> {
242 if control >= num_qubits || target >= num_qubits {
243 return Err(DeviceError::InvalidInput(format!(
244 "Two-qubit gate on ({control}, {target}) but circuit only has {num_qubits} qubits"
245 )));
246 }
247 if control == target {
248 return Err(DeviceError::InvalidInput(
249 "Two-qubit gate requires distinct control and target qubits".to_string(),
250 ));
251 }
252 Ok(())
253}
254
255pub fn outcome_probabilities(state: &[Complex64]) -> Vec<f64> {
259 state.iter().map(|amp| amp.norm_sqr()).collect()
260}
261
262fn index_to_bitstring(index: usize, num_qubits: usize) -> String {
266 let mut s = String::with_capacity(num_qubits);
267 for q in 0..num_qubits {
268 if index & (1usize << q) != 0 {
269 s.push('1');
270 } else {
271 s.push('0');
272 }
273 }
274 s
275}
276
277pub fn simulate_and_sample(
285 circuit: &ParameterizedQuantumCircuit,
286 shots: usize,
287) -> DeviceResult<CircuitResult> {
288 let num_qubits = circuit.num_qubits();
289 let state = simulate_statevector(circuit)?;
290 let probabilities = outcome_probabilities(&state);
291
292 let total: f64 = probabilities.iter().sum();
294 if total <= 0.0 || !total.is_finite() {
295 return Err(DeviceError::ExecutionFailed(
296 "Circuit produced a non-normalizable state (zero or non-finite total probability)"
297 .to_string(),
298 ));
299 }
300
301 let mut cumulative = Vec::with_capacity(probabilities.len());
302 let mut running = 0.0;
303 for p in &probabilities {
304 running += p / total;
305 cumulative.push(running);
306 }
307 if let Some(last) = cumulative.last_mut() {
309 *last = 1.0;
310 }
311
312 let mut counts: HashMap<String, usize> = HashMap::new();
313 for _ in 0..shots {
314 let r = fastrand::f64();
315 let idx = match cumulative
316 .binary_search_by(|probe| probe.partial_cmp(&r).unwrap_or(std::cmp::Ordering::Less))
317 {
318 Ok(i) | Err(i) => i.min(cumulative.len().saturating_sub(1)),
319 };
320 *counts
321 .entry(index_to_bitstring(idx, num_qubits))
322 .or_insert(0) += 1;
323 }
324
325 let mut metadata = HashMap::new();
326 metadata.insert("backend".to_string(), "local_statevector".to_string());
327 metadata.insert("num_qubits".to_string(), num_qubits.to_string());
328
329 Ok(CircuitResult {
330 counts,
331 shots,
332 metadata,
333 })
334}
335
336pub fn expected_hamming_weight(circuit: &ParameterizedQuantumCircuit) -> DeviceResult<f64> {
343 let num_qubits = circuit.num_qubits();
344 let state = simulate_statevector(circuit)?;
345 let mut expectation = 0.0;
346 for (idx, amp) in state.iter().enumerate() {
347 let weight = (idx.count_ones()) as f64;
348 expectation += weight * amp.norm_sqr();
349 }
350 Ok(expectation)
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356
357 #[test]
358 fn bell_state_is_correlated_not_uniform() {
359 let mut circuit = ParameterizedQuantumCircuit::new(2);
361 circuit.add_h_gate(0).unwrap();
362 circuit.add_cnot_gate(0, 1).unwrap();
363
364 let probs = outcome_probabilities(&simulate_statevector(&circuit).unwrap());
365 assert!((probs[0] - 0.5).abs() < 1e-9, "P(00) should be 0.5");
367 assert!((probs[3] - 0.5).abs() < 1e-9, "P(11) should be 0.5");
368 assert!(probs[1].abs() < 1e-9, "P(01) should be 0");
369 assert!(probs[2].abs() < 1e-9, "P(10) should be 0");
370
371 let result = simulate_and_sample(&circuit, 4096).unwrap();
375 let c01 = result.counts.get("10").copied().unwrap_or(0); let c10 = result.counts.get("01").copied().unwrap_or(0);
377 assert_eq!(c01, 0, "Bell state must never measure 01");
378 assert_eq!(c10, 0, "Bell state must never measure 10");
379 let c00 = result.counts.get("00").copied().unwrap_or(0);
380 let c11 = result.counts.get("11").copied().unwrap_or(0);
381 assert_eq!(c00 + c11, 4096);
382 assert!(c00 > 0 && c11 > 0, "both correlated outcomes should appear");
385 }
386
387 #[test]
388 fn x_gate_flips_qubit() {
389 let mut circuit = ParameterizedQuantumCircuit::new(1);
390 circuit.add_x_gate(0).unwrap();
391 let probs = outcome_probabilities(&simulate_statevector(&circuit).unwrap());
392 assert!(probs[1] > 0.999, "X|0> = |1>");
393 assert_eq!(expected_hamming_weight(&circuit).unwrap().round() as i64, 1);
394 }
395
396 #[test]
397 fn ry_rotation_matches_analytic_probability() {
398 let theta = 0.7;
400 let mut circuit = ParameterizedQuantumCircuit::new(1);
401 circuit.add_ry_gate(0, theta).unwrap();
402 let probs = outcome_probabilities(&simulate_statevector(&circuit).unwrap());
403 let expected_p1 = (theta / 2.0).sin().powi(2);
404 assert!((probs[1] - expected_p1).abs() < 1e-9);
405 let weight = expected_hamming_weight(&circuit).unwrap();
406 assert!((weight - expected_p1).abs() < 1e-9);
407 }
408
409 #[test]
410 fn rejects_oversized_circuit() {
411 let circuit = ParameterizedQuantumCircuit::new(MAX_SIMULATED_QUBITS + 1);
412 assert!(simulate_statevector(&circuit).is_err());
413 }
414}