1use scirs2_core::ndarray::Array2;
7use scirs2_core::random::prelude::*;
8use scirs2_core::random::SeedableRng;
9use std::collections::HashMap;
10use std::time::Duration;
11
12use super::types::{
13 Constraint, ConstraintType, Difficulty, GeneratorConfig, ProblemType, TestCase, TestGenerator,
14 TestMetadata,
15};
16
17pub struct MaxCutGenerator;
19
20impl TestGenerator for MaxCutGenerator {
21 fn generate(&self, config: &GeneratorConfig) -> Result<Vec<TestCase>, String> {
22 let mut rng = if let Some(seed) = config.seed {
23 StdRng::seed_from_u64(seed)
24 } else {
25 let mut thread_rng = thread_rng();
26 StdRng::from_rng(&mut thread_rng)
27 };
28
29 let mut test_cases = Vec::new();
30
31 let n = config.size;
33 let edge_probability = match config.difficulty {
34 Difficulty::Easy => 0.3,
35 Difficulty::Medium => 0.5,
36 Difficulty::Hard => 0.7,
37 Difficulty::VeryHard => 0.9,
38 Difficulty::Extreme => 0.95,
39 };
40
41 let mut qubo = Array2::zeros((n, n));
42 let mut var_map = HashMap::new();
43
44 for i in 0..n {
45 var_map.insert(format!("x_{i}"), i);
46 }
47
48 for i in 0..n {
50 for j in i + 1..n {
51 if rng.random::<f64>() < edge_probability {
52 let weight = rng.random_range(1.0..10.0);
53 qubo[[i, i]] -= weight;
55 qubo[[j, j]] -= weight;
56 qubo[[i, j]] += 2.0 * weight;
57 qubo[[j, i]] += 2.0 * weight;
58 }
59 }
60 }
61
62 test_cases.push(TestCase {
63 id: format!("maxcut_{}_{:?}", n, config.difficulty),
64 problem_type: ProblemType::MaxCut,
65 size: n,
66 qubo,
67 var_map,
68 optimal_solution: None,
69 optimal_value: None,
70 constraints: Vec::new(),
71 metadata: TestMetadata {
72 generation_method: "Random graph".to_string(),
73 difficulty: config.difficulty.clone(),
74 expected_runtime: Duration::from_millis(100),
75 notes: format!("Edge probability: {edge_probability}"),
76 tags: vec!["graph".to_string(), "maxcut".to_string()],
77 },
78 });
79
80 Ok(test_cases)
81 }
82
83 fn name(&self) -> &'static str {
84 "MaxCutGenerator"
85 }
86
87 fn supported_types(&self) -> Vec<ProblemType> {
88 vec![ProblemType::MaxCut]
89 }
90}
91
92pub struct TSPGenerator;
94
95impl TestGenerator for TSPGenerator {
96 fn generate(&self, config: &GeneratorConfig) -> Result<Vec<TestCase>, String> {
97 let mut rng = if let Some(seed) = config.seed {
98 StdRng::seed_from_u64(seed)
99 } else {
100 let mut thread_rng = thread_rng();
101 StdRng::from_rng(&mut thread_rng)
102 };
103
104 let n_cities = config.size;
105 let mut test_cases = Vec::new();
106
107 let mut cities = Vec::new();
109 for _ in 0..n_cities {
110 cities.push((rng.random_range(0.0..100.0), rng.random_range(0.0..100.0)));
111 }
112
113 let mut distances = Array2::zeros((n_cities, n_cities));
115 for i in 0..n_cities {
116 for j in 0..n_cities {
117 if i != j {
118 let dx: f64 = cities[i].0 - cities[j].0;
119 let dy: f64 = cities[i].1 - cities[j].1;
120 distances[[i, j]] = dx.hypot(dy);
121 }
122 }
123 }
124
125 let n_vars = n_cities * n_cities;
127 let mut qubo = Array2::zeros((n_vars, n_vars));
128 let mut var_map = HashMap::new();
129
130 for i in 0..n_cities {
132 for j in 0..n_cities {
133 let idx = i * n_cities + j;
134 var_map.insert(format!("x_{i}_{j}"), idx);
135 }
136 }
137
138 for i in 0..n_cities {
140 for j in 0..n_cities {
141 for k in 0..n_cities {
142 let next_j = (j + 1) % n_cities;
143 let idx1 = i * n_cities + j;
144 let idx2 = k * n_cities + next_j;
145 qubo[[idx1, idx2]] += distances[[i, k]];
146 }
147 }
148 }
149
150 let mut constraints = Vec::new();
152 let penalty = 1000.0;
153
154 for i in 0..n_cities {
156 let vars: Vec<_> = (0..n_cities).map(|j| format!("x_{i}_{j}")).collect();
157
158 constraints.push(Constraint {
159 constraint_type: ConstraintType::ExactlyK { k: 1 },
160 variables: vars,
161 parameters: HashMap::new(),
162 penalty,
163 });
164 }
165
166 for j in 0..n_cities {
168 let vars: Vec<_> = (0..n_cities).map(|i| format!("x_{i}_{j}")).collect();
169
170 constraints.push(Constraint {
171 constraint_type: ConstraintType::ExactlyK { k: 1 },
172 variables: vars,
173 parameters: HashMap::new(),
174 penalty,
175 });
176 }
177
178 self.add_constraint_penalties(&mut qubo, &var_map, &constraints)?;
180
181 test_cases.push(TestCase {
182 id: format!("tsp_{}_{:?}", n_cities, config.difficulty),
183 problem_type: ProblemType::TSP,
184 size: n_cities,
185 qubo,
186 var_map,
187 optimal_solution: None,
188 optimal_value: None,
189 constraints,
190 metadata: TestMetadata {
191 generation_method: "Random cities".to_string(),
192 difficulty: config.difficulty.clone(),
193 expected_runtime: Duration::from_millis(500),
194 notes: format!("{n_cities} cities"),
195 tags: vec!["routing".to_string(), "tsp".to_string()],
196 },
197 });
198
199 Ok(test_cases)
200 }
201
202 fn name(&self) -> &'static str {
203 "TSPGenerator"
204 }
205
206 fn supported_types(&self) -> Vec<ProblemType> {
207 vec![ProblemType::TSP]
208 }
209}
210
211impl TSPGenerator {
212 fn add_constraint_penalties(
213 &self,
214 qubo: &mut Array2<f64>,
215 var_map: &HashMap<String, usize>,
216 constraints: &[Constraint],
217 ) -> Result<(), String> {
218 for constraint in constraints {
219 if let ConstraintType::ExactlyK { k } = &constraint.constraint_type {
220 for v1 in &constraint.variables {
222 if let Some(&idx1) = var_map.get(v1) {
223 qubo[[idx1, idx1]] +=
225 constraint.penalty * 2.0f64.mul_add(-(*k as f64), 1.0);
226
227 for v2 in &constraint.variables {
229 if v1 != v2 {
230 if let Some(&idx2) = var_map.get(v2) {
231 qubo[[idx1, idx2]] += constraint.penalty * 2.0;
232 }
233 }
234 }
235 }
236 }
237 }
238 }
239
240 Ok(())
241 }
242}
243
244pub struct GraphColoringGenerator;
246
247impl TestGenerator for GraphColoringGenerator {
248 fn generate(&self, config: &GeneratorConfig) -> Result<Vec<TestCase>, String> {
249 let mut rng = if let Some(seed) = config.seed {
250 StdRng::seed_from_u64(seed)
251 } else {
252 let mut thread_rng = thread_rng();
253 StdRng::from_rng(&mut thread_rng)
254 };
255
256 let n_vertices = config.size;
257 let n_colors = match config.difficulty {
258 Difficulty::Easy => 4,
259 Difficulty::Medium => 3,
260 _ => 3,
261 };
262
263 let mut test_cases = Vec::new();
264
265 let edge_prob = 0.3;
267 let mut edges = Vec::new();
268
269 for i in 0..n_vertices {
270 for j in i + 1..n_vertices {
271 if rng.random::<f64>() < edge_prob {
272 edges.push((i, j));
273 }
274 }
275 }
276
277 let n_vars = n_vertices * n_colors;
279 let mut qubo = Array2::zeros((n_vars, n_vars));
280 let mut var_map = HashMap::new();
281
282 for v in 0..n_vertices {
284 for c in 0..n_colors {
285 let idx = v * n_colors + c;
286 var_map.insert(format!("x_{v}_{c}"), idx);
287 }
288 }
289
290 for v in 0..n_vertices {
292 for c in 0..n_colors {
293 let idx = v * n_colors + c;
294 qubo[[idx, idx]] -= c as f64; }
296 }
297
298 let mut constraints = Vec::new();
300 let penalty = 100.0;
301
302 for v in 0..n_vertices {
304 let vars: Vec<_> = (0..n_colors).map(|c| format!("x_{v}_{c}")).collect();
305
306 constraints.push(Constraint {
307 constraint_type: ConstraintType::ExactlyK { k: 1 },
308 variables: vars,
309 parameters: HashMap::new(),
310 penalty,
311 });
312 }
313
314 for (u, v) in &edges {
316 for c in 0..n_colors {
317 let idx_u = u * n_colors + c;
318 let idx_v = v * n_colors + c;
319 qubo[[idx_u, idx_v]] += penalty;
320 qubo[[idx_v, idx_u]] += penalty;
321 }
322 }
323
324 test_cases.push(TestCase {
325 id: format!(
326 "coloring_{}_{}_{}_{:?}",
327 n_vertices,
328 n_colors,
329 edges.len(),
330 config.difficulty
331 ),
332 problem_type: ProblemType::GraphColoring,
333 size: n_vertices,
334 qubo,
335 var_map,
336 optimal_solution: None,
337 optimal_value: None,
338 constraints,
339 metadata: TestMetadata {
340 generation_method: "Random graph".to_string(),
341 difficulty: config.difficulty.clone(),
342 expected_runtime: Duration::from_millis(200),
343 notes: format!(
344 "{} vertices, {} colors, {} edges",
345 n_vertices,
346 n_colors,
347 edges.len()
348 ),
349 tags: vec!["graph".to_string(), "coloring".to_string()],
350 },
351 });
352
353 Ok(test_cases)
354 }
355
356 fn name(&self) -> &'static str {
357 "GraphColoringGenerator"
358 }
359
360 fn supported_types(&self) -> Vec<ProblemType> {
361 vec![ProblemType::GraphColoring]
362 }
363}
364
365pub struct KnapsackGenerator;
367
368impl TestGenerator for KnapsackGenerator {
369 fn generate(&self, config: &GeneratorConfig) -> Result<Vec<TestCase>, String> {
370 let mut rng = if let Some(seed) = config.seed {
371 StdRng::seed_from_u64(seed)
372 } else {
373 let mut thread_rng = thread_rng();
374 StdRng::from_rng(&mut thread_rng)
375 };
376
377 let n_items = config.size;
378 let mut test_cases = Vec::new();
379
380 let mut values = Vec::new();
382 let mut weights = Vec::new();
383
384 for _ in 0..n_items {
385 values.push(rng.random_range(1.0..100.0));
386 weights.push(rng.random_range(1.0..50.0));
387 }
388
389 let capacity = weights.iter().sum::<f64>() * 0.5; let mut qubo = Array2::zeros((n_items, n_items));
393 let mut var_map = HashMap::new();
394
395 for i in 0..n_items {
396 var_map.insert(format!("x_{i}"), i);
397 qubo[[i, i]] -= values[i];
399 }
400
401 let _penalty = values.iter().sum::<f64>() * 2.0;
403
404 test_cases.push(TestCase {
409 id: format!("knapsack_{}_{:?}", n_items, config.difficulty),
410 problem_type: ProblemType::Knapsack,
411 size: n_items,
412 qubo,
413 var_map,
414 optimal_solution: None,
415 optimal_value: None,
416 constraints: vec![],
417 metadata: TestMetadata {
418 generation_method: "Random items".to_string(),
419 difficulty: config.difficulty.clone(),
420 expected_runtime: Duration::from_millis(100),
421 notes: format!("{n_items} items, capacity: {capacity:.1}"),
422 tags: vec!["optimization".to_string(), "knapsack".to_string()],
423 },
424 });
425
426 Ok(test_cases)
427 }
428
429 fn name(&self) -> &'static str {
430 "KnapsackGenerator"
431 }
432
433 fn supported_types(&self) -> Vec<ProblemType> {
434 vec![ProblemType::Knapsack]
435 }
436}
437
438pub struct RandomQuboGenerator;
440
441impl TestGenerator for RandomQuboGenerator {
442 fn generate(&self, config: &GeneratorConfig) -> Result<Vec<TestCase>, String> {
443 let mut rng = if let Some(seed) = config.seed {
444 StdRng::seed_from_u64(seed)
445 } else {
446 let mut thread_rng = thread_rng();
447 StdRng::from_rng(&mut thread_rng)
448 };
449
450 let n = config.size;
451 let mut test_cases = Vec::new();
452
453 let mut qubo = Array2::zeros((n, n));
455 let density = match config.difficulty {
456 Difficulty::Easy => 0.3,
457 Difficulty::Medium => 0.5,
458 Difficulty::Hard => 0.7,
459 Difficulty::VeryHard => 0.9,
460 Difficulty::Extreme => 1.0,
461 };
462
463 for i in 0..n {
464 for j in i..n {
465 if rng.random::<f64>() < density {
466 let value = rng.random_range(-10.0..10.0);
467 qubo[[i, j]] = value;
468 if i != j {
469 qubo[[j, i]] = value;
470 }
471 }
472 }
473 }
474
475 let mut var_map = HashMap::new();
476 for i in 0..n {
477 var_map.insert(format!("x_{i}"), i);
478 }
479
480 test_cases.push(TestCase {
481 id: format!("random_{}_{:?}", n, config.difficulty),
482 problem_type: ProblemType::Custom {
483 name: "Random QUBO".to_string(),
484 },
485 size: n,
486 qubo,
487 var_map,
488 optimal_solution: None,
489 optimal_value: None,
490 constraints: vec![],
491 metadata: TestMetadata {
492 generation_method: "Random generation".to_string(),
493 difficulty: config.difficulty.clone(),
494 expected_runtime: Duration::from_millis(50),
495 notes: format!("Density: {density}"),
496 tags: vec!["random".to_string(), "qubo".to_string()],
497 },
498 });
499
500 Ok(test_cases)
501 }
502
503 fn name(&self) -> &'static str {
504 "RandomQuboGenerator"
505 }
506
507 fn supported_types(&self) -> Vec<ProblemType> {
508 vec![
509 ProblemType::Custom {
510 name: "Random".to_string(),
511 },
512 ProblemType::Ising,
513 ]
514 }
515}
516
517pub struct FinanceTestGenerator;
519
520impl TestGenerator for FinanceTestGenerator {
521 fn generate(&self, config: &GeneratorConfig) -> Result<Vec<TestCase>, String> {
522 let mut rng = if let Some(seed) = config.seed {
523 StdRng::seed_from_u64(seed)
524 } else {
525 let mut thread_rng = thread_rng();
526 StdRng::from_rng(&mut thread_rng)
527 };
528
529 let n_assets = config.size;
530 let mut test_cases = Vec::new();
531
532 let mut qubo = Array2::zeros((n_assets, n_assets));
534 let mut var_map = HashMap::new();
535
536 for i in 0..n_assets {
537 var_map.insert(format!("asset_{i}"), i);
538
539 let expected_return = rng.random_range(0.05..0.15);
541 qubo[[i, i]] -= expected_return;
542 }
543
544 for i in 0..n_assets {
546 for j in 0..n_assets {
547 let covariance = if i == j {
548 rng.random_range(0.01..0.04) } else {
550 rng.random_range(-0.01..0.01) };
552 qubo[[i, j]] += covariance;
553 }
554 }
555
556 test_cases.push(TestCase {
557 id: format!("portfolio_{}_{:?}", n_assets, config.difficulty),
558 problem_type: ProblemType::Portfolio,
559 size: n_assets,
560 qubo,
561 var_map,
562 optimal_solution: None,
563 optimal_value: None,
564 constraints: vec![Constraint {
565 constraint_type: ConstraintType::LinearEquality { target: 1.0 },
566 variables: (0..n_assets).map(|i| format!("asset_{i}")).collect(),
567 parameters: HashMap::new(),
568 penalty: 1000.0,
569 }],
570 metadata: TestMetadata {
571 generation_method: "Random portfolio".to_string(),
572 difficulty: config.difficulty.clone(),
573 expected_runtime: Duration::from_millis(200),
574 notes: format!("{n_assets} assets"),
575 tags: vec!["finance".to_string(), "portfolio".to_string()],
576 },
577 });
578
579 Ok(test_cases)
580 }
581
582 fn name(&self) -> &'static str {
583 "FinanceTestGenerator"
584 }
585
586 fn supported_types(&self) -> Vec<ProblemType> {
587 vec![ProblemType::Portfolio]
588 }
589}
590
591pub struct LogisticsTestGenerator;
593
594impl TestGenerator for LogisticsTestGenerator {
595 fn generate(&self, config: &GeneratorConfig) -> Result<Vec<TestCase>, String> {
596 let mut rng = if let Some(seed) = config.seed {
597 StdRng::seed_from_u64(seed)
598 } else {
599 let mut thread_rng = thread_rng();
600 StdRng::from_rng(&mut thread_rng)
601 };
602
603 let n_vehicles = 2;
604 let n_locations = config.size;
605 let mut test_cases = Vec::new();
606
607 let n_vars = n_vehicles * n_locations * n_locations;
609 let mut qubo = Array2::zeros((n_vars, n_vars));
610 let mut var_map = HashMap::new();
611
612 for v in 0..n_vehicles {
614 for i in 0..n_locations {
615 for j in 0..n_locations {
616 let idx = v * n_locations * n_locations + i * n_locations + j;
617 var_map.insert(format!("x_{v}_{i}_{j}"), idx);
618 }
619 }
620 }
621
622 for v in 0..n_vehicles {
624 for i in 0..n_locations {
625 for j in 0..n_locations {
626 if i != j {
627 let idx = v * n_locations * n_locations + i * n_locations + j;
628 let distance = rng.random_range(1.0..20.0);
629 qubo[[idx, idx]] += distance;
630 }
631 }
632 }
633 }
634
635 test_cases.push(TestCase {
636 id: format!("vrp_{}_{}_{:?}", n_vehicles, n_locations, config.difficulty),
637 problem_type: ProblemType::VRP,
638 size: n_locations,
639 qubo,
640 var_map,
641 optimal_solution: None,
642 optimal_value: None,
643 constraints: vec![],
644 metadata: TestMetadata {
645 generation_method: "Random VRP".to_string(),
646 difficulty: config.difficulty.clone(),
647 expected_runtime: Duration::from_millis(500),
648 notes: format!("{n_vehicles} vehicles, {n_locations} locations"),
649 tags: vec!["logistics".to_string(), "vrp".to_string()],
650 },
651 });
652
653 Ok(test_cases)
654 }
655
656 fn name(&self) -> &'static str {
657 "LogisticsTestGenerator"
658 }
659
660 fn supported_types(&self) -> Vec<ProblemType> {
661 vec![ProblemType::VRP]
662 }
663}
664
665pub struct ManufacturingTestGenerator;
667
668impl TestGenerator for ManufacturingTestGenerator {
669 fn generate(&self, config: &GeneratorConfig) -> Result<Vec<TestCase>, String> {
670 let mut rng = if let Some(seed) = config.seed {
671 StdRng::seed_from_u64(seed)
672 } else {
673 let mut thread_rng = thread_rng();
674 StdRng::from_rng(&mut thread_rng)
675 };
676
677 let n_jobs = config.size;
678 let n_machines = 3;
679 let mut test_cases = Vec::new();
680
681 let n_vars = n_jobs * n_machines;
683 let mut qubo = Array2::zeros((n_vars, n_vars));
684 let mut var_map = HashMap::new();
685
686 for j in 0..n_jobs {
688 for m in 0..n_machines {
689 let idx = j * n_machines + m;
690 var_map.insert(format!("job_{j}_machine_{m}"), idx);
691 }
692 }
693
694 for j in 0..n_jobs {
696 for m in 0..n_machines {
697 let idx = j * n_machines + m;
698 let processing_time = rng.random_range(1.0..10.0);
699 qubo[[idx, idx]] += processing_time;
700 }
701 }
702
703 let mut constraints = Vec::new();
705 for j in 0..n_jobs {
706 let vars: Vec<_> = (0..n_machines)
707 .map(|m| format!("job_{j}_machine_{m}"))
708 .collect();
709 constraints.push(Constraint {
710 constraint_type: ConstraintType::ExactlyK { k: 1 },
711 variables: vars,
712 parameters: HashMap::new(),
713 penalty: 100.0,
714 });
715 }
716
717 test_cases.push(TestCase {
718 id: format!(
719 "scheduling_{}_{}_{:?}",
720 n_jobs, n_machines, config.difficulty
721 ),
722 problem_type: ProblemType::JobScheduling,
723 size: n_jobs,
724 qubo,
725 var_map,
726 optimal_solution: None,
727 optimal_value: None,
728 constraints,
729 metadata: TestMetadata {
730 generation_method: "Random job scheduling".to_string(),
731 difficulty: config.difficulty.clone(),
732 expected_runtime: Duration::from_millis(300),
733 notes: format!("{n_jobs} jobs, {n_machines} machines"),
734 tags: vec!["manufacturing".to_string(), "scheduling".to_string()],
735 },
736 });
737
738 Ok(test_cases)
739 }
740
741 fn name(&self) -> &'static str {
742 "ManufacturingTestGenerator"
743 }
744
745 fn supported_types(&self) -> Vec<ProblemType> {
746 vec![ProblemType::JobScheduling]
747 }
748}
749
750pub fn default_generators() -> Vec<Box<dyn TestGenerator>> {
752 vec![
753 Box::new(MaxCutGenerator),
754 Box::new(TSPGenerator),
755 Box::new(GraphColoringGenerator),
756 Box::new(KnapsackGenerator),
757 Box::new(RandomQuboGenerator),
758 ]
759}