quantrs2_core/qml/
simulator.rs1use crate::error::{QuantRS2Error, QuantRS2Result};
20use crate::gate::GateOp;
21use scirs2_core::ndarray::Array1;
22use scirs2_core::Complex64;
23
24#[must_use]
26pub fn zero_state(num_qubits: usize) -> Array1<Complex64> {
27 let dim = 1usize << num_qubits;
28 let mut state = Array1::zeros(dim);
29 state[0] = Complex64::new(1.0, 0.0);
30 state
31}
32
33pub fn apply_gate(state: &mut Array1<Complex64>, gate: &dyn GateOp) -> QuantRS2Result<()> {
40 let qubits = gate.qubits();
41 let matrix = gate.matrix()?;
42
43 match qubits.len() {
44 1 => apply_one_qubit(state, qubits[0].0 as usize, &matrix),
45 2 => apply_two_qubit(state, qubits[0].0 as usize, qubits[1].0 as usize, &matrix),
46 k => Err(QuantRS2Error::UnsupportedOperation(format!(
47 "state-vector simulator only supports 1- and 2-qubit gates, got {k}-qubit gate '{}'",
48 gate.name()
49 ))),
50 }
51}
52
53pub fn simulate(num_qubits: usize, gates: &[Box<dyn GateOp>]) -> QuantRS2Result<Array1<Complex64>> {
56 let mut state = zero_state(num_qubits);
57 for gate in gates {
58 apply_gate(&mut state, gate.as_ref())?;
59 }
60 Ok(state)
61}
62
63fn apply_one_qubit(
65 state: &mut Array1<Complex64>,
66 target: usize,
67 matrix: &[Complex64],
68) -> QuantRS2Result<()> {
69 if matrix.len() != 4 {
70 return Err(QuantRS2Error::InvalidInput(format!(
71 "one-qubit gate matrix must have 4 entries, got {}",
72 matrix.len()
73 )));
74 }
75 let dim = state.len();
76 let bit = 1usize << target;
77 if bit >= dim {
78 return Err(QuantRS2Error::InvalidInput(format!(
79 "qubit index {target} out of range for {dim}-amplitude state"
80 )));
81 }
82
83 let mut idx = 0;
84 while idx < dim {
85 if idx & bit == 0 {
86 let i0 = idx;
87 let i1 = idx | bit;
88 let a = state[i0];
89 let b = state[i1];
90 state[i0] = matrix[0] * a + matrix[1] * b;
91 state[i1] = matrix[2] * a + matrix[3] * b;
92 }
93 idx += 1;
94 }
95 Ok(())
96}
97
98fn apply_two_qubit(
104 state: &mut Array1<Complex64>,
105 q_first: usize,
106 q_second: usize,
107 matrix: &[Complex64],
108) -> QuantRS2Result<()> {
109 if matrix.len() != 16 {
110 return Err(QuantRS2Error::InvalidInput(format!(
111 "two-qubit gate matrix must have 16 entries, got {}",
112 matrix.len()
113 )));
114 }
115 if q_first == q_second {
116 return Err(QuantRS2Error::InvalidInput(
117 "two-qubit gate requires two distinct qubits".to_string(),
118 ));
119 }
120 let dim = state.len();
121 let bit_first = 1usize << q_first;
122 let bit_second = 1usize << q_second;
123 if bit_first >= dim || bit_second >= dim {
124 return Err(QuantRS2Error::InvalidInput(format!(
125 "qubit index ({q_first},{q_second}) out of range for {dim}-amplitude state"
126 )));
127 }
128
129 let mut idx = 0;
130 while idx < dim {
131 if idx & bit_first == 0 && idx & bit_second == 0 {
134 let i00 = idx;
135 let i01 = idx | bit_second;
136 let i10 = idx | bit_first;
137 let i11 = idx | bit_first | bit_second;
138 let amps = [state[i00], state[i01], state[i10], state[i11]];
139 for (row, target_idx) in [i00, i01, i10, i11].into_iter().enumerate() {
140 let mut acc = Complex64::new(0.0, 0.0);
141 for (col, amp) in amps.iter().enumerate() {
142 acc += matrix[row * 4 + col] * *amp;
143 }
144 state[target_idx] = acc;
145 }
146 }
147 idx += 1;
148 }
149 Ok(())
150}
151
152#[must_use]
154pub fn probability_one(state: &Array1<Complex64>, target: usize) -> f64 {
155 let bit = 1usize << target;
156 state
157 .iter()
158 .enumerate()
159 .filter(|(i, _)| i & bit != 0)
160 .map(|(_, a)| a.norm_sqr())
161 .sum()
162}
163
164#[must_use]
167pub fn expectation_z(state: &Array1<Complex64>, target: usize) -> f64 {
168 let p1 = probability_one(state, target);
169 1.0 - 2.0 * p1
170}
171
172#[must_use]
174pub fn probabilities(state: &Array1<Complex64>) -> Vec<f64> {
175 state.iter().map(scirs2_core::Complex::norm_sqr).collect()
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use crate::gate::multi::CNOT;
182 use crate::gate::single::{Hadamard, PauliX, RotationY};
183 use crate::qubit::QubitId;
184
185 #[test]
186 fn test_pauli_x_flips_qubit() {
187 let mut state = zero_state(1);
188 let x = PauliX { target: QubitId(0) };
189 apply_gate(&mut state, &x).expect("apply X");
190 assert!((state[1].norm() - 1.0).abs() < 1e-12);
192 assert!(state[0].norm() < 1e-12);
193 assert!((expectation_z(&state, 0) + 1.0).abs() < 1e-12);
194 }
195
196 #[test]
197 fn test_hadamard_superposition() {
198 let mut state = zero_state(1);
199 let h = Hadamard { target: QubitId(0) };
200 apply_gate(&mut state, &h).expect("apply H");
201 assert!((probability_one(&state, 0) - 0.5).abs() < 1e-12);
202 assert!(expectation_z(&state, 0).abs() < 1e-12);
203 }
204
205 #[test]
206 fn test_bell_state_entanglement() {
207 let gates: Vec<Box<dyn GateOp>> = vec![
209 Box::new(Hadamard { target: QubitId(0) }),
210 Box::new(CNOT {
211 control: QubitId(0),
212 target: QubitId(1),
213 }),
214 ];
215 let state = simulate(2, &gates).expect("simulate bell");
216 let probs = probabilities(&state);
217 assert!((probs[0b00] - 0.5).abs() < 1e-12);
218 assert!((probs[0b11] - 0.5).abs() < 1e-12);
219 assert!(probs[0b01] < 1e-12);
220 assert!(probs[0b10] < 1e-12);
221 }
222
223 #[test]
224 fn test_rotation_y_expectation_is_continuous() {
225 let theta = 0.7;
227 let mut state = zero_state(1);
228 let ry = RotationY {
229 target: QubitId(0),
230 theta,
231 };
232 apply_gate(&mut state, &ry).expect("apply RY");
233 assert!((expectation_z(&state, 0) - theta.cos()).abs() < 1e-10);
234 }
235
236 #[test]
237 fn test_two_qubit_gate_on_high_index_qubits() {
238 let mut state = zero_state(2);
240 state[0] = Complex64::new(0.0, 0.0);
242 state[0b10] = Complex64::new(1.0, 0.0);
243 let cnot = CNOT {
244 control: QubitId(1),
245 target: QubitId(0),
246 };
247 apply_gate(&mut state, &cnot).expect("apply CNOT");
248 assert!((state[0b11].norm() - 1.0).abs() < 1e-12);
249 }
250}