quantrs2_sim/mixed_precision_impl/
simulator.rs1use crate::adaptive_gate_fusion::{FusedGateBlock, GateType, QuantumGate};
7use crate::error::{Result, SimulatorError};
8use crate::prelude::SciRS2Backend;
9use ndarray::Array1;
10use num_complex::Complex64;
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13
14use super::analysis::{PrecisionAnalysis, PrecisionAnalyzer};
15use super::config::{MixedPrecisionConfig, QuantumPrecision};
16use super::state_vector::MixedPrecisionStateVector;
17
18pub struct MixedPrecisionSimulator {
20 config: MixedPrecisionConfig,
22 state: Option<MixedPrecisionStateVector>,
24 num_qubits: usize,
26 #[cfg(feature = "advanced_math")]
28 backend: Option<SciRS2Backend>,
29 stats: MixedPrecisionStats,
31 analyzer: PrecisionAnalyzer,
33}
34
35#[derive(Debug, Clone, Default, Serialize, Deserialize)]
37pub struct MixedPrecisionStats {
38 pub total_gates: usize,
40 pub precision_adaptations: usize,
42 pub total_time_ms: f64,
44 pub memory_usage_by_precision: HashMap<QuantumPrecision, usize>,
46 pub gate_time_by_precision: HashMap<QuantumPrecision, f64>,
48 pub error_estimates: HashMap<QuantumPrecision, f64>,
50}
51
52impl MixedPrecisionSimulator {
53 pub fn new(num_qubits: usize, config: MixedPrecisionConfig) -> Result<Self> {
55 config.validate()?;
56
57 let state = Some(MixedPrecisionStateVector::computational_basis(
58 num_qubits,
59 config.state_vector_precision,
60 ));
61
62 Ok(Self {
63 config,
64 state,
65 num_qubits,
66 #[cfg(feature = "advanced_math")]
67 backend: None,
68 stats: MixedPrecisionStats::default(),
69 analyzer: PrecisionAnalyzer::new(),
70 })
71 }
72
73 #[cfg(feature = "advanced_math")]
75 pub fn with_backend(mut self) -> Result<Self> {
76 self.backend = Some(SciRS2Backend::new());
77 Ok(self)
78 }
79
80 pub fn apply_gate(&mut self, gate: &QuantumGate) -> Result<()> {
82 let start_time = std::time::Instant::now();
83
84 let gate_precision = self.select_gate_precision(gate)?;
86
87 self.adapt_state_precision(gate_precision)?;
89
90 self.apply_gate_with_precision(gate, gate_precision)?;
92
93 let execution_time = start_time.elapsed().as_millis() as f64;
95 self.stats.total_gates += 1;
96 self.stats.total_time_ms += execution_time;
97 *self
98 .stats
99 .gate_time_by_precision
100 .entry(gate_precision)
101 .or_insert(0.0) += execution_time;
102
103 Ok(())
104 }
105
106 pub fn apply_fused_block(&mut self, block: &FusedGateBlock) -> Result<()> {
108 let optimal_precision = self.select_block_precision(block)?;
109 self.adapt_state_precision(optimal_precision)?;
110
111 for gate in &block.gates {
113 self.apply_gate_with_precision(gate, optimal_precision)?;
114 }
115
116 Ok(())
117 }
118
119 pub fn measure_qubit(&mut self, qubit: usize) -> Result<bool> {
121 if qubit >= self.num_qubits {
122 return Err(SimulatorError::InvalidInput(format!(
123 "Qubit {} out of range for {}-qubit system",
124 qubit, self.num_qubits
125 )));
126 }
127
128 self.adapt_state_precision(self.config.measurement_precision)?;
130
131 let state = self.state.as_ref().unwrap();
132
133 let mut prob_one = 0.0;
135 let mask = 1 << qubit;
136
137 for i in 0..state.len() {
138 if i & mask != 0 {
139 prob_one += state.probability(i)?;
140 }
141 }
142
143 let result = fastrand::f64() < prob_one;
145
146 self.collapse_state(qubit, result)?;
148
149 Ok(result)
150 }
151
152 pub fn measure_all(&mut self) -> Result<Vec<bool>> {
154 let mut results = Vec::new();
155 for qubit in 0..self.num_qubits {
156 results.push(self.measure_qubit(qubit)?);
157 }
158 Ok(results)
159 }
160
161 pub fn get_state(&self) -> Option<&MixedPrecisionStateVector> {
163 self.state.as_ref()
164 }
165
166 pub fn expectation_value(&self, pauli_string: &str) -> Result<f64> {
168 if pauli_string.len() != self.num_qubits {
169 return Err(SimulatorError::InvalidInput(
170 "Pauli string length must match number of qubits".to_string(),
171 ));
172 }
173
174 let state = self.state.as_ref().unwrap();
175 let mut expectation = 0.0;
176
177 for i in 0..state.len() {
178 let mut sign = 1.0;
179 let mut amplitude = state.amplitude(i)?;
180
181 for (qubit, pauli) in pauli_string.chars().enumerate() {
183 match pauli {
184 'I' => {} 'X' => {
186 let flipped = i ^ (1 << qubit);
188 amplitude = state.amplitude(flipped)?;
189 }
190 'Y' => {
191 let flipped = i ^ (1 << qubit);
193 amplitude = state.amplitude(flipped)?;
194 if i & (1 << qubit) != 0 {
195 sign *= -1.0;
196 }
197 amplitude *= Complex64::new(0.0, sign);
198 }
199 'Z' => {
200 if i & (1 << qubit) != 0 {
202 sign *= -1.0;
203 }
204 }
205 _ => {
206 return Err(SimulatorError::InvalidInput(format!(
207 "Invalid Pauli operator: {}",
208 pauli
209 )))
210 }
211 }
212 }
213
214 expectation += (amplitude.conj() * amplitude * sign).re;
215 }
216
217 Ok(expectation)
218 }
219
220 pub fn analyze_precision(&mut self) -> Result<PrecisionAnalysis> {
222 Ok(self
223 .analyzer
224 .analyze_for_tolerance(self.config.error_tolerance))
225 }
226
227 pub fn get_stats(&self) -> &MixedPrecisionStats {
229 &self.stats
230 }
231
232 pub fn reset(&mut self) -> Result<()> {
234 self.state = Some(MixedPrecisionStateVector::computational_basis(
235 self.num_qubits,
236 self.config.state_vector_precision,
237 ));
238 self.stats = MixedPrecisionStats::default();
239 self.analyzer.reset();
240 Ok(())
241 }
242
243 fn select_gate_precision(&self, gate: &QuantumGate) -> Result<QuantumPrecision> {
245 if !self.config.adaptive_precision {
246 return Ok(self.config.gate_precision);
247 }
248
249 let precision = match gate.gate_type {
251 GateType::PauliX
252 | GateType::PauliY
253 | GateType::PauliZ
254 | GateType::Hadamard
255 | GateType::Phase
256 | GateType::T
257 | GateType::RotationX
258 | GateType::RotationY
259 | GateType::RotationZ
260 | GateType::Identity => {
261 if self.config.gate_precision == QuantumPrecision::Adaptive {
263 QuantumPrecision::Single
264 } else {
265 self.config.gate_precision
266 }
267 }
268 GateType::CNOT | GateType::CZ | GateType::SWAP | GateType::ISwap => {
269 if self.config.gate_precision == QuantumPrecision::Adaptive {
271 if self.num_qubits > self.config.large_system_threshold {
272 QuantumPrecision::Single
273 } else {
274 QuantumPrecision::Double
275 }
276 } else {
277 self.config.gate_precision
278 }
279 }
280 GateType::Toffoli | GateType::Fredkin => {
281 if self.config.gate_precision == QuantumPrecision::Adaptive {
283 QuantumPrecision::Double
284 } else {
285 self.config.gate_precision
286 }
287 }
288 GateType::Custom(_) => {
289 QuantumPrecision::Double
291 }
292 };
293
294 Ok(precision)
295 }
296
297 fn select_block_precision(&self, _block: &FusedGateBlock) -> Result<QuantumPrecision> {
299 if self.config.adaptive_precision {
301 Ok(QuantumPrecision::Single)
302 } else {
303 Ok(self.config.gate_precision)
304 }
305 }
306
307 fn adapt_state_precision(&mut self, target_precision: QuantumPrecision) -> Result<()> {
309 if let Some(ref state) = self.state {
310 if state.precision() != target_precision {
311 let new_state = state.to_precision(target_precision)?;
312 self.state = Some(new_state);
313 self.stats.precision_adaptations += 1;
314 }
315 }
316 Ok(())
317 }
318
319 fn apply_gate_with_precision(
321 &mut self,
322 gate: &QuantumGate,
323 _precision: QuantumPrecision,
324 ) -> Result<()> {
325 if let Some(ref mut state) = self.state {
328 let memory_usage = state.memory_usage();
333 self.stats
334 .memory_usage_by_precision
335 .insert(state.precision(), memory_usage);
336 }
337
338 Ok(())
339 }
340
341 fn collapse_state(&mut self, qubit: usize, result: bool) -> Result<()> {
343 if let Some(ref mut state) = self.state {
344 let mask = 1 << qubit;
345 let mut norm_factor = 0.0;
346
347 for i in 0..state.len() {
349 let bit_value = (i & mask) != 0;
350 if bit_value == result {
351 norm_factor += state.probability(i)?;
352 }
353 }
354
355 if norm_factor == 0.0 {
356 return Err(SimulatorError::InvalidInput(
357 "Invalid measurement result: zero probability".to_string(),
358 ));
359 }
360
361 norm_factor = norm_factor.sqrt();
362
363 for i in 0..state.len() {
365 let bit_value = (i & mask) != 0;
366 if bit_value == result {
367 let amplitude = state.amplitude(i)?;
368 state.set_amplitude(i, amplitude / norm_factor)?;
369 } else {
370 state.set_amplitude(i, Complex64::new(0.0, 0.0))?;
371 }
372 }
373 }
374
375 Ok(())
376 }
377}
378
379impl MixedPrecisionStats {
380 pub fn average_gate_time(&self) -> f64 {
382 if self.total_gates > 0 {
383 self.total_time_ms / self.total_gates as f64
384 } else {
385 0.0
386 }
387 }
388
389 pub fn total_memory_usage(&self) -> usize {
391 self.memory_usage_by_precision.values().sum()
392 }
393
394 pub fn adaptation_rate(&self) -> f64 {
396 if self.total_gates > 0 {
397 self.precision_adaptations as f64 / self.total_gates as f64
398 } else {
399 0.0
400 }
401 }
402
403 pub fn summary(&self) -> String {
405 format!(
406 "Gates: {}, Adaptations: {}, Avg Time: {:.2}ms, Total Memory: {}MB",
407 self.total_gates,
408 self.precision_adaptations,
409 self.average_gate_time(),
410 self.total_memory_usage() / (1024 * 1024)
411 )
412 }
413}
414
415pub mod utils {
417 use super::*;
418
419 pub fn convert_state_vector(
421 state: &Array1<Complex64>,
422 precision: QuantumPrecision,
423 ) -> Result<MixedPrecisionStateVector> {
424 let mut mp_state = MixedPrecisionStateVector::new(state.len(), precision);
425
426 for (i, &litude) in state.iter().enumerate() {
427 mp_state.set_amplitude(i, amplitude)?;
428 }
429
430 Ok(mp_state)
431 }
432
433 pub fn extract_state_vector(mp_state: &MixedPrecisionStateVector) -> Result<Array1<Complex64>> {
435 let mut state = Array1::zeros(mp_state.len());
436
437 for i in 0..mp_state.len() {
438 state[i] = mp_state.amplitude(i)?;
439 }
440
441 Ok(state)
442 }
443
444 pub fn memory_savings(config: &MixedPrecisionConfig, num_qubits: usize) -> f64 {
446 let double_precision_size = (1 << num_qubits) * std::mem::size_of::<Complex64>();
447 let mixed_precision_size = config.estimate_memory_usage(num_qubits);
448
449 1.0 - (mixed_precision_size as f64 / double_precision_size as f64)
450 }
451
452 pub fn performance_improvement_factor(precision: QuantumPrecision) -> f64 {
454 1.0 / precision.computation_factor()
455 }
456}