1use super::{
4 ApplicationError, ApplicationResult, Duration, IsingModel, ProblemType, TestExecutionResult,
5 TestSuiteResults, TestingConfig,
6};
7use scirs2_core::random::prelude::*;
8
9use std::fmt::Write;
10pub fn create_standard_test_config(test_type: &str) -> ApplicationResult<TestingConfig> {
12 match test_type {
13 "performance" => Ok(TestingConfig {
14 enable_parallel: true,
15 max_concurrent_tests: 4,
16 test_timeout: Duration::from_secs(600),
17 performance_tolerance: 0.05,
18 significance_level: 0.05,
19 data_retention: Duration::from_secs(14 * 24 * 3600),
20 detailed_logging: true,
21 stress_test_sizes: vec![50, 100, 200, 500],
22 }),
23 "regression" => Ok(TestingConfig {
24 enable_parallel: false,
25 max_concurrent_tests: 1,
26 test_timeout: Duration::from_secs(300),
27 performance_tolerance: 0.1,
28 significance_level: 0.01,
29 data_retention: Duration::from_secs(60 * 24 * 3600),
30 detailed_logging: true,
31 stress_test_sizes: vec![10, 50, 100],
32 }),
33 "stress" => Ok(TestingConfig {
34 enable_parallel: true,
35 max_concurrent_tests: 8,
36 test_timeout: Duration::from_secs(1200),
37 performance_tolerance: 0.2,
38 significance_level: 0.05,
39 data_retention: Duration::from_secs(30 * 24 * 3600),
40 detailed_logging: false,
41 stress_test_sizes: vec![100, 500, 1000, 2000, 5000, 10_000],
42 }),
43 "property" => Ok(TestingConfig {
44 enable_parallel: true,
45 max_concurrent_tests: 6,
46 test_timeout: Duration::from_secs(180),
47 performance_tolerance: 0.1,
48 significance_level: 0.05,
49 data_retention: Duration::from_secs(21 * 24 * 3600),
50 detailed_logging: true,
51 stress_test_sizes: vec![10, 25, 50, 100],
52 }),
53 _ => Err(ApplicationError::ConfigurationError(format!(
54 "Unknown test type: {test_type}"
55 ))),
56 }
57}
58
59pub fn create_test_problem(
61 problem_type: ProblemType,
62 size: usize,
63 density: f64,
64 seed: Option<u64>,
65) -> ApplicationResult<IsingModel> {
66 let mut problem = IsingModel::new(size);
67
68 let mut rng_seed = seed.unwrap_or_else(|| thread_rng().random());
70
71 match problem_type {
72 ProblemType::RandomIsing => create_random_ising_problem(&mut problem, density, rng_seed)?,
73 ProblemType::MaxCut => create_max_cut_problem(&mut problem, density, rng_seed)?,
74 ProblemType::VertexCover => create_vertex_cover_problem(&mut problem, density, rng_seed)?,
75 ProblemType::TSP => create_tsp_problem(&mut problem, density, rng_seed)?,
76 ProblemType::Portfolio => create_portfolio_problem(&mut problem, density, rng_seed)?,
77 ProblemType::Custom(ref name) => {
78 return Err(ApplicationError::ConfigurationError(format!(
79 "Custom problem type not implemented: {name}"
80 )));
81 }
82 }
83
84 Ok(problem)
85}
86
87fn create_random_ising_problem(
89 problem: &mut IsingModel,
90 density: f64,
91 seed: u64,
92) -> ApplicationResult<()> {
93 let size = problem.num_qubits;
94
95 let mut local_seed = seed;
97
98 for i in 0..size {
100 local_seed = local_seed.wrapping_mul(1_103_515_245).wrapping_add(12_345);
101 let bias = ((local_seed % 2000) as f64 / 1000.0) - 1.0; problem.set_bias(i, bias)?;
103 }
104
105 let max_edges = size * (size - 1) / 2;
107 let target_edges = (max_edges as f64 * density) as usize;
108
109 let mut edges_added = 0;
110 for i in 0..size {
111 for j in (i + 1)..size {
112 if edges_added >= target_edges {
113 break;
114 }
115
116 local_seed = local_seed.wrapping_mul(1_103_515_245).wrapping_add(12_345);
117 if (local_seed % 1000) < (density * 1000.0) as u64 {
118 local_seed = local_seed.wrapping_mul(1_103_515_245).wrapping_add(12_345);
119 let coupling = ((local_seed % 2000) as f64 / 1000.0) - 1.0; problem.set_coupling(i, j, coupling)?;
121 edges_added += 1;
122 }
123 }
124 if edges_added >= target_edges {
125 break;
126 }
127 }
128
129 Ok(())
130}
131
132fn create_max_cut_problem(
134 problem: &mut IsingModel,
135 density: f64,
136 seed: u64,
137) -> ApplicationResult<()> {
138 let size = problem.num_qubits;
139 let mut local_seed = seed;
140
141 for i in 0..size {
143 problem.set_bias(i, 0.0)?;
144 }
145
146 let max_edges = size * (size - 1) / 2;
148 let target_edges = (max_edges as f64 * density) as usize;
149
150 let mut edges_added = 0;
151 for i in 0..size {
152 for j in (i + 1)..size {
153 if edges_added >= target_edges {
154 break;
155 }
156
157 local_seed = local_seed.wrapping_mul(1_103_515_245).wrapping_add(12_345);
158 if (local_seed % 1000) < (density * 1000.0) as u64 {
159 local_seed = local_seed.wrapping_mul(1_103_515_245).wrapping_add(12_345);
160 let weight = (local_seed % 100) as f64 / 100.0; problem.set_coupling(i, j, -weight)?;
163 edges_added += 1;
164 }
165 }
166 if edges_added >= target_edges {
167 break;
168 }
169 }
170
171 Ok(())
172}
173
174fn create_vertex_cover_problem(
176 problem: &mut IsingModel,
177 density: f64,
178 seed: u64,
179) -> ApplicationResult<()> {
180 let size = problem.num_qubits;
181 let mut local_seed = seed;
182
183 for i in 0..size {
186 problem.set_bias(i, 1.0)?; }
188
189 let max_edges = size * (size - 1) / 2;
191 let target_edges = (max_edges as f64 * density) as usize;
192
193 let mut edges_added = 0;
194 for i in 0..size {
195 for j in (i + 1)..size {
196 if edges_added >= target_edges {
197 break;
198 }
199
200 local_seed = local_seed.wrapping_mul(1_103_515_245).wrapping_add(12_345);
201 if (local_seed % 1000) < (density * 1000.0) as u64 {
202 let penalty = 10.0;
204 problem.set_coupling(i, j, penalty)?;
205 edges_added += 1;
206 }
207 }
208 if edges_added >= target_edges {
209 break;
210 }
211 }
212
213 Ok(())
214}
215
216fn create_tsp_problem(problem: &mut IsingModel, _density: f64, seed: u64) -> ApplicationResult<()> {
218 let size = problem.num_qubits;
219 let mut local_seed = seed;
220
221 for i in 0..size {
224 problem.set_bias(i, 0.0)?;
225 }
226
227 for i in 0..size {
229 for j in (i + 1)..size {
230 local_seed = local_seed.wrapping_mul(1_103_515_245).wrapping_add(12_345);
231 let distance = (local_seed % 100) as f64 / 10.0; problem.set_coupling(i, j, distance)?;
233 }
234 }
235
236 Ok(())
237}
238
239fn create_portfolio_problem(
241 problem: &mut IsingModel,
242 _density: f64,
243 seed: u64,
244) -> ApplicationResult<()> {
245 let size = problem.num_qubits;
246 let mut local_seed = seed;
247
248 for i in 0..size {
250 local_seed = local_seed.wrapping_mul(1_103_515_245).wrapping_add(12_345);
251 let expected_return = ((local_seed % 200) as f64 / 1000.0) + 0.05; problem.set_bias(i, -expected_return)?; }
254
255 for i in 0..size {
257 for j in (i + 1)..size {
258 local_seed = local_seed.wrapping_mul(1_103_515_245).wrapping_add(12_345);
259 let correlation = ((local_seed % 100) as f64 / 500.0) - 0.1; problem.set_coupling(i, j, correlation)?;
261 }
262 }
263
264 Ok(())
265}
266
267pub fn validate_framework_config(config: &TestingConfig) -> ApplicationResult<()> {
269 if config.max_concurrent_tests == 0 {
270 return Err(ApplicationError::ConfigurationError(
271 "max_concurrent_tests must be greater than 0".to_string(),
272 ));
273 }
274
275 if config.test_timeout.as_secs() == 0 {
276 return Err(ApplicationError::ConfigurationError(
277 "test_timeout must be greater than 0".to_string(),
278 ));
279 }
280
281 if config.performance_tolerance < 0.0 || config.performance_tolerance > 1.0 {
282 return Err(ApplicationError::ConfigurationError(
283 "performance_tolerance must be between 0.0 and 1.0".to_string(),
284 ));
285 }
286
287 if config.significance_level < 0.0 || config.significance_level > 1.0 {
288 return Err(ApplicationError::ConfigurationError(
289 "significance_level must be between 0.0 and 1.0".to_string(),
290 ));
291 }
292
293 if config.stress_test_sizes.is_empty() {
294 return Err(ApplicationError::ConfigurationError(
295 "stress_test_sizes cannot be empty".to_string(),
296 ));
297 }
298
299 for i in 1..config.stress_test_sizes.len() {
301 if config.stress_test_sizes[i] <= config.stress_test_sizes[i - 1] {
302 return Err(ApplicationError::ConfigurationError(
303 "stress_test_sizes must be in ascending order".to_string(),
304 ));
305 }
306 }
307
308 Ok(())
309}
310
311#[must_use]
313pub fn calculate_test_quality_metrics(results: &[TestExecutionResult]) -> TestQualityMetrics {
314 if results.is_empty() {
315 return TestQualityMetrics {
316 mean_quality: 0.0,
317 std_dev_quality: 0.0,
318 min_quality: 0.0,
319 max_quality: 0.0,
320 median_quality: 0.0,
321 success_rate: 0.0,
322 mean_execution_time: Duration::default(),
323 std_dev_execution_time: Duration::default(),
324 };
325 }
326
327 let qualities: Vec<f64> = results.iter().map(|r| r.solution_quality).collect();
328 let execution_times: Vec<Duration> = results.iter().map(|r| r.execution_time).collect();
329
330 let mean_quality = qualities.iter().sum::<f64>() / qualities.len() as f64;
332 let variance_quality = qualities
333 .iter()
334 .map(|q| (q - mean_quality).powi(2))
335 .sum::<f64>()
336 / (qualities.len() - 1).max(1) as f64;
337 let std_dev_quality = variance_quality.sqrt();
338
339 let mut sorted_qualities = qualities;
340 sorted_qualities.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
341 let median_quality = if sorted_qualities.len() % 2 == 0 {
342 f64::midpoint(
343 sorted_qualities[sorted_qualities.len() / 2 - 1],
344 sorted_qualities[sorted_qualities.len() / 2],
345 )
346 } else {
347 sorted_qualities[sorted_qualities.len() / 2]
348 };
349
350 let min_quality = sorted_qualities[0];
351 let max_quality = sorted_qualities[sorted_qualities.len() - 1];
352
353 let successful_tests = results.iter().filter(|r| r.convergence_achieved).count();
355 let success_rate = successful_tests as f64 / results.len() as f64;
356
357 let total_time: Duration = execution_times.iter().sum();
359 let mean_execution_time = total_time / execution_times.len() as u32;
360
361 let mean_time_secs = mean_execution_time.as_secs_f64();
362 let variance_time = execution_times
363 .iter()
364 .map(|t| (t.as_secs_f64() - mean_time_secs).powi(2))
365 .sum::<f64>()
366 / (execution_times.len() - 1).max(1) as f64;
367 let std_dev_execution_time = Duration::from_secs_f64(variance_time.sqrt());
368
369 TestQualityMetrics {
370 mean_quality,
371 std_dev_quality,
372 min_quality,
373 max_quality,
374 median_quality,
375 success_rate,
376 mean_execution_time,
377 std_dev_execution_time,
378 }
379}
380
381#[must_use]
383pub fn generate_test_summary(results: &TestSuiteResults) -> String {
384 let mut summary = String::new();
385
386 writeln!(summary, "# Test Suite Summary\n").expect("writing to String is infallible");
387 write!(
388 summary,
389 "**Execution Time:** {:?}\n",
390 results.execution_time
391 )
392 .expect("writing to String is infallible");
393 write!(
394 summary,
395 "**Overall Success:** {}\n\n",
396 if results.overall_success {
397 "✅"
398 } else {
399 "❌"
400 }
401 )
402 .expect("writing to String is infallible");
403
404 if !results.scenario_results.is_empty() {
406 let scenario_success = results
407 .scenario_results
408 .iter()
409 .filter(|r| r.success)
410 .count();
411 write!(
412 summary,
413 "## Scenario Tests\n- **Results:** {}/{} passed\n- **Success Rate:** {:.1}%\n\n",
414 scenario_success,
415 results.scenario_results.len(),
416 (scenario_success as f64 / results.scenario_results.len() as f64) * 100.0
417 )
418 .expect("writing to String is infallible");
419 }
420
421 if !results.regression_results.is_empty() {
423 let regressions_detected = results
424 .regression_results
425 .iter()
426 .filter(|r| r.regression_detected)
427 .count();
428 write!(
429 summary,
430 "## Regression Tests\n- **Regressions Detected:** {}\n- **Tests Analyzed:** {}\n\n",
431 regressions_detected,
432 results.regression_results.len()
433 )
434 .expect("writing to String is infallible");
435 }
436
437 if !results.platform_results.is_empty() {
439 let avg_compatibility = results
440 .platform_results
441 .iter()
442 .map(|r| r.compatibility_score)
443 .sum::<f64>()
444 / results.platform_results.len() as f64;
445
446 write!(
447 summary,
448 "## Platform Tests\n- **Platforms Tested:** {}\n- **Average Compatibility:** {:.2}\n\n",
449 results.platform_results.len(),
450 avg_compatibility
451 )
452 .expect("writing to String is infallible");
453 }
454
455 if !results.stress_results.is_empty() {
457 let avg_success_rate = results
458 .stress_results
459 .iter()
460 .map(|r| r.success_rate)
461 .sum::<f64>()
462 / results.stress_results.len() as f64;
463
464 write!(
465 summary,
466 "## Stress Tests\n- **Tests Completed:** {}\n- **Average Success Rate:** {:.1}%\n\n",
467 results.stress_results.len(),
468 avg_success_rate * 100.0
469 )
470 .expect("writing to String is infallible");
471 }
472
473 if !results.property_results.is_empty() {
475 let total_cases = results
476 .property_results
477 .iter()
478 .map(|r| r.cases_tested)
479 .sum::<usize>();
480 let total_passed = results
481 .property_results
482 .iter()
483 .map(|r| r.cases_passed)
484 .sum::<usize>();
485
486 write!(summary, "## Property Tests\n- **Properties Tested:** {}\n- **Test Cases:** {} total, {} passed\n- **Overall Confidence:** {:.1}%\n\n",
487 results.property_results.len(),
488 total_cases,
489 total_passed,
490 if total_cases > 0 { (total_passed as f64 / total_cases as f64) * 100.0 } else { 0.0 })
491 .expect("writing to String is infallible");
492 }
493
494 write!(
495 summary,
496 "---\n*Generated at: {}*\n",
497 std::time::SystemTime::now()
498 .duration_since(std::time::UNIX_EPOCH)
499 .unwrap_or_default()
500 .as_secs()
501 )
502 .expect("writing to String is infallible");
503
504 summary
505}
506
507#[must_use]
509pub fn compare_test_results(
510 result1: &TestExecutionResult,
511 result2: &TestExecutionResult,
512 tolerance: f64,
513) -> TestComparisonResult {
514 let quality_diff = (result1.solution_quality - result2.solution_quality).abs();
515 let quality_similar = quality_diff <= tolerance;
516
517 let time_diff = if result1.execution_time > result2.execution_time {
518 result1
519 .execution_time
520 .checked_sub(result2.execution_time)
521 .unwrap_or_default()
522 } else {
523 result2
524 .execution_time
525 .checked_sub(result1.execution_time)
526 .unwrap_or_default()
527 };
528
529 let energy_diff = (result1.final_energy - result2.final_energy).abs();
530 let energy_similar = energy_diff <= tolerance * result1.final_energy.abs().max(1.0);
531
532 TestComparisonResult {
533 quality_difference: quality_diff,
534 quality_similar,
535 time_difference: time_diff,
536 energy_difference: energy_diff,
537 energy_similar,
538 overall_similar: quality_similar && energy_similar,
539 }
540}
541
542#[derive(Debug, Clone)]
544pub struct TestQualityMetrics {
545 pub mean_quality: f64,
547 pub std_dev_quality: f64,
549 pub min_quality: f64,
551 pub max_quality: f64,
553 pub median_quality: f64,
555 pub success_rate: f64,
557 pub mean_execution_time: Duration,
559 pub std_dev_execution_time: Duration,
561}
562
563#[derive(Debug, Clone)]
565pub struct TestComparisonResult {
566 pub quality_difference: f64,
568 pub quality_similar: bool,
570 pub time_difference: Duration,
572 pub energy_difference: f64,
574 pub energy_similar: bool,
576 pub overall_similar: bool,
578}
579
580#[cfg(test)]
581mod tests {
582 use super::*;
583 use crate::advanced_testing_framework::ScenarioTestResult;
584
585 #[test]
586 fn test_standard_config_creation() {
587 let config = create_standard_test_config("performance")
588 .expect("should create performance test config");
589 assert!(config.enable_parallel);
590 assert_eq!(config.max_concurrent_tests, 4);
591 assert_eq!(config.performance_tolerance, 0.05);
592 }
593
594 #[test]
595 fn test_config_validation() {
596 let mut config = TestingConfig::default();
597 assert!(validate_framework_config(&config).is_ok());
598
599 config.max_concurrent_tests = 0;
600 assert!(validate_framework_config(&config).is_err());
601
602 config.max_concurrent_tests = 4;
603 config.performance_tolerance = 1.5;
604 assert!(validate_framework_config(&config).is_err());
605 }
606
607 #[test]
608 fn test_problem_creation() {
609 let problem = create_test_problem(ProblemType::RandomIsing, 10, 0.3, Some(42));
610 assert!(problem.is_ok());
611
612 let ising = problem.expect("should create random Ising problem");
613 assert_eq!(ising.num_qubits, 10);
614 }
615
616 #[test]
617 fn test_quality_metrics_calculation() {
618 let results = vec![
619 TestExecutionResult {
620 solution_quality: 0.9,
621 execution_time: Duration::from_millis(100),
622 final_energy: -0.9,
623 best_solution: vec![1, -1],
624 convergence_achieved: true,
625 memory_used: 1024,
626 },
627 TestExecutionResult {
628 solution_quality: 0.8,
629 execution_time: Duration::from_millis(150),
630 final_energy: -0.8,
631 best_solution: vec![-1, 1],
632 convergence_achieved: true,
633 memory_used: 1024,
634 },
635 ];
636
637 let metrics = calculate_test_quality_metrics(&results);
638 assert!((metrics.mean_quality - 0.85).abs() < 1e-10);
639 assert_eq!(metrics.success_rate, 1.0);
640 assert_eq!(metrics.min_quality, 0.8);
641 assert_eq!(metrics.max_quality, 0.9);
642 }
643
644 #[test]
645 fn test_test_comparison() {
646 let result1 = TestExecutionResult {
647 solution_quality: 0.9,
648 execution_time: Duration::from_millis(100),
649 final_energy: -0.9,
650 best_solution: vec![1],
651 convergence_achieved: true,
652 memory_used: 1024,
653 };
654
655 let result2 = TestExecutionResult {
656 solution_quality: 0.92,
657 execution_time: Duration::from_millis(110),
658 final_energy: -0.91,
659 best_solution: vec![1],
660 convergence_achieved: true,
661 memory_used: 1024,
662 };
663
664 let comparison = compare_test_results(&result1, &result2, 0.05);
665 assert!(comparison.quality_similar);
666 assert!(comparison.energy_similar);
667 assert!(comparison.overall_similar);
668 }
669
670 #[test]
671 fn test_max_cut_problem_creation() {
672 let problem = create_test_problem(ProblemType::MaxCut, 5, 0.5, Some(123));
673 assert!(problem.is_ok());
674
675 let max_cut = problem.expect("should create Max-Cut problem");
676 assert_eq!(max_cut.num_qubits, 5);
677
678 for i in 0..5 {
680 assert_eq!(max_cut.get_bias(i).expect("should get bias for qubit"), 0.0);
681 }
682 }
683
684 #[test]
685 fn test_test_summary_generation() {
686 let results = TestSuiteResults {
687 scenario_results: vec![ScenarioTestResult {
688 scenario_id: "test1".to_string(),
689 execution_time: Duration::from_millis(100),
690 test_result: TestExecutionResult {
691 solution_quality: 0.9,
692 execution_time: Duration::from_millis(100),
693 final_energy: -0.9,
694 best_solution: vec![1],
695 convergence_achieved: true,
696 memory_used: 1024,
697 },
698 validation_results: Vec::new(),
699 success: true,
700 }],
701 regression_results: Vec::new(),
702 platform_results: Vec::new(),
703 stress_results: Vec::new(),
704 property_results: Vec::new(),
705 execution_time: Duration::from_secs(1),
706 overall_success: true,
707 };
708
709 let summary = generate_test_summary(&results);
710 assert!(summary.contains("Test Suite Summary"));
711 assert!(summary.contains("1/1 passed"));
712 assert!(summary.contains("✅"));
713 }
714}