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