1use 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#[derive(Debug)]
11pub struct TestScenarioEngine {
12 pub scenarios: HashMap<String, TestScenario>,
14 pub execution_history: VecDeque<ScenarioExecution>,
16 pub generators: Vec<ProblemGenerator>,
18 pub validation_rules: Vec<ValidationRule>,
20}
21
22#[derive(Debug, Clone)]
24pub struct TestScenario {
25 pub id: String,
27 pub description: String,
29 pub problem_specs: ProblemSpecification,
31 pub expected_metrics: ExpectedMetrics,
33 pub validation_criteria: Vec<ValidationCriterion>,
35 pub timeout: Duration,
37 pub max_retries: usize,
39}
40
41#[derive(Debug, Clone)]
43pub struct ScenarioExecution {
44 pub scenario_id: String,
46 pub timestamp: Instant,
48 pub duration: Duration,
50 pub success: bool,
52 pub metrics: HashMap<String, f64>,
54 pub error: Option<String>,
56}
57
58#[derive(Debug)]
60pub struct ProblemGenerator {
61 pub id: String,
63 pub generator_type: GeneratorType,
65 pub parameters: HashMap<String, f64>,
67 pub constraints: Vec<GeneratorConstraint>,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
73pub enum GeneratorType {
74 Random,
76 Structured,
78 RealWorld,
80 Adversarial,
82 Benchmark,
84}
85
86#[derive(Debug, Clone)]
88pub struct GeneratorConstraint {
89 pub constraint_type: String,
91 pub parameters: HashMap<String, f64>,
93 pub priority: f64,
95}
96
97#[derive(Debug, Clone)]
99pub struct ValidationRule {
100 pub id: String,
102 pub description: String,
104 pub condition: RuleCondition,
106 pub expected_outcome: RuleOutcome,
108 pub severity: RuleSeverity,
110}
111
112#[derive(Debug, Clone)]
114pub struct RuleCondition {
115 pub expression: String,
117 pub parameters: HashMap<String, PropertyValue>,
119 pub evaluation_method: EvaluationMethod,
121}
122
123#[derive(Debug, Clone)]
125pub struct RuleOutcome {
126 pub expected_result: PropertyValue,
128 pub tolerance: f64,
130 pub comparison_op: ConditionOperator,
132}
133
134#[derive(Debug, Clone, PartialEq, Eq)]
136pub enum RuleSeverity {
137 Critical,
139 Warning,
141 Info,
143}
144
145#[derive(Debug, Clone, PartialEq, Eq)]
147pub enum EvaluationMethod {
148 Direct,
150 Statistical,
152 MachineLearning,
154 Custom(String),
156}
157
158impl TestScenarioEngine {
159 #[must_use]
160 pub fn new() -> Self {
161 let mut scenarios = HashMap::new();
162
163 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 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 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 pub fn add_scenario(&mut self, scenario: TestScenario) {
333 self.scenarios.insert(scenario.id.clone(), scenario);
334 }
335
336 pub fn remove_scenario(&mut self, scenario_id: &str) -> Option<TestScenario> {
338 self.scenarios.remove(scenario_id)
339 }
340
341 #[must_use]
343 pub fn get_scenario(&self, scenario_id: &str) -> Option<&TestScenario> {
344 self.scenarios.get(scenario_id)
345 }
346
347 pub fn record_execution(&mut self, execution: ScenarioExecution) {
349 self.execution_history.push_back(execution);
350
351 while self.execution_history.len() > 1000 {
353 self.execution_history.pop_front();
354 }
355 }
356
357 #[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 pub fn generate_problem(&self, spec: &ProblemSpecification) -> ApplicationResult<IsingModel> {
368 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 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 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 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 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 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 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 for i in 0..size {
480 let bias = if (i as f64) < size as f64 * regularity {
481 ((i % 4) as f64 - 1.5) / 2.0
483 } else {
484 (i as f64 % 7.0) / 7.0 - 0.5
486 };
487 problem.set_bias(i, bias)?;
488 }
489
490 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}