1use crate::error::{Result, SimulatorError};
6use quantrs2_circuit::prelude::Circuit;
7use scirs2_core::ndarray::{Array1, Array2};
8use scirs2_core::Complex64;
9use std::collections::HashMap;
10
11use super::types::{
12 CircuitComplexity, ComputePrecision, ContractionPath, CuQuantumConfig, CuQuantumError,
13 CuQuantumResult, CuQuantumSimulator, CuStateVecSimulator, CuTensorNetSimulator, CudaDeviceInfo,
14 GateFusionLevel, GpuResourcePlanner, Observable, PerformanceEstimator, RecommendedBackend,
15 SimulationStats, TensorNetworkState,
16};
17
18impl From<CuQuantumError> for SimulatorError {
19 fn from(err: CuQuantumError) -> Self {
20 SimulatorError::GpuError(err.to_string())
21 }
22}
23#[cfg(test)]
24#[allow(clippy::field_reassign_with_default)]
25mod tests {
26 use super::*;
27 #[test]
28 fn test_config_default() {
29 let config = CuQuantumConfig::default();
30 assert_eq!(config.device_id, -1);
31 assert!(!config.multi_gpu);
32 assert_eq!(config.precision, ComputePrecision::Double);
33 }
34 #[test]
35 fn test_config_large_circuit() {
36 let config = CuQuantumConfig::large_circuit();
37 assert!(config.memory_optimization);
38 assert_eq!(config.gate_fusion_level, GateFusionLevel::Aggressive);
39 }
40 #[test]
41 fn test_config_variational() {
42 let config = CuQuantumConfig::variational();
43 assert!(config.async_execution);
44 assert_eq!(config.gate_fusion_level, GateFusionLevel::Moderate);
45 }
46 #[test]
47 fn test_config_multi_gpu() {
48 let config = CuQuantumConfig::multi_gpu(4);
49 assert!(config.multi_gpu);
50 assert_eq!(config.num_gpus, 4);
51 }
52 #[test]
53 fn test_device_info_mock() {
54 let info = CuStateVecSimulator::get_device_info(-1).expect("Should get mock device info");
55 assert!(info.total_memory > 0);
56 assert!(info.compute_capability.0 >= 7);
57 }
58 #[test]
59 fn test_device_max_qubits() {
60 let info = CudaDeviceInfo {
61 device_id: 0,
62 name: "Test A100".to_string(),
63 total_memory: 40 * 1024 * 1024 * 1024,
64 free_memory: 32 * 1024 * 1024 * 1024,
65 compute_capability: (8, 0),
66 sm_count: 108,
67 max_threads_per_block: 1024,
68 warp_size: 32,
69 has_tensor_cores: true,
70 };
71 let max_qubits = info.max_statevec_qubits();
72 assert!(
73 max_qubits >= 30,
74 "Expected >= 30 qubits, got {}",
75 max_qubits
76 );
77 assert!(
78 max_qubits <= 35,
79 "Expected <= 35 qubits, got {}",
80 max_qubits
81 );
82 let info_small = CudaDeviceInfo {
83 device_id: 0,
84 name: "Test RTX 3080".to_string(),
85 total_memory: 12 * 1024 * 1024 * 1024,
86 free_memory: 10 * 1024 * 1024 * 1024,
87 compute_capability: (8, 6),
88 sm_count: 68,
89 max_threads_per_block: 1024,
90 warp_size: 32,
91 has_tensor_cores: true,
92 };
93 let max_qubits_small = info_small.max_statevec_qubits();
94 assert!(
95 max_qubits_small >= 28,
96 "Expected >= 28 qubits for 12GB GPU, got {}",
97 max_qubits_small
98 );
99 }
100 #[test]
101 fn test_custatevec_simulator_creation() {
102 let config = CuQuantumConfig::default();
103 let sim = CuStateVecSimulator::new(config);
104 assert!(sim.is_ok());
105 }
106 #[test]
107 fn test_cutensornet_simulator_creation() {
108 let config = CuQuantumConfig::default();
109 let sim = CuTensorNetSimulator::new(config);
110 assert!(sim.is_ok());
111 }
112 #[test]
113 fn test_simulation_stats() {
114 let mut stats = SimulationStats::default();
115 stats.total_simulations = 10;
116 stats.total_gates = 100;
117 stats.total_time_ms = 500.0;
118 stats.total_flops = 1e9;
119 assert_eq!(stats.avg_gates_per_sim(), 10.0);
120 assert_eq!(stats.avg_time_per_sim(), 50.0);
121 assert!((stats.throughput_gflops() - 2.0).abs() < 0.01);
122 }
123 #[test]
124 fn test_contraction_path() {
125 let mut path = ContractionPath::new();
126 path.add_contraction(0, 1);
127 path.add_contraction(0, 2);
128 assert_eq!(path.contractions.len(), 2);
129 assert!(path.total_cost() > 0.0);
130 }
131 #[test]
132 fn test_unified_simulator_creation() {
133 let config = CuQuantumConfig::default();
134 let sim = CuQuantumSimulator::new(config);
135 assert!(sim.is_ok());
136 }
137 #[test]
138 fn test_observable_creation() {
139 let obs = Observable::PauliZ(vec![0, 1]);
140 match obs {
141 Observable::PauliZ(qubits) => assert_eq!(qubits.len(), 2),
142 _ => panic!("Wrong observable type"),
143 }
144 }
145 #[test]
146 fn test_cuquantum_result_from_state_vector() {
147 use scirs2_core::ndarray::Array1;
148 use scirs2_core::Complex64;
149 let mut state = Array1::zeros(4);
150 state[0] = Complex64::new(1.0, 0.0);
151 let result = CuQuantumResult::from_state_vector(state.clone(), 2);
152 assert_eq!(result.num_qubits, 2);
153 assert!(result.state_vector.is_some());
154 assert!(result.counts.is_empty());
155 let probs = result.probabilities().expect("Should have probabilities");
156 assert_eq!(probs.len(), 4);
157 assert!((probs[0] - 1.0).abs() < 1e-10);
158 assert!(probs[1] < 1e-10);
159 assert!(probs[2] < 1e-10);
160 assert!(probs[3] < 1e-10);
161 }
162 #[test]
163 fn test_cuquantum_result_from_counts() {
164 let mut counts = HashMap::new();
165 counts.insert("00".to_string(), 500);
166 counts.insert("11".to_string(), 500);
167 let result = CuQuantumResult::from_counts(counts.clone(), 2);
168 assert_eq!(result.num_qubits, 2);
169 assert!(result.state_vector.is_none());
170 assert_eq!(result.counts.len(), 2);
171 assert_eq!(*result.counts.get("00").unwrap_or(&0), 500);
172 assert_eq!(*result.counts.get("11").unwrap_or(&0), 500);
173 }
174 #[test]
175 fn test_cuquantum_result_expectation_z() {
176 use scirs2_core::ndarray::Array1;
177 use scirs2_core::Complex64;
178 let mut state_zero = Array1::zeros(2);
179 state_zero[0] = Complex64::new(1.0, 0.0);
180 let result_zero = CuQuantumResult::from_state_vector(state_zero, 1);
181 let exp_z = result_zero
182 .expectation_z(0)
183 .expect("Should compute expectation");
184 assert!(
185 (exp_z - 1.0).abs() < 1e-10,
186 "Expected +1 for |0⟩, got {}",
187 exp_z
188 );
189 let mut state_one = Array1::zeros(2);
190 state_one[1] = Complex64::new(1.0, 0.0);
191 let result_one = CuQuantumResult::from_state_vector(state_one, 1);
192 let exp_z_one = result_one
193 .expectation_z(0)
194 .expect("Should compute expectation");
195 assert!(
196 (exp_z_one - (-1.0)).abs() < 1e-10,
197 "Expected -1 for |1⟩, got {}",
198 exp_z_one
199 );
200 let mut state_plus = Array1::zeros(2);
201 let inv_sqrt2 = 1.0 / 2.0_f64.sqrt();
202 state_plus[0] = Complex64::new(inv_sqrt2, 0.0);
203 state_plus[1] = Complex64::new(inv_sqrt2, 0.0);
204 let result_plus = CuQuantumResult::from_state_vector(state_plus, 1);
205 let exp_z_plus = result_plus
206 .expectation_z(0)
207 .expect("Should compute expectation");
208 assert!(
209 exp_z_plus.abs() < 1e-10,
210 "Expected 0 for |+⟩, got {}",
211 exp_z_plus
212 );
213 }
214 #[test]
215 fn test_custatevec_circuit_simulation() {
216 use quantrs2_circuit::prelude::Circuit;
217 let config = CuQuantumConfig::default();
218 let mut sim = CuStateVecSimulator::new(config).expect("Should create simulator");
219 let circuit: Circuit<2> = Circuit::new();
220 let result = sim.simulate(&circuit);
221 #[cfg(all(feature = "cuquantum", not(target_os = "macos")))]
223 {
224 assert!(
225 result.is_err(),
226 "Expected error for unimplemented cuStateVec"
227 );
228 }
229 #[cfg(any(target_os = "macos", not(feature = "cuquantum")))]
230 {
231 let result = result.expect("Should simulate circuit");
232 assert_eq!(result.num_qubits, 2);
233 assert!(result.state_vector.is_some());
234 let sv = result
235 .state_vector
236 .as_ref()
237 .expect("Should have state vector");
238 assert_eq!(sv.len(), 4);
239 assert!((sv[0].norm() - 1.0).abs() < 1e-10);
240 }
241 }
242 #[test]
243 fn test_custatevec_statistics() {
244 use quantrs2_circuit::prelude::Circuit;
245 let config = CuQuantumConfig::default();
246 let mut sim = CuStateVecSimulator::new(config).expect("Should create simulator");
247 let stats = sim.stats();
248 assert_eq!(stats.total_simulations, 0);
249 let circuit: Circuit<2> = Circuit::new();
250 let result = sim.simulate(&circuit);
251 let stats_after = sim.stats();
252 #[cfg(all(feature = "cuquantum", not(target_os = "macos")))]
254 {
255 assert!(result.is_err());
256 assert_eq!(stats_after.total_simulations, 0);
257 }
258 #[cfg(any(target_os = "macos", not(feature = "cuquantum")))]
259 {
260 let _ = result;
261 assert_eq!(stats_after.total_simulations, 1);
262 }
263 sim.reset_stats();
264 let stats_reset = sim.stats();
265 assert_eq!(stats_reset.total_simulations, 0);
266 }
267 #[test]
268 fn test_unified_simulator_auto_selection() {
269 use quantrs2_circuit::prelude::Circuit;
270 let config_small = CuQuantumConfig::default();
271 let mut sim_small = CuQuantumSimulator::new(config_small).expect("Should create simulator");
272 let circuit: Circuit<2> = Circuit::new();
273 let result = sim_small.simulate(&circuit);
274 #[cfg(all(feature = "cuquantum", not(target_os = "macos")))]
276 {
277 assert!(
278 result.is_err(),
279 "Expected error for unimplemented cuQuantum"
280 );
281 }
282 #[cfg(any(target_os = "macos", not(feature = "cuquantum")))]
283 {
284 let result = result.expect("Should simulate");
285 assert_eq!(result.num_qubits, 2);
286 let mut config_large = CuQuantumConfig::default();
287 config_large.max_statevec_qubits = 10;
288 let mut sim_large =
289 CuQuantumSimulator::new(config_large).expect("Should create simulator");
290 let result_large = sim_large.simulate(&circuit).expect("Should simulate");
291 assert_eq!(result_large.num_qubits, 2);
292 }
293 }
294 #[test]
295 fn test_cutensornet_network_building() {
296 use quantrs2_circuit::prelude::Circuit;
297 let config = CuQuantumConfig::default();
298 let mut sim = CuTensorNetSimulator::new(config).expect("Should create simulator");
299 let circuit: Circuit<4> = Circuit::new();
300 sim.build_network(&circuit)
301 .expect("Should build tensor network");
302 assert!(sim.tensor_network.is_some());
303 }
304 #[test]
305 fn test_cutensornet_contraction() {
306 use quantrs2_circuit::prelude::Circuit;
307 let config = CuQuantumConfig::default();
308 let mut sim = CuTensorNetSimulator::new(config).expect("Should create simulator");
309 let circuit: Circuit<3> = Circuit::new();
310 sim.build_network(&circuit)
311 .expect("Should build tensor network");
312 let result = sim.contract(&[0, 1, 2]);
313 #[cfg(all(feature = "cuquantum", not(target_os = "macos")))]
315 {
316 assert!(
317 result.is_err(),
318 "Expected error for unimplemented cuTensorNet"
319 );
320 }
321 #[cfg(any(target_os = "macos", not(feature = "cuquantum")))]
322 {
323 let amplitudes = result.expect("Should contract network");
324 assert_eq!(amplitudes.len(), 8);
325 }
326 }
327 #[test]
328 fn test_cutensornet_expectation_value() {
329 use quantrs2_circuit::prelude::Circuit;
330 let config = CuQuantumConfig::default();
331 let mut sim = CuTensorNetSimulator::new(config).expect("Should create simulator");
332 let circuit: Circuit<2> = Circuit::new();
333 sim.build_network(&circuit)
334 .expect("Should build tensor network");
335 let observable = Observable::PauliZ(vec![0]);
336 let result = sim.expectation_value(&observable);
337 #[cfg(all(feature = "cuquantum", not(target_os = "macos")))]
339 {
340 assert!(
341 result.is_err(),
342 "Expected error for unimplemented cuTensorNet"
343 );
344 }
345 #[cfg(any(target_os = "macos", not(feature = "cuquantum")))]
346 {
347 let exp_val = result.expect("Should compute expectation");
348 assert!((exp_val - 0.5).abs() < 1e-10);
349 }
350 }
351 #[test]
352 fn test_tensor_network_state_creation() {
353 use quantrs2_circuit::prelude::Circuit;
354 let circuit: Circuit<3> = Circuit::new();
355 let network = TensorNetworkState::from_circuit(&circuit).expect("Should create network");
356 assert!(
357 network.num_tensors() >= 3,
358 "Should have at least 3 tensors (one per qubit)"
359 );
360 }
361 #[test]
362 fn test_contraction_algorithms() {
363 use quantrs2_circuit::prelude::Circuit;
364 let config = CuQuantumConfig::default();
365 let mut sim = CuTensorNetSimulator::new(config).expect("Should create simulator");
366 let circuit: Circuit<4> = Circuit::new();
367 sim.build_network(&circuit)
368 .expect("Should build tensor network");
369 let path = sim
370 .find_contraction_order()
371 .expect("Should find contraction order");
372 assert!(!path.contractions.is_empty() || path.total_cost() >= 0.0);
373 }
374 #[test]
375 fn test_device_info_methods() {
376 let info = CudaDeviceInfo {
377 device_id: 0,
378 name: "Test Device".to_string(),
379 total_memory: 80 * 1024 * 1024 * 1024,
380 free_memory: 70 * 1024 * 1024 * 1024,
381 compute_capability: (8, 0),
382 sm_count: 108,
383 max_threads_per_block: 1024,
384 warp_size: 32,
385 has_tensor_cores: true,
386 };
387 let max_qubits = info.max_statevec_qubits();
388 assert!(
389 max_qubits >= 31,
390 "Expected >= 31 qubits for A100, got {}",
391 max_qubits
392 );
393 }
394 #[test]
395 fn test_is_available() {
396 let available = CuStateVecSimulator::is_available();
397 #[cfg(not(feature = "cuquantum"))]
398 assert!(!available);
399 }
400 #[test]
401 fn test_observable_variants() {
402 let obs_z = Observable::PauliZ(vec![0, 1]);
403 let obs_x = Observable::PauliX(vec![0]);
404 let obs_y = Observable::PauliY(vec![1]);
405 let mut hermitian = Array2::zeros((2, 2));
406 hermitian[[0, 0]] = Complex64::new(1.0, 0.0);
407 hermitian[[1, 1]] = Complex64::new(-1.0, 0.0);
408 let obs_h = Observable::Hermitian(hermitian);
409 let obs_sum = Observable::Sum(vec![
410 Observable::PauliZ(vec![0]),
411 Observable::PauliZ(vec![1]),
412 ]);
413 let obs_prod = Observable::Product(vec![
414 Observable::PauliX(vec![0]),
415 Observable::PauliY(vec![1]),
416 ]);
417 assert!(matches!(obs_z, Observable::PauliZ(_)));
418 assert!(matches!(obs_x, Observable::PauliX(_)));
419 assert!(matches!(obs_y, Observable::PauliY(_)));
420 assert!(matches!(obs_h, Observable::Hermitian(_)));
421 assert!(matches!(obs_sum, Observable::Sum(_)));
422 assert!(matches!(obs_prod, Observable::Product(_)));
423 }
424 #[test]
425 fn test_performance_estimator_creation() {
426 let config = CuQuantumConfig::default();
427 let estimator = PerformanceEstimator::with_default_device(config);
428 assert!(estimator.is_ok());
429 }
430 #[test]
431 fn test_performance_estimate_small_circuit() {
432 use quantrs2_circuit::prelude::Circuit;
433 let config = CuQuantumConfig::default();
434 let estimator =
435 PerformanceEstimator::with_default_device(config).expect("Should create estimator");
436 let circuit: Circuit<5> = Circuit::new();
437 let estimate = estimator.estimate(&circuit);
438 assert!(estimate.fits_in_memory);
439 assert_eq!(
440 estimate.recommended_backend,
441 RecommendedBackend::StateVector
442 );
443 assert!(estimate.estimated_memory_bytes > 0);
444 assert!(estimate.estimated_gpu_utilization >= 0.0);
445 assert!(estimate.estimated_gpu_utilization <= 1.0);
446 }
447 #[test]
448 fn test_performance_estimate_memory_calculation() {
449 let config = CuQuantumConfig::default();
450 let estimator =
451 PerformanceEstimator::with_default_device(config).expect("Should create estimator");
452 let device_info = estimator.device_info();
453 let _ = device_info;
454 use quantrs2_circuit::prelude::Circuit;
455 let circuit_10: Circuit<10> = Circuit::new();
456 let estimate_10 = estimator.estimate(&circuit_10);
457 assert_eq!(estimate_10.estimated_memory_bytes, 1024 * 16);
458 let circuit_20: Circuit<20> = Circuit::new();
459 let estimate_20 = estimator.estimate(&circuit_20);
460 assert_eq!(estimate_20.estimated_memory_bytes, 1024 * 1024 * 16);
461 }
462 #[test]
463 fn test_performance_estimate_flops_calculation() {
464 use quantrs2_circuit::prelude::Circuit;
465 let config = CuQuantumConfig::default();
466 let estimator =
467 PerformanceEstimator::with_default_device(config).expect("Should create estimator");
468 let circuit_empty: Circuit<5> = Circuit::new();
469 let estimate_empty = estimator.estimate(&circuit_empty);
470 assert_eq!(estimate_empty.estimated_flops, 0.0);
471 }
472 #[test]
473 fn test_circuit_complexity_analysis() {
474 use quantrs2_circuit::prelude::Circuit;
475 let circuit: Circuit<4> = Circuit::new();
476 let complexity = CircuitComplexity::analyze(&circuit);
477 assert_eq!(complexity.num_qubits, 4);
478 assert_eq!(complexity.num_gates, 0);
479 assert_eq!(complexity.depth, 0);
480 assert_eq!(complexity.entanglement_degree, 0.0);
481 }
482 #[test]
483 fn test_gpu_resource_planner() {
484 use quantrs2_circuit::prelude::Circuit;
485 let device = CudaDeviceInfo {
486 device_id: 0,
487 name: "Test GPU".to_string(),
488 total_memory: 16 * 1024 * 1024 * 1024,
489 free_memory: 12 * 1024 * 1024 * 1024,
490 compute_capability: (8, 6),
491 sm_count: 68,
492 max_threads_per_block: 1024,
493 warp_size: 32,
494 has_tensor_cores: true,
495 };
496 let config = CuQuantumConfig::default();
497 let planner = GpuResourcePlanner::new(vec![device], config);
498 let circuits: Vec<Circuit<4>> = vec![Circuit::new(), Circuit::new(), Circuit::new()];
499 let assignments = planner.plan_batch(&circuits);
500 assert_eq!(assignments.len(), 3);
501 for (device_id, _) in &assignments {
502 assert_eq!(*device_id, 0);
503 }
504 let batch_memory = planner.estimate_batch_memory(&circuits);
505 assert_eq!(batch_memory, 3 * 16 * 16);
506 }
507 #[test]
508 fn test_recommended_backend_enum() {
509 assert_ne!(
510 RecommendedBackend::StateVector,
511 RecommendedBackend::TensorNetwork
512 );
513 assert_ne!(RecommendedBackend::Hybrid, RecommendedBackend::NotFeasible);
514 let sv = format!("{:?}", RecommendedBackend::StateVector);
515 assert!(sv.contains("StateVector"));
516 }
517 #[test]
518 fn test_performance_suggestions() {
519 use quantrs2_circuit::prelude::Circuit;
520 let mut config = CuQuantumConfig::default();
521 config.gate_fusion_level = GateFusionLevel::None;
522 let estimator =
523 PerformanceEstimator::with_default_device(config).expect("Should create estimator");
524 let circuit: Circuit<26> = Circuit::new();
525 let estimate = estimator.estimate(&circuit);
526 let has_fusion_suggestion = estimate
527 .suggestions
528 .iter()
529 .any(|s| s.contains("gate fusion"));
530 assert!(
531 has_fusion_suggestion,
532 "Should suggest gate fusion for 26 qubit circuit"
533 );
534 }
535 #[test]
536 fn test_multi_gpu_planner() {
537 use quantrs2_circuit::prelude::Circuit;
538 let devices = vec![
539 CudaDeviceInfo {
540 device_id: 0,
541 name: "GPU 0".to_string(),
542 total_memory: 16 * 1024 * 1024 * 1024,
543 free_memory: 12 * 1024 * 1024 * 1024,
544 compute_capability: (8, 6),
545 sm_count: 68,
546 max_threads_per_block: 1024,
547 warp_size: 32,
548 has_tensor_cores: true,
549 },
550 CudaDeviceInfo {
551 device_id: 1,
552 name: "GPU 1".to_string(),
553 total_memory: 16 * 1024 * 1024 * 1024,
554 free_memory: 12 * 1024 * 1024 * 1024,
555 compute_capability: (8, 6),
556 sm_count: 68,
557 max_threads_per_block: 1024,
558 warp_size: 32,
559 has_tensor_cores: true,
560 },
561 ];
562 let config = CuQuantumConfig::default();
563 let planner = GpuResourcePlanner::new(devices, config);
564 let circuits: Vec<Circuit<4>> = vec![
565 Circuit::new(),
566 Circuit::new(),
567 Circuit::new(),
568 Circuit::new(),
569 ];
570 let assignments = planner.plan_batch(&circuits);
571 assert_eq!(assignments.len(), 4);
572 assert_eq!(assignments[0].0, 0);
573 assert_eq!(assignments[1].0, 1);
574 assert_eq!(assignments[2].0, 0);
575 assert_eq!(assignments[3].0, 1);
576 }
577}