Skip to main content

quantrs2_anneal/advanced_testing_framework/
scenario_engine.rs

1//! Test scenario engine for complex problem generation
2
3use super::{
4    ApplicationError, ApplicationResult, ConditionOperator, ConstraintSpec, ConvergenceExpectation,
5    CriterionType, CriterionValue, DensitySpec, Duration, ExpectedMetrics, HashMap, Instant,
6    IsingModel, ProblemSpecification, ProblemType, PropertyValue, ValidationCriterion, VecDeque,
7};
8
9/// Test scenario engine for complex problem generation
10#[derive(Debug)]
11pub struct TestScenarioEngine {
12    /// Available test scenarios
13    pub scenarios: HashMap<String, TestScenario>,
14    /// Scenario execution history
15    pub execution_history: VecDeque<ScenarioExecution>,
16    /// Problem generators
17    pub generators: Vec<ProblemGenerator>,
18    /// Validation rules
19    pub validation_rules: Vec<ValidationRule>,
20}
21
22/// Individual test scenario
23#[derive(Debug, Clone)]
24pub struct TestScenario {
25    /// Scenario identifier
26    pub id: String,
27    /// Scenario description
28    pub description: String,
29    /// Problem specification
30    pub problem_specs: ProblemSpecification,
31    /// Expected performance metrics
32    pub expected_metrics: ExpectedMetrics,
33    /// Validation criteria
34    pub validation_criteria: Vec<ValidationCriterion>,
35    /// Timeout for scenario execution
36    pub timeout: Duration,
37    /// Maximum number of retries
38    pub max_retries: usize,
39}
40
41/// Scenario execution record
42#[derive(Debug, Clone)]
43pub struct ScenarioExecution {
44    /// Scenario identifier
45    pub scenario_id: String,
46    /// Execution timestamp
47    pub timestamp: Instant,
48    /// Execution duration
49    pub duration: Duration,
50    /// Success status
51    pub success: bool,
52    /// Performance metrics achieved
53    pub metrics: HashMap<String, f64>,
54    /// Error information (if any)
55    pub error: Option<String>,
56}
57
58/// Problem generator for test scenarios
59#[derive(Debug)]
60pub struct ProblemGenerator {
61    /// Generator identifier
62    pub id: String,
63    /// Generator type
64    pub generator_type: GeneratorType,
65    /// Generation parameters
66    pub parameters: HashMap<String, f64>,
67    /// Problem constraints
68    pub constraints: Vec<GeneratorConstraint>,
69}
70
71/// Types of problem generators
72#[derive(Debug, Clone, PartialEq, Eq)]
73pub enum GeneratorType {
74    /// Random problem generator
75    Random,
76    /// Structured problem generator
77    Structured,
78    /// Real-world instance generator
79    RealWorld,
80    /// Adversarial generator
81    Adversarial,
82    /// Benchmark generator
83    Benchmark,
84}
85
86/// Constraints for problem generation
87#[derive(Debug, Clone)]
88pub struct GeneratorConstraint {
89    /// Constraint type
90    pub constraint_type: String,
91    /// Constraint parameters
92    pub parameters: HashMap<String, f64>,
93    /// Constraint priority
94    pub priority: f64,
95}
96
97/// Validation rule for test scenarios
98#[derive(Debug, Clone)]
99pub struct ValidationRule {
100    /// Rule identifier
101    pub id: String,
102    /// Rule description
103    pub description: String,
104    /// Rule condition
105    pub condition: RuleCondition,
106    /// Expected outcome
107    pub expected_outcome: RuleOutcome,
108    /// Rule severity
109    pub severity: RuleSeverity,
110}
111
112/// Condition for validation rule
113#[derive(Debug, Clone)]
114pub struct RuleCondition {
115    /// Condition expression
116    pub expression: String,
117    /// Condition parameters
118    pub parameters: HashMap<String, PropertyValue>,
119    /// Evaluation method
120    pub evaluation_method: EvaluationMethod,
121}
122
123/// Expected outcome for validation rule
124#[derive(Debug, Clone)]
125pub struct RuleOutcome {
126    /// Expected result
127    pub expected_result: PropertyValue,
128    /// Tolerance for comparison
129    pub tolerance: f64,
130    /// Comparison operator
131    pub comparison_op: ConditionOperator,
132}
133
134/// Severity levels for validation rules
135#[derive(Debug, Clone, PartialEq, Eq)]
136pub enum RuleSeverity {
137    /// Critical rule (must pass)
138    Critical,
139    /// Warning rule (should pass)
140    Warning,
141    /// Informational rule
142    Info,
143}
144
145/// Methods for rule evaluation
146#[derive(Debug, Clone, PartialEq, Eq)]
147pub enum EvaluationMethod {
148    /// Direct comparison
149    Direct,
150    /// Statistical test
151    Statistical,
152    /// Machine learning model
153    MachineLearning,
154    /// Custom evaluation
155    Custom(String),
156}
157
158impl TestScenarioEngine {
159    #[must_use]
160    pub fn new() -> Self {
161        let mut scenarios = HashMap::new();
162
163        // Create default test scenarios
164        scenarios.insert(
165            "basic_optimization".to_string(),
166            TestScenario {
167                id: "basic_optimization".to_string(),
168                description: "Basic optimization scenario".to_string(),
169                problem_specs: ProblemSpecification {
170                    problem_type: ProblemType::RandomIsing,
171                    size_range: (10, 100),
172                    density: DensitySpec {
173                        edge_density: (0.1, 0.3),
174                        constraint_density: None,
175                        bias_sparsity: Some(0.5),
176                    },
177                    constraints: ConstraintSpec {
178                        num_constraints: None,
179                        constraint_types: Vec::new(),
180                        strength_range: (0.1, 1.0),
181                    },
182                    seed: Some(42),
183                },
184                expected_metrics: ExpectedMetrics {
185                    solution_quality: (0.7, 1.0),
186                    runtime: (Duration::from_millis(100), Duration::from_secs(10)),
187                    success_rate: 0.9,
188                    convergence: ConvergenceExpectation {
189                        convergence_time: Duration::from_secs(5),
190                        final_energy: None,
191                        energy_gap: None,
192                    },
193                },
194                validation_criteria: vec![ValidationCriterion {
195                    criterion_type: CriterionType::Performance,
196                    expected_value: CriterionValue::Range(0.7, 1.0),
197                    tolerance: 0.1,
198                    mandatory: true,
199                }],
200                timeout: Duration::from_secs(30),
201                max_retries: 3,
202            },
203        );
204
205        scenarios.insert(
206            "large_scale_test".to_string(),
207            TestScenario {
208                id: "large_scale_test".to_string(),
209                description: "Large scale problem test".to_string(),
210                problem_specs: ProblemSpecification {
211                    problem_type: ProblemType::RandomIsing,
212                    size_range: (1000, 5000),
213                    density: DensitySpec {
214                        edge_density: (0.05, 0.15),
215                        constraint_density: None,
216                        bias_sparsity: Some(0.3),
217                    },
218                    constraints: ConstraintSpec {
219                        num_constraints: None,
220                        constraint_types: Vec::new(),
221                        strength_range: (0.1, 1.0),
222                    },
223                    seed: Some(123),
224                },
225                expected_metrics: ExpectedMetrics {
226                    solution_quality: (0.6, 0.9),
227                    runtime: (Duration::from_secs(10), Duration::from_secs(300)),
228                    success_rate: 0.8,
229                    convergence: ConvergenceExpectation {
230                        convergence_time: Duration::from_secs(60),
231                        final_energy: None,
232                        energy_gap: None,
233                    },
234                },
235                validation_criteria: vec![
236                    ValidationCriterion {
237                        criterion_type: CriterionType::Performance,
238                        expected_value: CriterionValue::Range(0.6, 0.9),
239                        tolerance: 0.1,
240                        mandatory: true,
241                    },
242                    ValidationCriterion {
243                        criterion_type: CriterionType::Runtime,
244                        expected_value: CriterionValue::Maximum(300.0),
245                        tolerance: 0.0,
246                        mandatory: true,
247                    },
248                ],
249                timeout: Duration::from_secs(600),
250                max_retries: 2,
251            },
252        );
253
254        Self {
255            scenarios,
256            execution_history: VecDeque::new(),
257            generators: Self::create_default_generators(),
258            validation_rules: Self::create_default_validation_rules(),
259        }
260    }
261
262    /// Create default problem generators
263    fn create_default_generators() -> Vec<ProblemGenerator> {
264        vec![
265            ProblemGenerator {
266                id: "random_ising".to_string(),
267                generator_type: GeneratorType::Random,
268                parameters: {
269                    let mut params = HashMap::new();
270                    params.insert("density".to_string(), 0.2);
271                    params.insert("bias_range".to_string(), 1.0);
272                    params.insert("coupling_range".to_string(), 1.0);
273                    params
274                },
275                constraints: Vec::new(),
276            },
277            ProblemGenerator {
278                id: "structured_ising".to_string(),
279                generator_type: GeneratorType::Structured,
280                parameters: {
281                    let mut params = HashMap::new();
282                    params.insert("regularity".to_string(), 0.8);
283                    params.insert("locality".to_string(), 0.9);
284                    params
285                },
286                constraints: Vec::new(),
287            },
288        ]
289    }
290
291    /// Create default validation rules
292    fn create_default_validation_rules() -> Vec<ValidationRule> {
293        vec![
294            ValidationRule {
295                id: "solution_feasibility".to_string(),
296                description: "Solution must be feasible".to_string(),
297                condition: RuleCondition {
298                    expression: "solution_valid == true".to_string(),
299                    parameters: HashMap::new(),
300                    evaluation_method: EvaluationMethod::Direct,
301                },
302                expected_outcome: RuleOutcome {
303                    expected_result: PropertyValue::Boolean(true),
304                    tolerance: 0.0,
305                    comparison_op: ConditionOperator::Equal,
306                },
307                severity: RuleSeverity::Critical,
308            },
309            ValidationRule {
310                id: "performance_threshold".to_string(),
311                description: "Performance must exceed minimum threshold".to_string(),
312                condition: RuleCondition {
313                    expression: "solution_quality >= threshold".to_string(),
314                    parameters: {
315                        let mut params = HashMap::new();
316                        params.insert("threshold".to_string(), PropertyValue::Numeric(0.5));
317                        params
318                    },
319                    evaluation_method: EvaluationMethod::Direct,
320                },
321                expected_outcome: RuleOutcome {
322                    expected_result: PropertyValue::Boolean(true),
323                    tolerance: 0.0,
324                    comparison_op: ConditionOperator::Equal,
325                },
326                severity: RuleSeverity::Warning,
327            },
328        ]
329    }
330
331    /// Add new test scenario
332    pub fn add_scenario(&mut self, scenario: TestScenario) {
333        self.scenarios.insert(scenario.id.clone(), scenario);
334    }
335
336    /// Remove test scenario
337    pub fn remove_scenario(&mut self, scenario_id: &str) -> Option<TestScenario> {
338        self.scenarios.remove(scenario_id)
339    }
340
341    /// Get scenario by ID
342    #[must_use]
343    pub fn get_scenario(&self, scenario_id: &str) -> Option<&TestScenario> {
344        self.scenarios.get(scenario_id)
345    }
346
347    /// Record scenario execution
348    pub fn record_execution(&mut self, execution: ScenarioExecution) {
349        self.execution_history.push_back(execution);
350
351        // Keep only recent executions
352        while self.execution_history.len() > 1000 {
353            self.execution_history.pop_front();
354        }
355    }
356
357    /// Get execution history for scenario
358    #[must_use]
359    pub fn get_execution_history(&self, scenario_id: &str) -> Vec<&ScenarioExecution> {
360        self.execution_history
361            .iter()
362            .filter(|exec| exec.scenario_id == scenario_id)
363            .collect()
364    }
365
366    /// Generate problem from specification
367    pub fn generate_problem(&self, spec: &ProblemSpecification) -> ApplicationResult<IsingModel> {
368        // Find appropriate generator
369        let generator = self
370            .generators
371            .iter()
372            .find(|g| self.can_generate_problem_type(g, &spec.problem_type))
373            .ok_or_else(|| {
374                ApplicationError::ConfigurationError(format!(
375                    "No generator available for problem type: {:?}",
376                    spec.problem_type
377                ))
378            })?;
379
380        self.generate_with_generator(generator, spec)
381    }
382
383    /// Check if generator can handle problem type
384    fn can_generate_problem_type(
385        &self,
386        generator: &ProblemGenerator,
387        problem_type: &ProblemType,
388    ) -> bool {
389        match (generator.generator_type.clone(), problem_type) {
390            (GeneratorType::Random, ProblemType::RandomIsing) => true,
391            (GeneratorType::Structured, _) => true,
392            (GeneratorType::Benchmark, _) => true,
393            _ => false,
394        }
395    }
396
397    /// Generate problem using specific generator
398    fn generate_with_generator(
399        &self,
400        generator: &ProblemGenerator,
401        spec: &ProblemSpecification,
402    ) -> ApplicationResult<IsingModel> {
403        let size = usize::midpoint(spec.size_range.0, spec.size_range.1);
404        let mut problem = IsingModel::new(size);
405
406        match generator.generator_type {
407            GeneratorType::Random => self.generate_random_problem(&mut problem, spec, generator)?,
408            GeneratorType::Structured => {
409                self.generate_structured_problem(&mut problem, spec, generator)?;
410            }
411            _ => {
412                return Err(ApplicationError::ConfigurationError(format!(
413                    "Generator type {:?} not implemented",
414                    generator.generator_type
415                )));
416            }
417        }
418
419        Ok(problem)
420    }
421
422    /// Generate random problem
423    fn generate_random_problem(
424        &self,
425        problem: &mut IsingModel,
426        spec: &ProblemSpecification,
427        generator: &ProblemGenerator,
428    ) -> ApplicationResult<()> {
429        let size = problem.num_qubits;
430        let bias_range = generator.parameters.get("bias_range").unwrap_or(&1.0);
431        let coupling_range = generator.parameters.get("coupling_range").unwrap_or(&1.0);
432
433        // Set random biases
434        for i in 0..size {
435            let bias = (i as f64 % 10.0) / 10.0 * bias_range - bias_range / 2.0;
436            problem.set_bias(i, bias)?;
437        }
438
439        // Set random couplings based on density
440        let target_density =
441            f64::midpoint(spec.density.edge_density.0, spec.density.edge_density.1);
442        let max_edges = size * (size - 1) / 2;
443        let target_edges = (max_edges as f64 * target_density) as usize;
444
445        let mut edges_added = 0;
446        for i in 0..size {
447            for j in (i + 1)..size {
448                if edges_added >= target_edges {
449                    break;
450                }
451
452                if (i + j) % 3 == 0 {
453                    let coupling =
454                        ((i + j) as f64 % 20.0) / 20.0 * coupling_range - coupling_range / 2.0;
455                    problem.set_coupling(i, j, coupling)?;
456                    edges_added += 1;
457                }
458            }
459            if edges_added >= target_edges {
460                break;
461            }
462        }
463
464        Ok(())
465    }
466
467    /// Generate structured problem
468    fn generate_structured_problem(
469        &self,
470        problem: &mut IsingModel,
471        spec: &ProblemSpecification,
472        generator: &ProblemGenerator,
473    ) -> ApplicationResult<()> {
474        let size = problem.num_qubits;
475        let regularity = generator.parameters.get("regularity").unwrap_or(&0.8);
476        let locality = generator.parameters.get("locality").unwrap_or(&0.9);
477
478        // Create structured biases
479        for i in 0..size {
480            let bias = if (i as f64) < size as f64 * regularity {
481                // Regular pattern
482                ((i % 4) as f64 - 1.5) / 2.0
483            } else {
484                // Random component
485                (i as f64 % 7.0) / 7.0 - 0.5
486            };
487            problem.set_bias(i, bias)?;
488        }
489
490        // Create local connections
491        let local_range = (size as f64 * locality) as usize;
492        for i in 0..size {
493            let max_j = (i + local_range).min(size);
494            for j in (i + 1)..max_j {
495                if (i + j) % 2 == 0 {
496                    let coupling = ((i as f64 - j as f64).abs() / local_range as f64) * 0.5;
497                    problem.set_coupling(i, j, coupling)?;
498                }
499            }
500        }
501
502        Ok(())
503    }
504}