quantrs2_tytan/
testing_framework.rs

1//! Automated testing framework for quantum optimization.
2//!
3//! This module provides comprehensive testing tools for QUBO problems,
4//! including test case generation, validation, and benchmarking.
5
6#![allow(dead_code)]
7
8#[cfg(feature = "dwave")]
9use crate::compile::{Compile, CompiledModel};
10use crate::sampler::Sampler;
11use scirs2_core::ndarray::Array2;
12use scirs2_core::random::prelude::*;
13use scirs2_core::random::SeedableRng;
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16use std::fs::File;
17use std::io::Write;
18use std::time::{Duration, Instant};
19
20/// Automated testing framework
21pub struct TestingFramework {
22    /// Test configuration
23    config: TestConfig,
24    /// Test suite
25    suite: TestSuite,
26    /// Test results
27    results: TestResults,
28    /// Validators
29    validators: Vec<Box<dyn Validator>>,
30    /// Generators
31    generators: Vec<Box<dyn TestGenerator>>,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct TestConfig {
36    /// Random seed
37    pub seed: Option<u64>,
38    /// Number of test cases per category
39    pub cases_per_category: usize,
40    /// Problem sizes to test
41    pub problem_sizes: Vec<usize>,
42    /// Samplers to test
43    pub samplers: Vec<SamplerConfig>,
44    /// Timeout per test
45    pub timeout: Duration,
46    /// Validation settings
47    pub validation: ValidationConfig,
48    /// Output settings
49    pub output: OutputConfig,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct SamplerConfig {
54    /// Sampler name
55    pub name: String,
56    /// Number of samples
57    pub num_samples: usize,
58    /// Additional parameters
59    pub parameters: HashMap<String, f64>,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct ValidationConfig {
64    /// Check constraint satisfaction
65    pub check_constraints: bool,
66    /// Check objective improvement
67    pub check_objective: bool,
68    /// Statistical validation
69    pub statistical_tests: bool,
70    /// Tolerance for floating point comparisons
71    pub tolerance: f64,
72    /// Minimum solution quality
73    pub min_quality: f64,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct OutputConfig {
78    /// Generate report
79    pub generate_report: bool,
80    /// Report format
81    pub format: ReportFormat,
82    /// Output directory
83    pub output_dir: String,
84    /// Verbosity level
85    pub verbosity: VerbosityLevel,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub enum ReportFormat {
90    /// Plain text
91    Text,
92    /// JSON
93    Json,
94    /// HTML
95    Html,
96    /// Markdown
97    Markdown,
98    /// CSV
99    Csv,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub enum VerbosityLevel {
104    /// Only errors
105    Error,
106    /// Warnings and errors
107    Warning,
108    /// Info messages
109    Info,
110    /// Debug information
111    Debug,
112}
113
114/// Test suite
115#[derive(Debug, Clone)]
116pub struct TestSuite {
117    /// Test categories
118    pub categories: Vec<TestCategory>,
119    /// Individual test cases
120    pub test_cases: Vec<TestCase>,
121    /// Benchmarks
122    pub benchmarks: Vec<Benchmark>,
123}
124
125#[derive(Debug, Clone)]
126pub struct TestCategory {
127    /// Category name
128    pub name: String,
129    /// Description
130    pub description: String,
131    /// Problem types
132    pub problem_types: Vec<ProblemType>,
133    /// Difficulty levels
134    pub difficulties: Vec<Difficulty>,
135    /// Tags
136    pub tags: Vec<String>,
137}
138
139#[derive(Debug, Clone, PartialEq, Eq)]
140pub enum ProblemType {
141    /// Max-cut problem
142    MaxCut,
143    /// Traveling salesman
144    TSP,
145    /// Graph coloring
146    GraphColoring,
147    /// Number partitioning
148    NumberPartitioning,
149    /// Knapsack
150    Knapsack,
151    /// Set cover
152    SetCover,
153    /// Vehicle routing
154    VRP,
155    /// Job scheduling
156    JobScheduling,
157    /// Portfolio optimization
158    Portfolio,
159    /// Ising model
160    Ising,
161    /// Custom problem
162    Custom { name: String },
163}
164
165#[derive(Debug, Clone)]
166pub enum Difficulty {
167    /// Easy problems
168    Easy,
169    /// Medium difficulty
170    Medium,
171    /// Hard problems
172    Hard,
173    /// Very hard (NP-hard instances)
174    VeryHard,
175    /// Stress test
176    Extreme,
177}
178
179#[derive(Debug, Clone)]
180pub struct TestCase {
181    /// Test ID
182    pub id: String,
183    /// Problem type
184    pub problem_type: ProblemType,
185    /// Problem size
186    pub size: usize,
187    /// QUBO matrix
188    pub qubo: Array2<f64>,
189    /// Variable mapping
190    pub var_map: HashMap<String, usize>,
191    /// Known optimal solution (if available)
192    pub optimal_solution: Option<HashMap<String, bool>>,
193    /// Optimal value
194    pub optimal_value: Option<f64>,
195    /// Constraints
196    pub constraints: Vec<Constraint>,
197    /// Metadata
198    pub metadata: TestMetadata,
199}
200
201#[derive(Debug, Clone)]
202pub struct TestMetadata {
203    /// Generation method
204    pub generation_method: String,
205    /// Difficulty estimate
206    pub difficulty: Difficulty,
207    /// Expected runtime
208    pub expected_runtime: Duration,
209    /// Notes
210    pub notes: String,
211    /// Tags
212    pub tags: Vec<String>,
213}
214
215#[derive(Debug, Clone)]
216pub struct Constraint {
217    /// Constraint type
218    pub constraint_type: ConstraintType,
219    /// Variables involved
220    pub variables: Vec<String>,
221    /// Parameters
222    pub parameters: HashMap<String, f64>,
223    /// Penalty weight
224    pub penalty: f64,
225}
226
227#[derive(Debug, Clone)]
228pub enum ConstraintType {
229    /// Linear equality
230    LinearEquality { target: f64 },
231    /// Linear inequality
232    LinearInequality { bound: f64, is_upper: bool },
233    /// One-hot encoding
234    OneHot,
235    /// At most k
236    AtMostK { k: usize },
237    /// At least k
238    AtLeastK { k: usize },
239    /// Exactly k
240    ExactlyK { k: usize },
241    /// Custom constraint
242    Custom { name: String },
243}
244
245#[derive(Debug, Clone)]
246pub struct Benchmark {
247    /// Benchmark name
248    pub name: String,
249    /// Test cases
250    pub test_cases: Vec<String>,
251    /// Performance metrics to collect
252    pub metrics: Vec<PerformanceMetric>,
253    /// Baseline results
254    pub baseline: Option<BenchmarkResults>,
255}
256
257#[derive(Debug, Clone)]
258pub enum PerformanceMetric {
259    /// Solving time
260    SolveTime,
261    /// Solution quality
262    SolutionQuality,
263    /// Constraint violations
264    ConstraintViolations,
265    /// Memory usage
266    MemoryUsage,
267    /// Convergence rate
268    ConvergenceRate,
269    /// Sample efficiency
270    SampleEfficiency,
271}
272
273/// Test results
274#[derive(Debug, Clone)]
275pub struct TestResults {
276    /// Individual test results
277    pub test_results: Vec<TestResult>,
278    /// Summary statistics
279    pub summary: TestSummary,
280    /// Failures
281    pub failures: Vec<TestFailure>,
282    /// Performance data
283    pub performance: PerformanceData,
284}
285
286#[derive(Debug, Clone)]
287pub struct TestResult {
288    /// Test case ID
289    pub test_id: String,
290    /// Sampler used
291    pub sampler: String,
292    /// Solution found
293    pub solution: HashMap<String, bool>,
294    /// Objective value
295    pub objective_value: f64,
296    /// Constraints satisfied
297    pub constraints_satisfied: bool,
298    /// Validation results
299    pub validation: ValidationResult,
300    /// Runtime
301    pub runtime: Duration,
302    /// Additional metrics
303    pub metrics: HashMap<String, f64>,
304}
305
306#[derive(Debug, Clone)]
307pub struct ValidationResult {
308    /// Overall valid
309    pub is_valid: bool,
310    /// Validation checks
311    pub checks: Vec<ValidationCheck>,
312    /// Warnings
313    pub warnings: Vec<String>,
314}
315
316#[derive(Debug, Clone)]
317pub struct ValidationCheck {
318    /// Check name
319    pub name: String,
320    /// Passed
321    pub passed: bool,
322    /// Message
323    pub message: String,
324    /// Details
325    pub details: Option<String>,
326}
327
328#[derive(Debug, Clone)]
329pub struct TestFailure {
330    /// Test ID
331    pub test_id: String,
332    /// Failure type
333    pub failure_type: FailureType,
334    /// Error message
335    pub message: String,
336    /// Stack trace (if available)
337    pub stack_trace: Option<String>,
338    /// Context
339    pub context: HashMap<String, String>,
340}
341
342#[derive(Debug, Clone)]
343pub enum FailureType {
344    /// Timeout
345    Timeout,
346    /// Constraint violation
347    ConstraintViolation,
348    /// Invalid solution
349    InvalidSolution,
350    /// Sampler error
351    SamplerError,
352    /// Validation error
353    ValidationError,
354    /// Unexpected error
355    UnexpectedError,
356}
357
358#[derive(Debug, Clone)]
359pub struct TestSummary {
360    /// Total tests run
361    pub total_tests: usize,
362    /// Passed tests
363    pub passed: usize,
364    /// Failed tests
365    pub failed: usize,
366    /// Skipped tests
367    pub skipped: usize,
368    /// Average runtime
369    pub avg_runtime: Duration,
370    /// Success rate
371    pub success_rate: f64,
372    /// Quality metrics
373    pub quality_metrics: QualityMetrics,
374}
375
376#[derive(Debug, Clone)]
377pub struct QualityMetrics {
378    /// Average solution quality
379    pub avg_quality: f64,
380    /// Best solution quality
381    pub best_quality: f64,
382    /// Worst solution quality
383    pub worst_quality: f64,
384    /// Standard deviation
385    pub std_dev: f64,
386    /// Constraint satisfaction rate
387    pub constraint_satisfaction_rate: f64,
388}
389
390#[derive(Debug, Clone)]
391pub struct PerformanceData {
392    /// Runtime statistics
393    pub runtime_stats: RuntimeStats,
394    /// Memory statistics
395    pub memory_stats: MemoryStats,
396    /// Convergence data
397    pub convergence_data: ConvergenceData,
398}
399
400#[derive(Debug, Clone)]
401pub struct RuntimeStats {
402    /// Total runtime
403    pub total_time: Duration,
404    /// QUBO generation time
405    pub qubo_generation_time: Duration,
406    /// Solving time
407    pub solving_time: Duration,
408    /// Validation time
409    pub validation_time: Duration,
410    /// Time per test
411    pub time_per_test: Vec<(String, Duration)>,
412}
413
414#[derive(Debug, Clone)]
415pub struct MemoryStats {
416    /// Peak memory usage
417    pub peak_memory: usize,
418    /// Average memory usage
419    pub avg_memory: usize,
420    /// Memory per test
421    pub memory_per_test: Vec<(String, usize)>,
422}
423
424#[derive(Debug, Clone)]
425pub struct ConvergenceData {
426    /// Convergence curves
427    pub curves: Vec<ConvergenceCurve>,
428    /// Average iterations to convergence
429    pub avg_iterations: f64,
430    /// Convergence rate
431    pub convergence_rate: f64,
432}
433
434#[derive(Debug, Clone)]
435pub struct ConvergenceCurve {
436    /// Test ID
437    pub test_id: String,
438    /// Iteration data
439    pub iterations: Vec<IterationData>,
440    /// Converged
441    pub converged: bool,
442}
443
444#[derive(Debug, Clone)]
445pub struct IterationData {
446    /// Iteration number
447    pub iteration: usize,
448    /// Best objective value
449    pub best_value: f64,
450    /// Current value
451    pub current_value: f64,
452    /// Temperature (if applicable)
453    pub temperature: Option<f64>,
454}
455
456#[derive(Debug, Clone)]
457pub struct BenchmarkResults {
458    /// Benchmark name
459    pub name: String,
460    /// Results per metric
461    pub metrics: HashMap<String, f64>,
462    /// Timestamp
463    pub timestamp: std::time::SystemTime,
464}
465
466/// Regression testing report
467#[derive(Debug, Clone)]
468pub struct RegressionReport {
469    /// Performance regressions detected
470    pub regressions: Vec<RegressionIssue>,
471    /// Performance improvements detected
472    pub improvements: Vec<RegressionIssue>,
473    /// Number of baseline tests
474    pub baseline_tests: usize,
475    /// Number of current tests
476    pub current_tests: usize,
477}
478
479/// Individual regression issue
480#[derive(Debug, Clone)]
481pub struct RegressionIssue {
482    /// Test ID
483    pub test_id: String,
484    /// Metric that regressed (quality, runtime, etc.)
485    pub metric: String,
486    /// Baseline value
487    pub baseline_value: f64,
488    /// Current value
489    pub current_value: f64,
490    /// Percentage change
491    pub change_percent: f64,
492}
493
494/// CI/CD integration report
495#[derive(Debug, Clone)]
496pub struct CIReport {
497    /// Overall CI status
498    pub status: CIStatus,
499    /// Test pass rate
500    pub passed_rate: f64,
501    /// Total number of tests
502    pub total_tests: usize,
503    /// Number of failed tests
504    pub failed_tests: usize,
505    /// Number of critical failures
506    pub critical_failures: usize,
507    /// Average runtime
508    pub avg_runtime: Duration,
509    /// Overall quality score (0-100)
510    pub quality_score: f64,
511}
512
513/// CI status enumeration
514#[derive(Debug, Clone)]
515pub enum CIStatus {
516    /// All tests passed with good performance
517    Pass,
518    /// Tests passed but with warnings
519    Warning,
520    /// Critical failures detected
521    Fail,
522}
523
524/// Extended performance metrics
525#[derive(Debug, Clone)]
526pub struct ExtendedPerformanceMetrics {
527    /// CPU utilization percentage
528    pub cpu_utilization: f64,
529    /// Memory utilization (MB)
530    pub memory_usage: f64,
531    /// GPU utilization (if applicable)
532    pub gpu_utilization: Option<f64>,
533    /// Energy consumption (Joules)
534    pub energy_consumption: f64,
535    /// Iterations per second
536    pub iterations_per_second: f64,
537    /// Solution quality trend
538    pub quality_trend: QualityTrend,
539}
540
541/// Quality trend analysis
542#[derive(Debug, Clone)]
543pub enum QualityTrend {
544    /// Quality improving over time
545    Improving,
546    /// Quality stable
547    Stable,
548    /// Quality degrading
549    Degrading,
550    /// Insufficient data
551    Unknown,
552}
553
554/// Test execution environment
555#[derive(Debug, Clone)]
556pub struct TestEnvironment {
557    /// Operating system
558    pub os: String,
559    /// CPU model
560    pub cpu_model: String,
561    /// Available memory (GB)
562    pub memory_gb: f64,
563    /// GPU information (if available)
564    pub gpu_info: Option<String>,
565    /// Rust version
566    pub rust_version: String,
567    /// Compilation flags
568    pub compile_flags: Vec<String>,
569}
570
571/// Sampler comparison results
572#[derive(Debug, Clone)]
573pub struct SamplerComparison {
574    /// First sampler name
575    pub sampler1_name: String,
576    /// Second sampler name
577    pub sampler2_name: String,
578    /// Individual test comparisons
579    pub test_comparisons: Vec<TestComparison>,
580    /// Average quality improvement (sampler2 vs sampler1)
581    pub avg_quality_improvement: f64,
582    /// Average runtime ratio (sampler2 / sampler1)
583    pub avg_runtime_ratio: f64,
584    /// Overall winner
585    pub winner: String,
586}
587
588/// Individual test comparison
589#[derive(Debug, Clone)]
590pub struct TestComparison {
591    /// Test ID
592    pub test_id: String,
593    /// First sampler quality
594    pub sampler1_quality: f64,
595    /// Second sampler quality
596    pub sampler2_quality: f64,
597    /// Quality improvement (positive means sampler2 is better)
598    pub quality_improvement: f64,
599    /// First sampler runtime
600    pub sampler1_runtime: Duration,
601    /// Second sampler runtime
602    pub sampler2_runtime: Duration,
603    /// Runtime ratio (sampler2 / sampler1)
604    pub runtime_ratio: f64,
605}
606
607/// Test generator trait
608pub trait TestGenerator: Send + Sync {
609    /// Generate test cases
610    fn generate(&self, config: &GeneratorConfig) -> Result<Vec<TestCase>, String>;
611
612    /// Generator name
613    fn name(&self) -> &str;
614
615    /// Supported problem types
616    fn supported_types(&self) -> Vec<ProblemType>;
617}
618
619#[derive(Debug, Clone)]
620pub struct GeneratorConfig {
621    /// Problem type
622    pub problem_type: ProblemType,
623    /// Problem size
624    pub size: usize,
625    /// Difficulty
626    pub difficulty: Difficulty,
627    /// Random seed
628    pub seed: Option<u64>,
629    /// Additional parameters
630    pub parameters: HashMap<String, f64>,
631}
632
633/// Validator trait
634pub trait Validator: Send + Sync {
635    /// Validate test result
636    fn validate(&self, test_case: &TestCase, result: &TestResult) -> ValidationResult;
637
638    /// Validator name
639    fn name(&self) -> &str;
640}
641
642impl TestingFramework {
643    /// Run regression tests against baseline
644    pub fn run_regression_tests<S: Sampler>(
645        &mut self,
646        sampler: &S,
647        baseline_file: &str,
648    ) -> Result<RegressionReport, String> {
649        // Load baseline results
650        let baseline = self.load_baseline(baseline_file)?;
651
652        // Run current tests
653        self.run_suite(sampler)?;
654
655        // Compare with baseline
656        let mut regressions = Vec::new();
657        let mut improvements = Vec::new();
658
659        for current_result in &self.results.test_results {
660            if let Some(baseline_result) = baseline
661                .iter()
662                .find(|b| b.test_id == current_result.test_id)
663            {
664                let quality_change = (current_result.objective_value
665                    - baseline_result.objective_value)
666                    / baseline_result.objective_value.abs();
667                let runtime_change = (current_result.runtime.as_secs_f64()
668                    - baseline_result.runtime.as_secs_f64())
669                    / baseline_result.runtime.as_secs_f64();
670
671                if quality_change > 0.05 || runtime_change > 0.2 {
672                    regressions.push(RegressionIssue {
673                        test_id: current_result.test_id.clone(),
674                        metric: if quality_change > 0.05 {
675                            "quality".to_string()
676                        } else {
677                            "runtime".to_string()
678                        },
679                        baseline_value: if quality_change > 0.05 {
680                            baseline_result.objective_value
681                        } else {
682                            baseline_result.runtime.as_secs_f64()
683                        },
684                        current_value: if quality_change > 0.05 {
685                            current_result.objective_value
686                        } else {
687                            current_result.runtime.as_secs_f64()
688                        },
689                        change_percent: if quality_change > 0.05 {
690                            quality_change * 100.0
691                        } else {
692                            runtime_change * 100.0
693                        },
694                    });
695                } else if quality_change < -0.05 || runtime_change < -0.2 {
696                    improvements.push(RegressionIssue {
697                        test_id: current_result.test_id.clone(),
698                        metric: if quality_change < -0.05 {
699                            "quality".to_string()
700                        } else {
701                            "runtime".to_string()
702                        },
703                        baseline_value: if quality_change < -0.05 {
704                            baseline_result.objective_value
705                        } else {
706                            baseline_result.runtime.as_secs_f64()
707                        },
708                        current_value: if quality_change < -0.05 {
709                            current_result.objective_value
710                        } else {
711                            current_result.runtime.as_secs_f64()
712                        },
713                        change_percent: if quality_change < -0.05 {
714                            quality_change * 100.0
715                        } else {
716                            runtime_change * 100.0
717                        },
718                    });
719                }
720            }
721        }
722
723        Ok(RegressionReport {
724            regressions,
725            improvements,
726            baseline_tests: baseline.len(),
727            current_tests: self.results.test_results.len(),
728        })
729    }
730
731    /// Load baseline results from file
732    const fn load_baseline(&self, _filename: &str) -> Result<Vec<TestResult>, String> {
733        // Simplified implementation - in practice would load from JSON/CSV
734        Ok(Vec::new())
735    }
736
737    /// Run test suite in parallel
738    pub fn run_suite_parallel<S: Sampler + Clone + Send + Sync + 'static>(
739        &mut self,
740        sampler: &S,
741        num_threads: usize,
742    ) -> Result<(), String> {
743        use std::sync::{Arc, Mutex};
744        use std::thread;
745
746        let test_cases = Arc::new(self.suite.test_cases.clone());
747        let results = Arc::new(Mutex::new(Vec::new()));
748        let failures = Arc::new(Mutex::new(Vec::new()));
749
750        let total_start = Instant::now();
751        let chunk_size = test_cases.len().div_ceil(num_threads);
752
753        let mut handles = Vec::new();
754
755        for thread_id in 0..num_threads {
756            let start_idx = thread_id * chunk_size;
757            let end_idx = ((thread_id + 1) * chunk_size).min(test_cases.len());
758
759            if start_idx >= test_cases.len() {
760                break;
761            }
762
763            let test_cases_clone = Arc::clone(&test_cases);
764            let results_clone = Arc::clone(&results);
765            let failures_clone = Arc::clone(&failures);
766            let sampler_clone = sampler.clone();
767
768            let handle = thread::spawn(move || {
769                for idx in start_idx..end_idx {
770                    let test_case = &test_cases_clone[idx];
771
772                    match Self::run_single_test_static(test_case, &sampler_clone) {
773                        Ok(result) => {
774                            if let Ok(mut guard) = results_clone.lock() {
775                                guard.push(result);
776                            }
777                        }
778                        Err(e) => {
779                            if let Ok(mut guard) = failures_clone.lock() {
780                                guard.push(TestFailure {
781                                    test_id: test_case.id.clone(),
782                                    failure_type: FailureType::SamplerError,
783                                    message: e,
784                                    stack_trace: None,
785                                    context: HashMap::new(),
786                                });
787                            }
788                        }
789                    }
790                }
791            });
792
793            handles.push(handle);
794        }
795
796        // Wait for all threads to complete
797        for handle in handles {
798            handle.join().map_err(|_| "Thread panic")?;
799        }
800
801        // Collect results
802        self.results.test_results = results
803            .lock()
804            .map(|guard| guard.clone())
805            .unwrap_or_default();
806        self.results.failures = failures
807            .lock()
808            .map(|guard| guard.clone())
809            .unwrap_or_default();
810
811        self.results.performance.runtime_stats.total_time = total_start.elapsed();
812        self.results.summary.passed = self.results.test_results.len();
813        self.results.summary.failed = self.results.failures.len();
814        self.results.summary.total_tests =
815            self.results.summary.passed + self.results.summary.failed;
816
817        self.calculate_summary();
818
819        Ok(())
820    }
821
822    /// Static version of run_single_test for parallel execution
823    fn run_single_test_static<S: Sampler>(
824        test_case: &TestCase,
825        sampler: &S,
826    ) -> Result<TestResult, String> {
827        let solve_start = Instant::now();
828
829        // Run sampler
830        let sample_result = sampler
831            .run_qubo(&(test_case.qubo.clone(), test_case.var_map.clone()), 100)
832            .map_err(|e| format!("Sampler error: {e:?}"))?;
833
834        let solve_time = solve_start.elapsed();
835
836        // Get best solution
837        let best_sample = sample_result
838            .iter()
839            .min_by(|a, b| {
840                a.energy
841                    .partial_cmp(&b.energy)
842                    .unwrap_or(std::cmp::Ordering::Equal)
843            })
844            .ok_or("No samples returned")?;
845
846        let solution = best_sample.assignments.clone();
847
848        Ok(TestResult {
849            test_id: test_case.id.clone(),
850            sampler: "parallel".to_string(),
851            solution,
852            objective_value: best_sample.energy,
853            constraints_satisfied: true,
854            validation: ValidationResult {
855                is_valid: true,
856                checks: Vec::new(),
857                warnings: Vec::new(),
858            },
859            runtime: solve_time,
860            metrics: HashMap::new(),
861        })
862    }
863
864    /// Generate CI/CD report
865    pub fn generate_ci_report(&self) -> Result<CIReport, String> {
866        let passed_rate = if self.results.summary.total_tests > 0 {
867            self.results.summary.passed as f64 / self.results.summary.total_tests as f64
868        } else {
869            0.0
870        };
871
872        let status = if passed_rate >= 0.95 {
873            CIStatus::Pass
874        } else if passed_rate >= 0.8 {
875            CIStatus::Warning
876        } else {
877            CIStatus::Fail
878        };
879
880        Ok(CIReport {
881            status,
882            passed_rate,
883            total_tests: self.results.summary.total_tests,
884            failed_tests: self.results.summary.failed,
885            critical_failures: self
886                .results
887                .failures
888                .iter()
889                .filter(|f| {
890                    matches!(
891                        f.failure_type,
892                        FailureType::Timeout | FailureType::SamplerError
893                    )
894                })
895                .count(),
896            avg_runtime: self.results.summary.avg_runtime,
897            quality_score: self.calculate_quality_score(),
898        })
899    }
900
901    /// Calculate overall quality score
902    fn calculate_quality_score(&self) -> f64 {
903        if self.results.test_results.is_empty() {
904            return 0.0;
905        }
906
907        let constraint_score = self
908            .results
909            .summary
910            .quality_metrics
911            .constraint_satisfaction_rate;
912        let success_score = self.results.summary.success_rate;
913        let quality_score = if self
914            .results
915            .summary
916            .quality_metrics
917            .best_quality
918            .is_finite()
919        {
920            0.8 // Base score for having finite solutions
921        } else {
922            0.0
923        };
924
925        (constraint_score.mul_add(0.4, success_score * 0.4) + quality_score * 0.2) * 100.0
926    }
927
928    /// Add stress test cases
929    pub fn add_stress_tests(&mut self) {
930        let stress_categories = vec![
931            TestCategory {
932                name: "Large Scale Tests".to_string(),
933                description: "Tests with large problem sizes".to_string(),
934                problem_types: vec![ProblemType::MaxCut, ProblemType::TSP],
935                difficulties: vec![Difficulty::Extreme],
936                tags: vec!["stress".to_string(), "large".to_string()],
937            },
938            TestCategory {
939                name: "Memory Stress Tests".to_string(),
940                description: "Tests designed to stress memory usage".to_string(),
941                problem_types: vec![ProblemType::Knapsack],
942                difficulties: vec![Difficulty::VeryHard, Difficulty::Extreme],
943                tags: vec!["stress".to_string(), "memory".to_string()],
944            },
945            TestCategory {
946                name: "Runtime Stress Tests".to_string(),
947                description: "Tests with challenging runtime requirements".to_string(),
948                problem_types: vec![ProblemType::GraphColoring],
949                difficulties: vec![Difficulty::Extreme],
950                tags: vec!["stress".to_string(), "runtime".to_string()],
951            },
952        ];
953
954        for category in stress_categories {
955            self.suite.categories.push(category);
956        }
957    }
958
959    /// Detect test environment
960    pub fn detect_environment(&self) -> TestEnvironment {
961        TestEnvironment {
962            os: std::env::consts::OS.to_string(),
963            cpu_model: "Unknown".to_string(), // Would need OS-specific detection
964            memory_gb: 8.0,                   // Simplified - would need system detection
965            gpu_info: None,
966            rust_version: std::env::var("RUSTC_VERSION").unwrap_or_else(|_| "unknown".to_string()),
967            compile_flags: vec!["--release".to_string()],
968        }
969    }
970
971    /// Export test results for external analysis
972    pub fn export_results(&self, format: &str) -> Result<String, String> {
973        match format {
974            "csv" => self.export_csv(),
975            "json" => self.generate_json_report(),
976            "xml" => self.export_xml(),
977            _ => Err(format!("Unsupported export format: {format}")),
978        }
979    }
980
981    /// Export results as CSV
982    fn export_csv(&self) -> Result<String, String> {
983        let mut csv = String::new();
984        csv.push_str("test_id,problem_type,size,sampler,objective_value,runtime_ms,constraints_satisfied,valid\n");
985
986        for result in &self.results.test_results {
987            // Find corresponding test case for additional info
988            if let Some(test_case) = self
989                .suite
990                .test_cases
991                .iter()
992                .find(|tc| tc.id == result.test_id)
993            {
994                csv.push_str(&format!(
995                    "{},{:?},{},{},{},{},{},{}\n",
996                    result.test_id,
997                    test_case.problem_type,
998                    test_case.size,
999                    result.sampler,
1000                    result.objective_value,
1001                    result.runtime.as_millis(),
1002                    result.constraints_satisfied,
1003                    result.validation.is_valid
1004                ));
1005            }
1006        }
1007
1008        Ok(csv)
1009    }
1010
1011    /// Export results as XML
1012    fn export_xml(&self) -> Result<String, String> {
1013        let mut xml = String::new();
1014        xml.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
1015        xml.push_str("<test_results>\n");
1016        xml.push_str(&format!(
1017            "  <summary total=\"{}\" passed=\"{}\" failed=\"{}\" success_rate=\"{:.2}\"/>\n",
1018            self.results.summary.total_tests,
1019            self.results.summary.passed,
1020            self.results.summary.failed,
1021            self.results.summary.success_rate
1022        ));
1023
1024        xml.push_str("  <tests>\n");
1025        for result in &self.results.test_results {
1026            xml.push_str(&format!(
1027                "    <test id=\"{}\" sampler=\"{}\" objective=\"{}\" runtime_ms=\"{}\" valid=\"{}\"/>\n",
1028                result.test_id,
1029                result.sampler,
1030                result.objective_value,
1031                result.runtime.as_millis(),
1032                result.validation.is_valid
1033            ));
1034        }
1035        xml.push_str("  </tests>\n");
1036        xml.push_str("</test_results>\n");
1037
1038        Ok(xml)
1039    }
1040
1041    /// Add industry-specific test generators
1042    pub fn add_industry_generators(&mut self) {
1043        // Add finance test generator
1044        self.generators.push(Box::new(FinanceTestGenerator));
1045
1046        // Add logistics test generator
1047        self.generators.push(Box::new(LogisticsTestGenerator));
1048
1049        // Add manufacturing test generator
1050        self.generators.push(Box::new(ManufacturingTestGenerator));
1051    }
1052
1053    /// Generate performance comparison report
1054    pub fn compare_samplers<S1: Sampler, S2: Sampler>(
1055        &mut self,
1056        sampler1: &S1,
1057        sampler2: &S2,
1058        sampler1_name: &str,
1059        sampler2_name: &str,
1060    ) -> Result<SamplerComparison, String> {
1061        // Run tests with first sampler
1062        self.run_suite(sampler1)?;
1063        let results1 = self.results.test_results.clone();
1064
1065        // Clear results and run with second sampler
1066        self.results.test_results.clear();
1067        self.run_suite(sampler2)?;
1068        let results2 = self.results.test_results.clone();
1069
1070        // Compare results
1071        let mut comparisons = Vec::new();
1072
1073        for r1 in &results1 {
1074            if let Some(r2) = results2.iter().find(|r| r.test_id == r1.test_id) {
1075                let quality_diff = r2.objective_value - r1.objective_value;
1076                let runtime_ratio = r2.runtime.as_secs_f64() / r1.runtime.as_secs_f64();
1077
1078                comparisons.push(TestComparison {
1079                    test_id: r1.test_id.clone(),
1080                    sampler1_quality: r1.objective_value,
1081                    sampler2_quality: r2.objective_value,
1082                    quality_improvement: -quality_diff, // Negative because lower is better
1083                    sampler1_runtime: r1.runtime,
1084                    sampler2_runtime: r2.runtime,
1085                    runtime_ratio,
1086                });
1087            }
1088        }
1089
1090        let avg_quality_improvement = comparisons
1091            .iter()
1092            .map(|c| c.quality_improvement)
1093            .sum::<f64>()
1094            / comparisons.len() as f64;
1095        let avg_runtime_ratio =
1096            comparisons.iter().map(|c| c.runtime_ratio).sum::<f64>() / comparisons.len() as f64;
1097
1098        Ok(SamplerComparison {
1099            sampler1_name: sampler1_name.to_string(),
1100            sampler2_name: sampler2_name.to_string(),
1101            test_comparisons: comparisons,
1102            avg_quality_improvement,
1103            avg_runtime_ratio,
1104            winner: if avg_quality_improvement > 0.0 {
1105                sampler2_name.to_string()
1106            } else {
1107                sampler1_name.to_string()
1108            },
1109        })
1110    }
1111    /// Create new testing framework
1112    pub fn new(config: TestConfig) -> Self {
1113        Self {
1114            config,
1115            suite: TestSuite {
1116                categories: Vec::new(),
1117                test_cases: Vec::new(),
1118                benchmarks: Vec::new(),
1119            },
1120            results: TestResults {
1121                test_results: Vec::new(),
1122                summary: TestSummary {
1123                    total_tests: 0,
1124                    passed: 0,
1125                    failed: 0,
1126                    skipped: 0,
1127                    avg_runtime: Duration::from_secs(0),
1128                    success_rate: 0.0,
1129                    quality_metrics: QualityMetrics {
1130                        avg_quality: 0.0,
1131                        best_quality: f64::NEG_INFINITY,
1132                        worst_quality: f64::INFINITY,
1133                        std_dev: 0.0,
1134                        constraint_satisfaction_rate: 0.0,
1135                    },
1136                },
1137                failures: Vec::new(),
1138                performance: PerformanceData {
1139                    runtime_stats: RuntimeStats {
1140                        total_time: Duration::from_secs(0),
1141                        qubo_generation_time: Duration::from_secs(0),
1142                        solving_time: Duration::from_secs(0),
1143                        validation_time: Duration::from_secs(0),
1144                        time_per_test: Vec::new(),
1145                    },
1146                    memory_stats: MemoryStats {
1147                        peak_memory: 0,
1148                        avg_memory: 0,
1149                        memory_per_test: Vec::new(),
1150                    },
1151                    convergence_data: ConvergenceData {
1152                        curves: Vec::new(),
1153                        avg_iterations: 0.0,
1154                        convergence_rate: 0.0,
1155                    },
1156                },
1157            },
1158            validators: Self::default_validators(),
1159            generators: Self::default_generators(),
1160        }
1161    }
1162
1163    /// Get default validators
1164    fn default_validators() -> Vec<Box<dyn Validator>> {
1165        vec![
1166            Box::new(ConstraintValidator),
1167            Box::new(ObjectiveValidator),
1168            Box::new(BoundsValidator),
1169            Box::new(SymmetryValidator),
1170        ]
1171    }
1172
1173    /// Get default generators
1174    fn default_generators() -> Vec<Box<dyn TestGenerator>> {
1175        vec![
1176            Box::new(MaxCutGenerator),
1177            Box::new(TSPGenerator),
1178            Box::new(GraphColoringGenerator),
1179            Box::new(KnapsackGenerator),
1180            Box::new(RandomQuboGenerator),
1181        ]
1182    }
1183
1184    /// Add test category
1185    pub fn add_category(&mut self, category: TestCategory) {
1186        self.suite.categories.push(category);
1187    }
1188
1189    /// Add custom generator
1190    pub fn add_generator(&mut self, generator: Box<dyn TestGenerator>) {
1191        self.generators.push(generator);
1192    }
1193
1194    /// Add custom validator
1195    pub fn add_validator(&mut self, validator: Box<dyn Validator>) {
1196        self.validators.push(validator);
1197    }
1198
1199    /// Generate test suite
1200    pub fn generate_suite(&mut self) -> Result<(), String> {
1201        let start_time = Instant::now();
1202
1203        // Generate tests for each category
1204        for category in &self.suite.categories {
1205            for problem_type in &category.problem_types {
1206                for difficulty in &category.difficulties {
1207                    for size in &self.config.problem_sizes {
1208                        let config = GeneratorConfig {
1209                            problem_type: problem_type.clone(),
1210                            size: *size,
1211                            difficulty: difficulty.clone(),
1212                            seed: self.config.seed,
1213                            parameters: HashMap::new(),
1214                        };
1215
1216                        // Find suitable generator
1217                        for generator in &self.generators {
1218                            if generator.supported_types().contains(problem_type) {
1219                                let test_cases = generator.generate(&config)?;
1220                                self.suite.test_cases.extend(test_cases);
1221                                break;
1222                            }
1223                        }
1224                    }
1225                }
1226            }
1227        }
1228
1229        self.results.performance.runtime_stats.qubo_generation_time = start_time.elapsed();
1230
1231        Ok(())
1232    }
1233
1234    /// Run test suite
1235    pub fn run_suite<S: Sampler>(&mut self, sampler: &S) -> Result<(), String> {
1236        let total_start = Instant::now();
1237
1238        let test_cases = self.suite.test_cases.clone();
1239        for test_case in &test_cases {
1240            let test_start = Instant::now();
1241
1242            // Run test with timeout
1243            match self.run_single_test(test_case, sampler) {
1244                Ok(result) => {
1245                    self.results.test_results.push(result);
1246                    self.results.summary.passed += 1;
1247                }
1248                Err(e) => {
1249                    self.results.failures.push(TestFailure {
1250                        test_id: test_case.id.clone(),
1251                        failure_type: FailureType::SamplerError,
1252                        message: e,
1253                        stack_trace: None,
1254                        context: HashMap::new(),
1255                    });
1256                    self.results.summary.failed += 1;
1257                }
1258            }
1259
1260            let test_time = test_start.elapsed();
1261            self.results
1262                .performance
1263                .runtime_stats
1264                .time_per_test
1265                .push((test_case.id.clone(), test_time));
1266
1267            self.results.summary.total_tests += 1;
1268        }
1269
1270        self.results.performance.runtime_stats.total_time = total_start.elapsed();
1271        self.calculate_summary();
1272
1273        Ok(())
1274    }
1275
1276    /// Run single test
1277    fn run_single_test<S: Sampler>(
1278        &mut self,
1279        test_case: &TestCase,
1280        sampler: &S,
1281    ) -> Result<TestResult, String> {
1282        let solve_start = Instant::now();
1283
1284        // Run sampler
1285        let sample_result = sampler
1286            .run_qubo(
1287                &(test_case.qubo.clone(), test_case.var_map.clone()),
1288                self.config.samplers[0].num_samples,
1289            )
1290            .map_err(|e| format!("Sampler error: {e:?}"))?;
1291
1292        let solve_time = solve_start.elapsed();
1293
1294        // Get best solution
1295        let best_sample = sample_result
1296            .iter()
1297            .min_by(|a, b| {
1298                a.energy
1299                    .partial_cmp(&b.energy)
1300                    .unwrap_or(std::cmp::Ordering::Equal)
1301            })
1302            .ok_or("No samples returned")?;
1303
1304        // Use the assignments directly (already decoded)
1305        let solution = best_sample.assignments.clone();
1306
1307        // Validate
1308        let validation_start = Instant::now();
1309        let mut validation = ValidationResult {
1310            is_valid: true,
1311            checks: Vec::new(),
1312            warnings: Vec::new(),
1313        };
1314
1315        for validator in &self.validators {
1316            let result = validator.validate(
1317                test_case,
1318                &TestResult {
1319                    test_id: test_case.id.clone(),
1320                    sampler: "test".to_string(),
1321                    solution: solution.clone(),
1322                    objective_value: best_sample.energy,
1323                    constraints_satisfied: true,
1324                    validation: validation.clone(),
1325                    runtime: solve_time,
1326                    metrics: HashMap::new(),
1327                },
1328            );
1329
1330            validation.checks.extend(result.checks);
1331            validation.warnings.extend(result.warnings);
1332            validation.is_valid &= result.is_valid;
1333        }
1334
1335        let validation_time = validation_start.elapsed();
1336        self.results.performance.runtime_stats.solving_time += solve_time;
1337        self.results.performance.runtime_stats.validation_time += validation_time;
1338
1339        Ok(TestResult {
1340            test_id: test_case.id.clone(),
1341            sampler: self.config.samplers[0].name.clone(),
1342            solution,
1343            objective_value: best_sample.energy,
1344            constraints_satisfied: validation.is_valid,
1345            validation,
1346            runtime: solve_time + validation_time,
1347            metrics: HashMap::new(),
1348        })
1349    }
1350
1351    /// Decode solution
1352    fn decode_solution(
1353        &self,
1354        var_map: &HashMap<String, usize>,
1355        sample: &[i8],
1356    ) -> HashMap<String, bool> {
1357        let mut solution = HashMap::new();
1358
1359        for (var_name, &idx) in var_map {
1360            if idx < sample.len() {
1361                solution.insert(var_name.clone(), sample[idx] == 1);
1362            }
1363        }
1364
1365        solution
1366    }
1367
1368    /// Calculate summary statistics
1369    fn calculate_summary(&mut self) {
1370        if self.results.test_results.is_empty() {
1371            return;
1372        }
1373
1374        // Success rate
1375        self.results.summary.success_rate =
1376            self.results.summary.passed as f64 / self.results.summary.total_tests as f64;
1377
1378        // Average runtime
1379        let total_runtime: Duration = self.results.test_results.iter().map(|r| r.runtime).sum();
1380        self.results.summary.avg_runtime = total_runtime / self.results.test_results.len() as u32;
1381
1382        // Quality metrics
1383        let qualities: Vec<f64> = self
1384            .results
1385            .test_results
1386            .iter()
1387            .map(|r| r.objective_value)
1388            .collect();
1389
1390        self.results.summary.quality_metrics.avg_quality =
1391            qualities.iter().sum::<f64>() / qualities.len() as f64;
1392
1393        self.results.summary.quality_metrics.best_quality = *qualities
1394            .iter()
1395            .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
1396            .unwrap_or(&0.0);
1397
1398        self.results.summary.quality_metrics.worst_quality = *qualities
1399            .iter()
1400            .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
1401            .unwrap_or(&0.0);
1402
1403        // Standard deviation
1404        let mean = self.results.summary.quality_metrics.avg_quality;
1405        let variance =
1406            qualities.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / qualities.len() as f64;
1407        self.results.summary.quality_metrics.std_dev = variance.sqrt();
1408
1409        // Constraint satisfaction rate
1410        let satisfied = self
1411            .results
1412            .test_results
1413            .iter()
1414            .filter(|r| r.constraints_satisfied)
1415            .count();
1416        self.results
1417            .summary
1418            .quality_metrics
1419            .constraint_satisfaction_rate =
1420            satisfied as f64 / self.results.test_results.len() as f64;
1421    }
1422
1423    /// Generate report
1424    pub fn generate_report(&self) -> Result<String, String> {
1425        match self.config.output.format {
1426            ReportFormat::Text => self.generate_text_report(),
1427            ReportFormat::Json => self.generate_json_report(),
1428            ReportFormat::Html => self.generate_html_report(),
1429            ReportFormat::Markdown => self.generate_markdown_report(),
1430            ReportFormat::Csv => self.generate_csv_report(),
1431        }
1432    }
1433
1434    /// Generate text report
1435    fn generate_text_report(&self) -> Result<String, String> {
1436        let mut report = String::new();
1437
1438        report.push_str("=== Quantum Optimization Test Report ===\n\n");
1439
1440        report.push_str(&format!(
1441            "Total Tests: {}\n",
1442            self.results.summary.total_tests
1443        ));
1444        report.push_str(&format!("Passed: {}\n", self.results.summary.passed));
1445        report.push_str(&format!("Failed: {}\n", self.results.summary.failed));
1446        report.push_str(&format!(
1447            "Success Rate: {:.2}%\n",
1448            self.results.summary.success_rate * 100.0
1449        ));
1450        report.push_str(&format!(
1451            "Average Runtime: {:?}\n\n",
1452            self.results.summary.avg_runtime
1453        ));
1454
1455        report.push_str("Quality Metrics:\n");
1456        report.push_str(&format!(
1457            "  Average Quality: {:.4}\n",
1458            self.results.summary.quality_metrics.avg_quality
1459        ));
1460        report.push_str(&format!(
1461            "  Best Quality: {:.4}\n",
1462            self.results.summary.quality_metrics.best_quality
1463        ));
1464        report.push_str(&format!(
1465            "  Worst Quality: {:.4}\n",
1466            self.results.summary.quality_metrics.worst_quality
1467        ));
1468        report.push_str(&format!(
1469            "  Std Dev: {:.4}\n",
1470            self.results.summary.quality_metrics.std_dev
1471        ));
1472        report.push_str(&format!(
1473            "  Constraint Satisfaction: {:.2}%\n\n",
1474            self.results
1475                .summary
1476                .quality_metrics
1477                .constraint_satisfaction_rate
1478                * 100.0
1479        ));
1480
1481        if !self.results.failures.is_empty() {
1482            report.push_str("Failures:\n");
1483            for failure in &self.results.failures {
1484                report.push_str(&format!(
1485                    "  - {} ({}): {}\n",
1486                    failure.test_id,
1487                    format!("{:?}", failure.failure_type),
1488                    failure.message
1489                ));
1490            }
1491        }
1492
1493        Ok(report)
1494    }
1495
1496    /// Generate JSON report
1497    fn generate_json_report(&self) -> Result<String, String> {
1498        use std::fmt::Write;
1499
1500        // Helper to convert fmt::Error to String
1501        fn write_err(e: std::fmt::Error) -> String {
1502            format!("JSON write error: {e}")
1503        }
1504
1505        let mut json = String::new();
1506
1507        // Build JSON manually (avoiding serde dependency issues)
1508        json.push_str("{\n");
1509
1510        // Summary section
1511        json.push_str("  \"summary\": {\n");
1512        writeln!(
1513            &mut json,
1514            "    \"total_tests\": {},",
1515            self.results.summary.total_tests
1516        )
1517        .map_err(write_err)?;
1518        writeln!(
1519            &mut json,
1520            "    \"passed\": {},",
1521            self.results.summary.passed
1522        )
1523        .map_err(write_err)?;
1524        writeln!(
1525            &mut json,
1526            "    \"failed\": {},",
1527            self.results.summary.failed
1528        )
1529        .map_err(write_err)?;
1530        writeln!(
1531            &mut json,
1532            "    \"skipped\": {},",
1533            self.results.summary.skipped
1534        )
1535        .map_err(write_err)?;
1536        writeln!(
1537            &mut json,
1538            "    \"success_rate\": {},",
1539            self.results.summary.success_rate
1540        )
1541        .map_err(write_err)?;
1542        writeln!(
1543            &mut json,
1544            "    \"avg_runtime_ms\": {}",
1545            self.results.summary.avg_runtime.as_millis()
1546        )
1547        .map_err(write_err)?;
1548        json.push_str("  },\n");
1549
1550        // Quality metrics
1551        json.push_str("  \"quality_metrics\": {\n");
1552        writeln!(
1553            &mut json,
1554            "    \"avg_quality\": {},",
1555            self.results.summary.quality_metrics.avg_quality
1556        )
1557        .map_err(write_err)?;
1558        writeln!(
1559            &mut json,
1560            "    \"best_quality\": {},",
1561            self.results.summary.quality_metrics.best_quality
1562        )
1563        .map_err(write_err)?;
1564        writeln!(
1565            &mut json,
1566            "    \"worst_quality\": {},",
1567            self.results.summary.quality_metrics.worst_quality
1568        )
1569        .map_err(write_err)?;
1570        writeln!(
1571            &mut json,
1572            "    \"std_dev\": {},",
1573            self.results.summary.quality_metrics.std_dev
1574        )
1575        .map_err(write_err)?;
1576        writeln!(
1577            &mut json,
1578            "    \"constraint_satisfaction_rate\": {}",
1579            self.results
1580                .summary
1581                .quality_metrics
1582                .constraint_satisfaction_rate
1583        )
1584        .map_err(write_err)?;
1585        json.push_str("  },\n");
1586
1587        // Performance data
1588        json.push_str("  \"performance\": {\n");
1589        writeln!(
1590            &mut json,
1591            "    \"total_time_ms\": {},",
1592            self.results
1593                .performance
1594                .runtime_stats
1595                .total_time
1596                .as_millis()
1597        )
1598        .map_err(write_err)?;
1599        writeln!(
1600            &mut json,
1601            "    \"solving_time_ms\": {},",
1602            self.results
1603                .performance
1604                .runtime_stats
1605                .solving_time
1606                .as_millis()
1607        )
1608        .map_err(write_err)?;
1609        writeln!(
1610            &mut json,
1611            "    \"validation_time_ms\": {}",
1612            self.results
1613                .performance
1614                .runtime_stats
1615                .validation_time
1616                .as_millis()
1617        )
1618        .map_err(write_err)?;
1619        json.push_str("  },\n");
1620
1621        // Test results
1622        json.push_str("  \"test_results\": [\n");
1623        for (i, result) in self.results.test_results.iter().enumerate() {
1624            json.push_str("    {\n");
1625            writeln!(&mut json, "      \"test_id\": \"{}\",", result.test_id).map_err(write_err)?;
1626            writeln!(&mut json, "      \"sampler\": \"{}\",", result.sampler).map_err(write_err)?;
1627            writeln!(
1628                &mut json,
1629                "      \"objective_value\": {},",
1630                result.objective_value
1631            )
1632            .map_err(write_err)?;
1633            writeln!(
1634                &mut json,
1635                "      \"constraints_satisfied\": {},",
1636                result.constraints_satisfied
1637            )
1638            .map_err(write_err)?;
1639            writeln!(
1640                &mut json,
1641                "      \"runtime_ms\": {},",
1642                result.runtime.as_millis()
1643            )
1644            .map_err(write_err)?;
1645            writeln!(
1646                &mut json,
1647                "      \"is_valid\": {}",
1648                result.validation.is_valid
1649            )
1650            .map_err(write_err)?;
1651            json.push_str("    }");
1652            if i < self.results.test_results.len() - 1 {
1653                json.push(',');
1654            }
1655            json.push('\n');
1656        }
1657        json.push_str("  ],\n");
1658
1659        // Failures
1660        json.push_str("  \"failures\": [\n");
1661        for (i, failure) in self.results.failures.iter().enumerate() {
1662            json.push_str("    {\n");
1663            writeln!(&mut json, "      \"test_id\": \"{}\",", failure.test_id)
1664                .map_err(write_err)?;
1665            writeln!(
1666                &mut json,
1667                "      \"failure_type\": \"{:?}\",",
1668                failure.failure_type
1669            )
1670            .map_err(write_err)?;
1671            writeln!(
1672                &mut json,
1673                "      \"message\": \"{}\"",
1674                failure.message.replace('"', "\\\"")
1675            )
1676            .map_err(write_err)?;
1677            json.push_str("    }");
1678            if i < self.results.failures.len() - 1 {
1679                json.push(',');
1680            }
1681            json.push('\n');
1682        }
1683        json.push_str("  ]\n");
1684
1685        json.push_str("}\n");
1686
1687        Ok(json)
1688    }
1689
1690    /// Generate HTML report
1691    fn generate_html_report(&self) -> Result<String, String> {
1692        let mut html = String::new();
1693
1694        html.push_str("<!DOCTYPE html>\n<html>\n<head>\n");
1695        html.push_str("<title>Quantum Optimization Test Report</title>\n");
1696        html.push_str(
1697            "<style>
1698            body { font-family: Arial, sans-serif; margin: 20px; }
1699            table { border-collapse: collapse; width: 100%; }
1700            th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
1701            th { background-color: #f2f2f2; }
1702            .passed { color: green; }
1703            .failed { color: red; }
1704        </style>\n",
1705        );
1706        html.push_str("</head>\n<body>\n");
1707
1708        html.push_str("<h1>Quantum Optimization Test Report</h1>\n");
1709
1710        // Summary
1711        html.push_str("<h2>Summary</h2>\n");
1712        html.push_str("<table>\n");
1713        html.push_str(&format!(
1714            "<tr><td>Total Tests</td><td>{}</td></tr>\n",
1715            self.results.summary.total_tests
1716        ));
1717        html.push_str(&format!(
1718            "<tr><td>Passed</td><td class='passed'>{}</td></tr>\n",
1719            self.results.summary.passed
1720        ));
1721        html.push_str(&format!(
1722            "<tr><td>Failed</td><td class='failed'>{}</td></tr>\n",
1723            self.results.summary.failed
1724        ));
1725        html.push_str(&format!(
1726            "<tr><td>Success Rate</td><td>{:.2}%</td></tr>\n",
1727            self.results.summary.success_rate * 100.0
1728        ));
1729        html.push_str("</table>\n");
1730
1731        html.push_str("</body>\n</html>");
1732
1733        Ok(html)
1734    }
1735
1736    /// Generate Markdown report
1737    fn generate_markdown_report(&self) -> Result<String, String> {
1738        let mut md = String::new();
1739
1740        md.push_str("# Quantum Optimization Test Report\n\n");
1741
1742        md.push_str("## Summary\n\n");
1743        md.push_str("| Metric | Value |\n");
1744        md.push_str("|--------|-------|\n");
1745        md.push_str(&format!(
1746            "| Total Tests | {} |\n",
1747            self.results.summary.total_tests
1748        ));
1749        md.push_str(&format!("| Passed | {} |\n", self.results.summary.passed));
1750        md.push_str(&format!("| Failed | {} |\n", self.results.summary.failed));
1751        md.push_str(&format!(
1752            "| Success Rate | {:.2}% |\n",
1753            self.results.summary.success_rate * 100.0
1754        ));
1755        md.push_str(&format!(
1756            "| Average Runtime | {:?} |\n\n",
1757            self.results.summary.avg_runtime
1758        ));
1759
1760        md.push_str("## Quality Metrics\n\n");
1761        md.push_str(&format!(
1762            "- **Average Quality**: {:.4}\n",
1763            self.results.summary.quality_metrics.avg_quality
1764        ));
1765        md.push_str(&format!(
1766            "- **Best Quality**: {:.4}\n",
1767            self.results.summary.quality_metrics.best_quality
1768        ));
1769        md.push_str(&format!(
1770            "- **Worst Quality**: {:.4}\n",
1771            self.results.summary.quality_metrics.worst_quality
1772        ));
1773        md.push_str(&format!(
1774            "- **Standard Deviation**: {:.4}\n",
1775            self.results.summary.quality_metrics.std_dev
1776        ));
1777        md.push_str(&format!(
1778            "- **Constraint Satisfaction Rate**: {:.2}%\n\n",
1779            self.results
1780                .summary
1781                .quality_metrics
1782                .constraint_satisfaction_rate
1783                * 100.0
1784        ));
1785
1786        Ok(md)
1787    }
1788
1789    /// Generate CSV report
1790    fn generate_csv_report(&self) -> Result<String, String> {
1791        let mut csv = String::new();
1792
1793        csv.push_str("test_id,sampler,objective_value,constraints_satisfied,runtime_ms,valid\n");
1794
1795        for result in &self.results.test_results {
1796            csv.push_str(&format!(
1797                "{},{},{},{},{},{}\n",
1798                result.test_id,
1799                result.sampler,
1800                result.objective_value,
1801                result.constraints_satisfied,
1802                result.runtime.as_millis(),
1803                result.validation.is_valid
1804            ));
1805        }
1806
1807        Ok(csv)
1808    }
1809
1810    /// Save report to file
1811    pub fn save_report(&self, filename: &str) -> Result<(), String> {
1812        let report = self.generate_report()?;
1813        let mut file = File::create(filename).map_err(|e| format!("Failed to create file: {e}"))?;
1814        file.write_all(report.as_bytes())
1815            .map_err(|e| format!("Failed to write file: {e}"))?;
1816        Ok(())
1817    }
1818}
1819
1820/// Constraint validator
1821struct ConstraintValidator;
1822
1823impl Validator for ConstraintValidator {
1824    fn validate(&self, test_case: &TestCase, result: &TestResult) -> ValidationResult {
1825        let mut checks = Vec::new();
1826        let mut is_valid = true;
1827
1828        for constraint in &test_case.constraints {
1829            let satisfied = self.check_constraint(constraint, &result.solution);
1830
1831            checks.push(ValidationCheck {
1832                name: format!("Constraint {:?}", constraint.constraint_type),
1833                passed: satisfied,
1834                message: if satisfied {
1835                    "Constraint satisfied".to_string()
1836                } else {
1837                    "Constraint violated".to_string()
1838                },
1839                details: None,
1840            });
1841
1842            is_valid &= satisfied;
1843        }
1844
1845        ValidationResult {
1846            is_valid,
1847            checks,
1848            warnings: Vec::new(),
1849        }
1850    }
1851
1852    fn name(&self) -> &'static str {
1853        "ConstraintValidator"
1854    }
1855}
1856
1857impl ConstraintValidator {
1858    fn check_constraint(&self, constraint: &Constraint, solution: &HashMap<String, bool>) -> bool {
1859        match &constraint.constraint_type {
1860            ConstraintType::OneHot => {
1861                let active = constraint
1862                    .variables
1863                    .iter()
1864                    .filter(|v| *solution.get(*v).unwrap_or(&false))
1865                    .count();
1866                active == 1
1867            }
1868            ConstraintType::AtMostK { k } => {
1869                let active = constraint
1870                    .variables
1871                    .iter()
1872                    .filter(|v| *solution.get(*v).unwrap_or(&false))
1873                    .count();
1874                active <= *k
1875            }
1876            ConstraintType::AtLeastK { k } => {
1877                let active = constraint
1878                    .variables
1879                    .iter()
1880                    .filter(|v| *solution.get(*v).unwrap_or(&false))
1881                    .count();
1882                active >= *k
1883            }
1884            ConstraintType::ExactlyK { k } => {
1885                let active = constraint
1886                    .variables
1887                    .iter()
1888                    .filter(|v| *solution.get(*v).unwrap_or(&false))
1889                    .count();
1890                active == *k
1891            }
1892            _ => true, // Other constraints not implemented
1893        }
1894    }
1895}
1896
1897/// Objective validator
1898struct ObjectiveValidator;
1899
1900impl Validator for ObjectiveValidator {
1901    fn validate(&self, test_case: &TestCase, result: &TestResult) -> ValidationResult {
1902        let mut checks = Vec::new();
1903
1904        // Check if objective is better than random
1905        let random_value = self.estimate_random_objective(&test_case.qubo);
1906        let improvement = (random_value - result.objective_value) / random_value.abs();
1907
1908        checks.push(ValidationCheck {
1909            name: "Objective improvement".to_string(),
1910            passed: improvement > 0.0,
1911            message: format!("Improvement over random: {:.2}%", improvement * 100.0),
1912            details: Some(format!(
1913                "Random: {:.4}, Found: {:.4}",
1914                random_value, result.objective_value
1915            )),
1916        });
1917
1918        // Check against optimal if known
1919        if let Some(optimal_value) = test_case.optimal_value {
1920            let gap = (result.objective_value - optimal_value).abs() / optimal_value.abs();
1921            let acceptable_gap = 0.05; // 5% gap
1922
1923            checks.push(ValidationCheck {
1924                name: "Optimality gap".to_string(),
1925                passed: gap <= acceptable_gap,
1926                message: format!("Gap to optimal: {:.2}%", gap * 100.0),
1927                details: Some(format!(
1928                    "Optimal: {:.4}, Found: {:.4}",
1929                    optimal_value, result.objective_value
1930                )),
1931            });
1932        }
1933
1934        ValidationResult {
1935            is_valid: checks.iter().all(|c| c.passed),
1936            checks,
1937            warnings: Vec::new(),
1938        }
1939    }
1940
1941    fn name(&self) -> &'static str {
1942        "ObjectiveValidator"
1943    }
1944}
1945
1946impl ObjectiveValidator {
1947    fn estimate_random_objective(&self, qubo: &Array2<f64>) -> f64 {
1948        let n = qubo.shape()[0];
1949        let mut rng = thread_rng();
1950        let mut total = 0.0;
1951        let samples = 100;
1952
1953        for _ in 0..samples {
1954            let mut x = vec![0.0; n];
1955            for x_item in x.iter_mut().take(n) {
1956                *x_item = if rng.gen::<bool>() { 1.0 } else { 0.0 };
1957            }
1958
1959            let mut value = 0.0;
1960            for i in 0..n {
1961                for j in 0..n {
1962                    value += qubo[[i, j]] * x[i] * x[j];
1963                }
1964            }
1965
1966            total += value;
1967        }
1968
1969        total / samples as f64
1970    }
1971}
1972
1973/// Bounds validator
1974struct BoundsValidator;
1975
1976impl Validator for BoundsValidator {
1977    fn validate(&self, test_case: &TestCase, result: &TestResult) -> ValidationResult {
1978        let mut checks = Vec::new();
1979
1980        // Check all variables are binary (always true for bool type)
1981        let all_binary = true;
1982
1983        checks.push(ValidationCheck {
1984            name: "Binary variables".to_string(),
1985            passed: all_binary,
1986            message: if all_binary {
1987                "All variables are binary".to_string()
1988            } else {
1989                "Non-binary values found".to_string()
1990            },
1991            details: None,
1992        });
1993
1994        // Check variable count
1995        let expected_vars = test_case.var_map.len();
1996        let actual_vars = result.solution.len();
1997
1998        checks.push(ValidationCheck {
1999            name: "Variable count".to_string(),
2000            passed: expected_vars == actual_vars,
2001            message: format!("Expected {expected_vars} variables, found {actual_vars}"),
2002            details: None,
2003        });
2004
2005        ValidationResult {
2006            is_valid: checks.iter().all(|c| c.passed),
2007            checks,
2008            warnings: Vec::new(),
2009        }
2010    }
2011
2012    fn name(&self) -> &'static str {
2013        "BoundsValidator"
2014    }
2015}
2016
2017/// Symmetry validator
2018struct SymmetryValidator;
2019
2020impl Validator for SymmetryValidator {
2021    fn validate(&self, test_case: &TestCase, _result: &TestResult) -> ValidationResult {
2022        let mut warnings = Vec::new();
2023
2024        // Check for symmetries in QUBO
2025        if self.is_symmetric(&test_case.qubo) {
2026            warnings.push("QUBO matrix has symmetries that might not be broken".to_string());
2027        }
2028
2029        ValidationResult {
2030            is_valid: true,
2031            checks: Vec::new(),
2032            warnings,
2033        }
2034    }
2035
2036    fn name(&self) -> &'static str {
2037        "SymmetryValidator"
2038    }
2039}
2040
2041impl SymmetryValidator {
2042    fn is_symmetric(&self, qubo: &Array2<f64>) -> bool {
2043        let n = qubo.shape()[0];
2044
2045        for i in 0..n {
2046            for j in i + 1..n {
2047                if (qubo[[i, j]] - qubo[[j, i]]).abs() > 1e-10 {
2048                    return false;
2049                }
2050            }
2051        }
2052
2053        true
2054    }
2055}
2056
2057/// Max-cut problem generator
2058struct MaxCutGenerator;
2059
2060impl TestGenerator for MaxCutGenerator {
2061    fn generate(&self, config: &GeneratorConfig) -> Result<Vec<TestCase>, String> {
2062        let mut rng = if let Some(seed) = config.seed {
2063            StdRng::seed_from_u64(seed)
2064        } else {
2065            let mut thread_rng = thread_rng();
2066            StdRng::from_rng(&mut thread_rng)
2067        };
2068
2069        let mut test_cases = Vec::new();
2070
2071        // Generate random graph
2072        let n = config.size;
2073        let edge_probability = match config.difficulty {
2074            Difficulty::Easy => 0.3,
2075            Difficulty::Medium => 0.5,
2076            Difficulty::Hard => 0.7,
2077            Difficulty::VeryHard => 0.9,
2078            Difficulty::Extreme => 0.95,
2079        };
2080
2081        let mut qubo = Array2::zeros((n, n));
2082        let mut var_map = HashMap::new();
2083
2084        for i in 0..n {
2085            var_map.insert(format!("x_{i}"), i);
2086        }
2087
2088        // Generate edges
2089        for i in 0..n {
2090            for j in i + 1..n {
2091                if rng.gen::<f64>() < edge_probability {
2092                    let weight = rng.gen_range(1.0..10.0);
2093                    // Max-cut: minimize -w_ij * (x_i + x_j - 2*x_i*x_j)
2094                    qubo[[i, i]] -= weight;
2095                    qubo[[j, j]] -= weight;
2096                    qubo[[i, j]] += 2.0 * weight;
2097                    qubo[[j, i]] += 2.0 * weight;
2098                }
2099            }
2100        }
2101
2102        test_cases.push(TestCase {
2103            id: format!("maxcut_{}_{:?}", n, config.difficulty),
2104            problem_type: ProblemType::MaxCut,
2105            size: n,
2106            qubo,
2107            var_map,
2108            optimal_solution: None,
2109            optimal_value: None,
2110            constraints: Vec::new(),
2111            metadata: TestMetadata {
2112                generation_method: "Random graph".to_string(),
2113                difficulty: config.difficulty.clone(),
2114                expected_runtime: Duration::from_millis(100),
2115                notes: format!("Edge probability: {edge_probability}"),
2116                tags: vec!["graph".to_string(), "maxcut".to_string()],
2117            },
2118        });
2119
2120        Ok(test_cases)
2121    }
2122
2123    fn name(&self) -> &'static str {
2124        "MaxCutGenerator"
2125    }
2126
2127    fn supported_types(&self) -> Vec<ProblemType> {
2128        vec![ProblemType::MaxCut]
2129    }
2130}
2131
2132/// TSP generator
2133struct TSPGenerator;
2134
2135impl TestGenerator for TSPGenerator {
2136    fn generate(&self, config: &GeneratorConfig) -> Result<Vec<TestCase>, String> {
2137        let mut rng = if let Some(seed) = config.seed {
2138            StdRng::seed_from_u64(seed)
2139        } else {
2140            let mut thread_rng = thread_rng();
2141            StdRng::from_rng(&mut thread_rng)
2142        };
2143
2144        let n_cities = config.size;
2145        let mut test_cases = Vec::new();
2146
2147        // Generate random city locations
2148        let mut cities = Vec::new();
2149        for _ in 0..n_cities {
2150            cities.push((rng.gen_range(0.0..100.0), rng.gen_range(0.0..100.0)));
2151        }
2152
2153        // Calculate distances
2154        let mut distances = Array2::zeros((n_cities, n_cities));
2155        for i in 0..n_cities {
2156            for j in 0..n_cities {
2157                if i != j {
2158                    let dx: f64 = cities[i].0 - cities[j].0;
2159                    let dy: f64 = cities[i].1 - cities[j].1;
2160                    distances[[i, j]] = dx.hypot(dy);
2161                }
2162            }
2163        }
2164
2165        // Create QUBO
2166        let n_vars = n_cities * n_cities;
2167        let mut qubo = Array2::zeros((n_vars, n_vars));
2168        let mut var_map = HashMap::new();
2169
2170        // Variable mapping: x[i,j] = city i at position j
2171        for i in 0..n_cities {
2172            for j in 0..n_cities {
2173                let idx = i * n_cities + j;
2174                var_map.insert(format!("x_{i}_{j}"), idx);
2175            }
2176        }
2177
2178        // Objective: minimize total distance
2179        for i in 0..n_cities {
2180            for j in 0..n_cities {
2181                for k in 0..n_cities {
2182                    let next_j = (j + 1) % n_cities;
2183                    let idx1 = i * n_cities + j;
2184                    let idx2 = k * n_cities + next_j;
2185                    qubo[[idx1, idx2]] += distances[[i, k]];
2186                }
2187            }
2188        }
2189
2190        // Constraints
2191        let mut constraints = Vec::new();
2192        let penalty = 1000.0;
2193
2194        // Each city visited exactly once
2195        for i in 0..n_cities {
2196            let vars: Vec<_> = (0..n_cities).map(|j| format!("x_{i}_{j}")).collect();
2197
2198            constraints.push(Constraint {
2199                constraint_type: ConstraintType::ExactlyK { k: 1 },
2200                variables: vars,
2201                parameters: HashMap::new(),
2202                penalty,
2203            });
2204        }
2205
2206        // Each position has exactly one city
2207        for j in 0..n_cities {
2208            let vars: Vec<_> = (0..n_cities).map(|i| format!("x_{i}_{j}")).collect();
2209
2210            constraints.push(Constraint {
2211                constraint_type: ConstraintType::ExactlyK { k: 1 },
2212                variables: vars,
2213                parameters: HashMap::new(),
2214                penalty,
2215            });
2216        }
2217
2218        // Add constraint penalties to QUBO
2219        self.add_constraint_penalties(&mut qubo, &var_map, &constraints)?;
2220
2221        test_cases.push(TestCase {
2222            id: format!("tsp_{}_{:?}", n_cities, config.difficulty),
2223            problem_type: ProblemType::TSP,
2224            size: n_cities,
2225            qubo,
2226            var_map,
2227            optimal_solution: None,
2228            optimal_value: None,
2229            constraints,
2230            metadata: TestMetadata {
2231                generation_method: "Random cities".to_string(),
2232                difficulty: config.difficulty.clone(),
2233                expected_runtime: Duration::from_millis(500),
2234                notes: format!("{n_cities} cities"),
2235                tags: vec!["routing".to_string(), "tsp".to_string()],
2236            },
2237        });
2238
2239        Ok(test_cases)
2240    }
2241
2242    fn name(&self) -> &'static str {
2243        "TSPGenerator"
2244    }
2245
2246    fn supported_types(&self) -> Vec<ProblemType> {
2247        vec![ProblemType::TSP]
2248    }
2249}
2250
2251impl TSPGenerator {
2252    fn add_constraint_penalties(
2253        &self,
2254        qubo: &mut Array2<f64>,
2255        var_map: &HashMap<String, usize>,
2256        constraints: &[Constraint],
2257    ) -> Result<(), String> {
2258        for constraint in constraints {
2259            if let ConstraintType::ExactlyK { k } = &constraint.constraint_type {
2260                // (sum x_i - k)^2
2261                for v1 in &constraint.variables {
2262                    if let Some(&idx1) = var_map.get(v1) {
2263                        // Linear term: -2k
2264                        qubo[[idx1, idx1]] +=
2265                            constraint.penalty * 2.0f64.mul_add(-(*k as f64), 1.0);
2266
2267                        // Quadratic terms
2268                        for v2 in &constraint.variables {
2269                            if v1 != v2 {
2270                                if let Some(&idx2) = var_map.get(v2) {
2271                                    qubo[[idx1, idx2]] += constraint.penalty * 2.0;
2272                                }
2273                            }
2274                        }
2275                    }
2276                }
2277            }
2278        }
2279
2280        Ok(())
2281    }
2282}
2283
2284/// Graph coloring generator
2285struct GraphColoringGenerator;
2286
2287impl TestGenerator for GraphColoringGenerator {
2288    fn generate(&self, config: &GeneratorConfig) -> Result<Vec<TestCase>, String> {
2289        let mut rng = if let Some(seed) = config.seed {
2290            StdRng::seed_from_u64(seed)
2291        } else {
2292            let mut thread_rng = thread_rng();
2293            StdRng::from_rng(&mut thread_rng)
2294        };
2295
2296        let n_vertices = config.size;
2297        let n_colors = match config.difficulty {
2298            Difficulty::Easy => 4,
2299            Difficulty::Medium => 3,
2300            _ => 3,
2301        };
2302
2303        let mut test_cases = Vec::new();
2304
2305        // Generate random graph
2306        let edge_prob = 0.3;
2307        let mut edges = Vec::new();
2308
2309        for i in 0..n_vertices {
2310            for j in i + 1..n_vertices {
2311                if rng.gen::<f64>() < edge_prob {
2312                    edges.push((i, j));
2313                }
2314            }
2315        }
2316
2317        // Create QUBO
2318        let n_vars = n_vertices * n_colors;
2319        let mut qubo = Array2::zeros((n_vars, n_vars));
2320        let mut var_map = HashMap::new();
2321
2322        // Variable mapping: x[v,c] = vertex v has color c
2323        for v in 0..n_vertices {
2324            for c in 0..n_colors {
2325                let idx = v * n_colors + c;
2326                var_map.insert(format!("x_{v}_{c}"), idx);
2327            }
2328        }
2329
2330        // Objective: minimize number of colors used (simplified)
2331        for v in 0..n_vertices {
2332            for c in 0..n_colors {
2333                let idx = v * n_colors + c;
2334                qubo[[idx, idx]] -= c as f64; // Prefer lower colors
2335            }
2336        }
2337
2338        // Constraints
2339        let mut constraints = Vec::new();
2340        let penalty = 100.0;
2341
2342        // Each vertex has exactly one color
2343        for v in 0..n_vertices {
2344            let vars: Vec<_> = (0..n_colors).map(|c| format!("x_{v}_{c}")).collect();
2345
2346            constraints.push(Constraint {
2347                constraint_type: ConstraintType::ExactlyK { k: 1 },
2348                variables: vars,
2349                parameters: HashMap::new(),
2350                penalty,
2351            });
2352        }
2353
2354        // Adjacent vertices have different colors
2355        for (u, v) in &edges {
2356            for c in 0..n_colors {
2357                let idx_u = u * n_colors + c;
2358                let idx_v = v * n_colors + c;
2359                qubo[[idx_u, idx_v]] += penalty;
2360                qubo[[idx_v, idx_u]] += penalty;
2361            }
2362        }
2363
2364        test_cases.push(TestCase {
2365            id: format!(
2366                "coloring_{}_{}_{}_{:?}",
2367                n_vertices,
2368                n_colors,
2369                edges.len(),
2370                config.difficulty
2371            ),
2372            problem_type: ProblemType::GraphColoring,
2373            size: n_vertices,
2374            qubo,
2375            var_map,
2376            optimal_solution: None,
2377            optimal_value: None,
2378            constraints,
2379            metadata: TestMetadata {
2380                generation_method: "Random graph".to_string(),
2381                difficulty: config.difficulty.clone(),
2382                expected_runtime: Duration::from_millis(200),
2383                notes: format!(
2384                    "{} vertices, {} colors, {} edges",
2385                    n_vertices,
2386                    n_colors,
2387                    edges.len()
2388                ),
2389                tags: vec!["graph".to_string(), "coloring".to_string()],
2390            },
2391        });
2392
2393        Ok(test_cases)
2394    }
2395
2396    fn name(&self) -> &'static str {
2397        "GraphColoringGenerator"
2398    }
2399
2400    fn supported_types(&self) -> Vec<ProblemType> {
2401        vec![ProblemType::GraphColoring]
2402    }
2403}
2404
2405/// Knapsack generator
2406struct KnapsackGenerator;
2407
2408impl TestGenerator for KnapsackGenerator {
2409    fn generate(&self, config: &GeneratorConfig) -> Result<Vec<TestCase>, String> {
2410        let mut rng = if let Some(seed) = config.seed {
2411            StdRng::seed_from_u64(seed)
2412        } else {
2413            let mut thread_rng = thread_rng();
2414            StdRng::from_rng(&mut thread_rng)
2415        };
2416
2417        let n_items = config.size;
2418        let mut test_cases = Vec::new();
2419
2420        // Generate items
2421        let mut values = Vec::new();
2422        let mut weights = Vec::new();
2423
2424        for _ in 0..n_items {
2425            values.push(rng.gen_range(1.0..100.0));
2426            weights.push(rng.gen_range(1.0..50.0));
2427        }
2428
2429        let capacity = weights.iter().sum::<f64>() * 0.5; // 50% of total weight
2430
2431        // Create QUBO
2432        let mut qubo = Array2::zeros((n_items, n_items));
2433        let mut var_map = HashMap::new();
2434
2435        for i in 0..n_items {
2436            var_map.insert(format!("x_{i}"), i);
2437            // Maximize value (negative in minimization)
2438            qubo[[i, i]] -= values[i];
2439        }
2440
2441        // Weight constraint penalty
2442        let _penalty = values.iter().sum::<f64>() * 2.0;
2443
2444        // Add soft constraint for capacity
2445        // Penalty for exceeding capacity: (sum w_i x_i - W)^2 if sum > W
2446        // This is simplified - proper implementation would use slack variables
2447
2448        test_cases.push(TestCase {
2449            id: format!("knapsack_{}_{:?}", n_items, config.difficulty),
2450            problem_type: ProblemType::Knapsack,
2451            size: n_items,
2452            qubo,
2453            var_map,
2454            optimal_solution: None,
2455            optimal_value: None,
2456            constraints: vec![],
2457            metadata: TestMetadata {
2458                generation_method: "Random items".to_string(),
2459                difficulty: config.difficulty.clone(),
2460                expected_runtime: Duration::from_millis(100),
2461                notes: format!("{n_items} items, capacity: {capacity:.1}"),
2462                tags: vec!["optimization".to_string(), "knapsack".to_string()],
2463            },
2464        });
2465
2466        Ok(test_cases)
2467    }
2468
2469    fn name(&self) -> &'static str {
2470        "KnapsackGenerator"
2471    }
2472
2473    fn supported_types(&self) -> Vec<ProblemType> {
2474        vec![ProblemType::Knapsack]
2475    }
2476}
2477
2478/// Random QUBO generator
2479struct RandomQuboGenerator;
2480
2481impl TestGenerator for RandomQuboGenerator {
2482    fn generate(&self, config: &GeneratorConfig) -> Result<Vec<TestCase>, String> {
2483        let mut rng = if let Some(seed) = config.seed {
2484            StdRng::seed_from_u64(seed)
2485        } else {
2486            let mut thread_rng = thread_rng();
2487            StdRng::from_rng(&mut thread_rng)
2488        };
2489
2490        let n = config.size;
2491        let mut test_cases = Vec::new();
2492
2493        // Generate random QUBO
2494        let mut qubo = Array2::zeros((n, n));
2495        let density = match config.difficulty {
2496            Difficulty::Easy => 0.3,
2497            Difficulty::Medium => 0.5,
2498            Difficulty::Hard => 0.7,
2499            Difficulty::VeryHard => 0.9,
2500            Difficulty::Extreme => 1.0,
2501        };
2502
2503        for i in 0..n {
2504            for j in i..n {
2505                if rng.gen::<f64>() < density {
2506                    let value = rng.gen_range(-10.0..10.0);
2507                    qubo[[i, j]] = value;
2508                    if i != j {
2509                        qubo[[j, i]] = value;
2510                    }
2511                }
2512            }
2513        }
2514
2515        let mut var_map = HashMap::new();
2516        for i in 0..n {
2517            var_map.insert(format!("x_{i}"), i);
2518        }
2519
2520        test_cases.push(TestCase {
2521            id: format!("random_{}_{:?}", n, config.difficulty),
2522            problem_type: ProblemType::Custom {
2523                name: "Random QUBO".to_string(),
2524            },
2525            size: n,
2526            qubo,
2527            var_map,
2528            optimal_solution: None,
2529            optimal_value: None,
2530            constraints: vec![],
2531            metadata: TestMetadata {
2532                generation_method: "Random generation".to_string(),
2533                difficulty: config.difficulty.clone(),
2534                expected_runtime: Duration::from_millis(50),
2535                notes: format!("Density: {density}"),
2536                tags: vec!["random".to_string(), "qubo".to_string()],
2537            },
2538        });
2539
2540        Ok(test_cases)
2541    }
2542
2543    fn name(&self) -> &'static str {
2544        "RandomQuboGenerator"
2545    }
2546
2547    fn supported_types(&self) -> Vec<ProblemType> {
2548        vec![
2549            ProblemType::Custom {
2550                name: "Random".to_string(),
2551            },
2552            ProblemType::Ising,
2553        ]
2554    }
2555}
2556
2557/// Finance industry test generator
2558struct FinanceTestGenerator;
2559
2560impl TestGenerator for FinanceTestGenerator {
2561    fn generate(&self, config: &GeneratorConfig) -> Result<Vec<TestCase>, String> {
2562        let mut rng = if let Some(seed) = config.seed {
2563            StdRng::seed_from_u64(seed)
2564        } else {
2565            let mut thread_rng = thread_rng();
2566            StdRng::from_rng(&mut thread_rng)
2567        };
2568
2569        let n_assets = config.size;
2570        let mut test_cases = Vec::new();
2571
2572        // Generate portfolio optimization test case
2573        let mut qubo = Array2::zeros((n_assets, n_assets));
2574        let mut var_map = HashMap::new();
2575
2576        for i in 0..n_assets {
2577            var_map.insert(format!("asset_{i}"), i);
2578
2579            // Expected return (negative for minimization)
2580            let expected_return = rng.gen_range(0.05..0.15);
2581            qubo[[i, i]] -= expected_return;
2582        }
2583
2584        // Risk covariance terms
2585        for i in 0..n_assets {
2586            for j in 0..n_assets {
2587                let covariance = if i == j {
2588                    rng.gen_range(0.01..0.04) // Variance
2589                } else {
2590                    rng.gen_range(-0.01..0.01) // Covariance
2591                };
2592                qubo[[i, j]] += covariance;
2593            }
2594        }
2595
2596        test_cases.push(TestCase {
2597            id: format!("portfolio_{}_{:?}", n_assets, config.difficulty),
2598            problem_type: ProblemType::Portfolio,
2599            size: n_assets,
2600            qubo,
2601            var_map,
2602            optimal_solution: None,
2603            optimal_value: None,
2604            constraints: vec![Constraint {
2605                constraint_type: ConstraintType::LinearEquality { target: 1.0 },
2606                variables: (0..n_assets).map(|i| format!("asset_{i}")).collect(),
2607                parameters: HashMap::new(),
2608                penalty: 1000.0,
2609            }],
2610            metadata: TestMetadata {
2611                generation_method: "Random portfolio".to_string(),
2612                difficulty: config.difficulty.clone(),
2613                expected_runtime: Duration::from_millis(200),
2614                notes: format!("{n_assets} assets"),
2615                tags: vec!["finance".to_string(), "portfolio".to_string()],
2616            },
2617        });
2618
2619        Ok(test_cases)
2620    }
2621
2622    fn name(&self) -> &'static str {
2623        "FinanceTestGenerator"
2624    }
2625
2626    fn supported_types(&self) -> Vec<ProblemType> {
2627        vec![ProblemType::Portfolio]
2628    }
2629}
2630
2631/// Logistics industry test generator
2632struct LogisticsTestGenerator;
2633
2634impl TestGenerator for LogisticsTestGenerator {
2635    fn generate(&self, config: &GeneratorConfig) -> Result<Vec<TestCase>, String> {
2636        let mut rng = if let Some(seed) = config.seed {
2637            StdRng::seed_from_u64(seed)
2638        } else {
2639            let mut thread_rng = thread_rng();
2640            StdRng::from_rng(&mut thread_rng)
2641        };
2642
2643        let n_vehicles = 2;
2644        let n_locations = config.size;
2645        let mut test_cases = Vec::new();
2646
2647        // Generate vehicle routing problem
2648        let n_vars = n_vehicles * n_locations * n_locations;
2649        let mut qubo = Array2::zeros((n_vars, n_vars));
2650        let mut var_map = HashMap::new();
2651
2652        // Variable mapping: x[v][i][j] = vehicle v goes from i to j
2653        for v in 0..n_vehicles {
2654            for i in 0..n_locations {
2655                for j in 0..n_locations {
2656                    let idx = v * n_locations * n_locations + i * n_locations + j;
2657                    var_map.insert(format!("x_{v}_{i}_{j}"), idx);
2658                }
2659            }
2660        }
2661
2662        // Add distance objective
2663        for v in 0..n_vehicles {
2664            for i in 0..n_locations {
2665                for j in 0..n_locations {
2666                    if i != j {
2667                        let idx = v * n_locations * n_locations + i * n_locations + j;
2668                        let distance = rng.gen_range(1.0..20.0);
2669                        qubo[[idx, idx]] += distance;
2670                    }
2671                }
2672            }
2673        }
2674
2675        test_cases.push(TestCase {
2676            id: format!("vrp_{}_{}_{:?}", n_vehicles, n_locations, config.difficulty),
2677            problem_type: ProblemType::VRP,
2678            size: n_locations,
2679            qubo,
2680            var_map,
2681            optimal_solution: None,
2682            optimal_value: None,
2683            constraints: vec![],
2684            metadata: TestMetadata {
2685                generation_method: "Random VRP".to_string(),
2686                difficulty: config.difficulty.clone(),
2687                expected_runtime: Duration::from_millis(500),
2688                notes: format!("{n_vehicles} vehicles, {n_locations} locations"),
2689                tags: vec!["logistics".to_string(), "vrp".to_string()],
2690            },
2691        });
2692
2693        Ok(test_cases)
2694    }
2695
2696    fn name(&self) -> &'static str {
2697        "LogisticsTestGenerator"
2698    }
2699
2700    fn supported_types(&self) -> Vec<ProblemType> {
2701        vec![ProblemType::VRP]
2702    }
2703}
2704
2705/// Manufacturing industry test generator
2706struct ManufacturingTestGenerator;
2707
2708impl TestGenerator for ManufacturingTestGenerator {
2709    fn generate(&self, config: &GeneratorConfig) -> Result<Vec<TestCase>, String> {
2710        let mut rng = if let Some(seed) = config.seed {
2711            StdRng::seed_from_u64(seed)
2712        } else {
2713            let mut thread_rng = thread_rng();
2714            StdRng::from_rng(&mut thread_rng)
2715        };
2716
2717        let n_jobs = config.size;
2718        let n_machines = 3;
2719        let mut test_cases = Vec::new();
2720
2721        // Generate job scheduling problem
2722        let n_vars = n_jobs * n_machines;
2723        let mut qubo = Array2::zeros((n_vars, n_vars));
2724        let mut var_map = HashMap::new();
2725
2726        // Variable mapping: x[j][m] = job j on machine m
2727        for j in 0..n_jobs {
2728            for m in 0..n_machines {
2729                let idx = j * n_machines + m;
2730                var_map.insert(format!("job_{j}_machine_{m}"), idx);
2731            }
2732        }
2733
2734        // Add processing time objective
2735        for j in 0..n_jobs {
2736            for m in 0..n_machines {
2737                let idx = j * n_machines + m;
2738                let processing_time = rng.gen_range(1.0..10.0);
2739                qubo[[idx, idx]] += processing_time;
2740            }
2741        }
2742
2743        // Add constraints: each job assigned to exactly one machine
2744        let mut constraints = Vec::new();
2745        for j in 0..n_jobs {
2746            let vars: Vec<_> = (0..n_machines)
2747                .map(|m| format!("job_{j}_machine_{m}"))
2748                .collect();
2749            constraints.push(Constraint {
2750                constraint_type: ConstraintType::ExactlyK { k: 1 },
2751                variables: vars,
2752                parameters: HashMap::new(),
2753                penalty: 100.0,
2754            });
2755        }
2756
2757        test_cases.push(TestCase {
2758            id: format!(
2759                "scheduling_{}_{}_{:?}",
2760                n_jobs, n_machines, config.difficulty
2761            ),
2762            problem_type: ProblemType::JobScheduling,
2763            size: n_jobs,
2764            qubo,
2765            var_map,
2766            optimal_solution: None,
2767            optimal_value: None,
2768            constraints,
2769            metadata: TestMetadata {
2770                generation_method: "Random job scheduling".to_string(),
2771                difficulty: config.difficulty.clone(),
2772                expected_runtime: Duration::from_millis(300),
2773                notes: format!("{n_jobs} jobs, {n_machines} machines"),
2774                tags: vec!["manufacturing".to_string(), "scheduling".to_string()],
2775            },
2776        });
2777
2778        Ok(test_cases)
2779    }
2780
2781    fn name(&self) -> &'static str {
2782        "ManufacturingTestGenerator"
2783    }
2784
2785    fn supported_types(&self) -> Vec<ProblemType> {
2786        vec![ProblemType::JobScheduling]
2787    }
2788}
2789
2790#[cfg(test)]
2791mod tests {
2792    use super::*;
2793    use crate::sampler::SASampler;
2794
2795    #[test]
2796    #[ignore]
2797    fn test_testing_framework() {
2798        let mut config = TestConfig {
2799            seed: Some(42),
2800            cases_per_category: 5,
2801            problem_sizes: vec![5, 10],
2802            samplers: vec![SamplerConfig {
2803                name: "SA".to_string(),
2804                num_samples: 100,
2805                parameters: HashMap::new(),
2806            }],
2807            timeout: Duration::from_secs(10),
2808            validation: ValidationConfig {
2809                check_constraints: true,
2810                check_objective: true,
2811                statistical_tests: false,
2812                tolerance: 1e-6,
2813                min_quality: 0.0,
2814            },
2815            output: OutputConfig {
2816                generate_report: true,
2817                format: ReportFormat::Text,
2818                output_dir: "/tmp".to_string(),
2819                verbosity: VerbosityLevel::Info,
2820            },
2821        };
2822
2823        let mut framework = TestingFramework::new(config);
2824
2825        // Add test categories
2826        framework.add_category(TestCategory {
2827            name: "Graph Problems".to_string(),
2828            description: "Graph-based optimization problems".to_string(),
2829            problem_types: vec![ProblemType::MaxCut, ProblemType::GraphColoring],
2830            difficulties: vec![Difficulty::Easy, Difficulty::Medium],
2831            tags: vec!["graph".to_string()],
2832        });
2833
2834        // Generate test suite
2835        let mut result = framework.generate_suite();
2836        assert!(result.is_ok());
2837        assert!(!framework.suite.test_cases.is_empty());
2838
2839        // Run tests
2840        let sampler = SASampler::new(Some(42));
2841        let mut result = framework.run_suite(&sampler);
2842        assert!(result.is_ok());
2843
2844        // Check results
2845        assert!(framework.results.summary.total_tests > 0);
2846        assert!(framework.results.summary.success_rate >= 0.0);
2847
2848        // Generate report
2849        let mut report = framework.generate_report();
2850        assert!(report.is_ok());
2851    }
2852}