1use crate::config::{CutDirectionPreference, CuttingConfig};
23use crate::contour::{ContourType, CutContour};
24use crate::cost::point_distance;
25use crate::result::CutDirection;
26
27#[derive(Debug, Clone)]
29pub struct PierceCandidate {
30 pub contour_id: usize,
32 pub candidate_index: usize,
34 pub point: (f64, f64),
36 pub vertex_index: usize,
38 pub direction: CutDirection,
40 pub end_point: (f64, f64),
42}
43
44#[derive(Debug, Clone)]
46pub struct GtspCluster {
47 pub contour_id: usize,
49 pub candidates: Vec<PierceCandidate>,
51}
52
53#[derive(Debug, Clone)]
55pub struct GtspInstance {
56 pub clusters: Vec<GtspCluster>,
58 pub distances: Vec<Vec<f64>>,
63 pub home_distances: Vec<f64>,
65 pub cluster_offsets: Vec<usize>,
67 pub total_candidates: usize,
69}
70
71impl GtspInstance {
72 pub fn global_to_local(&self, global_idx: usize) -> (usize, usize) {
74 for (c, offset) in self.cluster_offsets.iter().enumerate() {
75 let size = self.clusters[c].candidates.len();
76 if global_idx >= *offset && global_idx < offset + size {
77 return (c, global_idx - offset);
78 }
79 }
80 (self.clusters.len() - 1, 0)
82 }
83
84 pub fn local_to_global(&self, cluster_idx: usize, candidate_idx: usize) -> usize {
86 self.cluster_offsets[cluster_idx] + candidate_idx
87 }
88
89 pub fn candidate(&self, global_idx: usize) -> &PierceCandidate {
91 let (c, l) = self.global_to_local(global_idx);
92 &self.clusters[c].candidates[l]
93 }
94}
95
96pub fn discretize_contours(contours: &[CutContour], config: &CuttingConfig) -> Vec<GtspCluster> {
106 let n_candidates = config.pierce_candidates.max(1);
107
108 contours
109 .iter()
110 .map(|contour| {
111 let direction = determine_direction(contour.contour_type, config);
112 let candidates = if n_candidates == 1 {
113 vec![PierceCandidate {
115 contour_id: contour.id,
116 candidate_index: 0,
117 point: contour.vertices[0],
118 vertex_index: 0,
119 direction,
120 end_point: contour.vertices[0],
121 }]
122 } else {
123 generate_equidistant_candidates(contour, n_candidates, direction)
124 };
125
126 GtspCluster {
127 contour_id: contour.id,
128 candidates,
129 }
130 })
131 .collect()
132}
133
134pub fn build_gtsp_instance(clusters: Vec<GtspCluster>, home: (f64, f64)) -> GtspInstance {
145 let mut cluster_offsets = Vec::with_capacity(clusters.len());
147 let mut offset = 0;
148 for cluster in &clusters {
149 cluster_offsets.push(offset);
150 offset += cluster.candidates.len();
151 }
152 let total = offset;
153
154 let all_candidates: Vec<&PierceCandidate> =
156 clusters.iter().flat_map(|c| c.candidates.iter()).collect();
157
158 let mut distances = vec![vec![f64::MAX; total]; total];
160 for (i, ci) in all_candidates.iter().enumerate() {
161 for (j, cj) in all_candidates.iter().enumerate() {
162 if ci.contour_id == cj.contour_id {
163 continue; }
165 distances[i][j] = point_distance(ci.end_point, cj.point);
166 }
167 }
168
169 let home_distances: Vec<f64> = all_candidates
171 .iter()
172 .map(|c| point_distance(home, c.point))
173 .collect();
174
175 GtspInstance {
176 clusters,
177 distances,
178 home_distances,
179 cluster_offsets,
180 total_candidates: total,
181 }
182}
183
184pub fn evaluate_solution(instance: &GtspInstance, solution: &[usize]) -> f64 {
188 if solution.is_empty() {
189 return 0.0;
190 }
191
192 let mut total = instance.home_distances[solution[0]];
193
194 for i in 1..solution.len() {
195 total += instance.distances[solution[i - 1]][solution[i]];
196 }
197
198 total
199}
200
201pub fn solve_nn(instance: &GtspInstance) -> Vec<usize> {
206 let n_clusters = instance.clusters.len();
207 if n_clusters == 0 {
208 return Vec::new();
209 }
210
211 let mut visited_clusters = vec![false; n_clusters];
212 let mut solution = Vec::with_capacity(n_clusters);
213
214 let mut best_idx = 0;
216 let mut best_dist = f64::MAX;
217 for (g, dist) in instance.home_distances.iter().enumerate() {
218 if *dist < best_dist {
219 best_dist = *dist;
220 best_idx = g;
221 }
222 }
223
224 let (cluster, _) = instance.global_to_local(best_idx);
225 visited_clusters[cluster] = true;
226 solution.push(best_idx);
227
228 for _ in 1..n_clusters {
230 let last = *solution.last().expect("solution not empty");
231 let mut next_best = 0;
232 let mut next_dist = f64::MAX;
233
234 for (g, &dist) in instance.distances[last].iter().enumerate() {
235 let (c, _) = instance.global_to_local(g);
236 if visited_clusters[c] {
237 continue;
238 }
239 if dist < next_dist {
240 next_dist = dist;
241 next_best = g;
242 }
243 }
244
245 let (c, _) = instance.global_to_local(next_best);
246 visited_clusters[c] = true;
247 solution.push(next_best);
248 }
249
250 solution
251}
252
253pub fn solve_constrained(
262 instance: &GtspInstance,
263 dag: &crate::hierarchy::CuttingDag,
264 max_2opt_iterations: usize,
265) -> Vec<usize> {
266 let n_clusters = instance.clusters.len();
267 if n_clusters == 0 {
268 return Vec::new();
269 }
270
271 let mut solution = nn_constrained(instance, dag);
273
274 if max_2opt_iterations > 0 && solution.len() >= 3 {
276 improve_2opt_constrained(&mut solution, instance, dag, max_2opt_iterations);
277 }
278
279 solution
280}
281
282fn nn_constrained(instance: &GtspInstance, dag: &crate::hierarchy::CuttingDag) -> Vec<usize> {
284 let n_clusters = instance.clusters.len();
285 let mut visited_clusters = vec![false; n_clusters];
286 let mut solution = Vec::with_capacity(n_clusters);
287 let mut visited_contours: std::collections::HashSet<usize> =
288 std::collections::HashSet::with_capacity(n_clusters);
289
290 for _ in 0..n_clusters {
291 let mut best_idx = None;
292 let mut best_dist = f64::MAX;
293
294 for (ci, cluster) in instance.clusters.iter().enumerate() {
295 if visited_clusters[ci] {
296 continue;
297 }
298
299 let predecessors = dag.predecessors(cluster.contour_id);
301 let ready = predecessors
302 .iter()
303 .all(|pred_id| visited_contours.contains(pred_id));
304 if !ready {
305 continue;
306 }
307
308 for cand in &cluster.candidates {
310 let global = instance.local_to_global(ci, cand.candidate_index);
311 let dist = if solution.is_empty() {
312 instance.home_distances[global]
313 } else {
314 let last: usize = *solution.last().expect("solution not empty");
315 let row: &Vec<f64> = &instance.distances[last];
316 row[global]
317 };
318
319 if dist < best_dist {
320 best_dist = dist;
321 best_idx = Some((ci, global));
322 }
323 }
324 }
325
326 if let Some((ci, global)) = best_idx {
327 visited_clusters[ci] = true;
328 visited_contours.insert(instance.clusters[ci].contour_id);
329 solution.push(global);
330 }
331 }
332
333 solution
334}
335
336fn improve_2opt_constrained(
341 solution: &mut [usize],
342 instance: &GtspInstance,
343 dag: &crate::hierarchy::CuttingDag,
344 max_iterations: usize,
345) {
346 let n = solution.len();
347 let mut improved = true;
348 let mut iterations = 0;
349 let mut current_cost = evaluate_solution(instance, solution);
350
351 while improved && iterations < max_iterations {
352 improved = false;
353 iterations += 1;
354
355 for pos in 0..n {
357 let current_global = solution[pos];
358 let (ci, _) = instance.global_to_local(current_global);
359 let cluster = &instance.clusters[ci];
360
361 for cand in &cluster.candidates {
362 let alt_global = instance.local_to_global(ci, cand.candidate_index);
363 if alt_global == current_global {
364 continue;
365 }
366
367 solution[pos] = alt_global;
368 let new_cost = evaluate_solution(instance, solution);
369
370 if new_cost < current_cost - 1e-10 {
371 current_cost = new_cost;
372 improved = true;
373 } else {
374 solution[pos] = current_global; }
376 }
377 }
378
379 for i in 0..n.saturating_sub(1) {
381 for j in (i + 2)..n {
382 solution[i + 1..=j].reverse();
383
384 let cluster_order: Vec<usize> = solution
386 .iter()
387 .map(|&g| instance.clusters[instance.global_to_local(g).0].contour_id)
388 .collect();
389
390 if dag.is_valid_sequence(&cluster_order) {
391 let new_cost = evaluate_solution(instance, solution);
392 if new_cost < current_cost - 1e-10 {
393 current_cost = new_cost;
394 improved = true;
395 } else {
396 solution[i + 1..=j].reverse(); }
398 } else {
399 solution[i + 1..=j].reverse(); }
401 }
402 }
403 }
404}
405
406fn determine_direction(contour_type: ContourType, config: &CuttingConfig) -> CutDirection {
408 let pref = match contour_type {
409 ContourType::Exterior => config.exterior_direction,
410 ContourType::Interior => config.interior_direction,
411 };
412
413 match pref {
414 CutDirectionPreference::Ccw => CutDirection::Ccw,
415 CutDirectionPreference::Cw => CutDirection::Cw,
416 CutDirectionPreference::Auto => match contour_type {
417 ContourType::Exterior => CutDirection::Ccw,
418 ContourType::Interior => CutDirection::Cw,
419 },
420 }
421}
422
423fn generate_equidistant_candidates(
425 contour: &CutContour,
426 n: usize,
427 direction: CutDirection,
428) -> Vec<PierceCandidate> {
429 let vertices = &contour.vertices;
430 let nv = vertices.len();
431 if nv == 0 {
432 return Vec::new();
433 }
434
435 let mut edge_lengths = Vec::with_capacity(nv);
437 let mut cumulative = Vec::with_capacity(nv + 1);
438 cumulative.push(0.0);
439
440 for i in 0..nv {
441 let j = (i + 1) % nv;
442 let len = point_distance(vertices[i], vertices[j]);
443 edge_lengths.push(len);
444 cumulative.push(cumulative[i] + len);
445 }
446
447 let perimeter = *cumulative.last().expect("at least one vertex");
448 if perimeter < 1e-12 {
449 return vec![PierceCandidate {
450 contour_id: contour.id,
451 candidate_index: 0,
452 point: vertices[0],
453 vertex_index: 0,
454 direction,
455 end_point: vertices[0],
456 }];
457 }
458
459 let spacing = perimeter / n as f64;
460 let mut candidates = Vec::with_capacity(n);
461
462 for k in 0..n {
463 let target_dist = k as f64 * spacing;
464
465 let (point, vertex_idx) =
467 point_at_distance(vertices, &cumulative, &edge_lengths, target_dist);
468
469 candidates.push(PierceCandidate {
470 contour_id: contour.id,
471 candidate_index: k,
472 point,
473 vertex_index: vertex_idx,
474 direction,
475 end_point: point, });
477 }
478
479 candidates
480}
481
482fn point_at_distance(
484 vertices: &[(f64, f64)],
485 cumulative: &[f64],
486 edge_lengths: &[f64],
487 distance: f64,
488) -> ((f64, f64), usize) {
489 let nv = vertices.len();
490 let perimeter = cumulative[nv];
491 let dist = distance % perimeter;
492
493 for i in 0..nv {
494 if dist >= cumulative[i] && dist <= cumulative[i + 1] + 1e-12 {
495 let edge_len = edge_lengths[i];
496 if edge_len < 1e-12 {
497 return (vertices[i], i);
498 }
499 let t = (dist - cumulative[i]) / edge_len;
500 let j = (i + 1) % nv;
501 let px = vertices[i].0 + t * (vertices[j].0 - vertices[i].0);
502 let py = vertices[i].1 + t * (vertices[j].1 - vertices[i].1);
503 return ((px, py), i);
504 }
505 }
506
507 (vertices[nv - 1], nv - 1)
509}
510
511#[cfg(test)]
512mod tests {
513 use super::*;
514
515 fn make_rect(id: usize, x: f64, y: f64, w: f64, h: f64) -> CutContour {
516 CutContour {
517 id,
518 geometry_id: format!("part{}", id),
519 instance: 0,
520 contour_type: ContourType::Exterior,
521 vertices: vec![(x, y), (x + w, y), (x + w, y + h), (x, y + h)],
522 perimeter: 2.0 * (w + h),
523 centroid: (x + w / 2.0, y + h / 2.0),
524 }
525 }
526
527 #[test]
528 fn test_discretize_single_candidate() {
529 let contours = vec![make_rect(0, 0.0, 0.0, 10.0, 10.0)];
530 let config = CuttingConfig::new().with_pierce_candidates(1);
531 let clusters = discretize_contours(&contours, &config);
532
533 assert_eq!(clusters.len(), 1);
534 assert_eq!(clusters[0].candidates.len(), 1);
535 assert_eq!(clusters[0].candidates[0].point, (0.0, 0.0));
536 }
537
538 #[test]
539 fn test_discretize_four_candidates_on_square() {
540 let contours = vec![make_rect(0, 0.0, 0.0, 10.0, 10.0)];
541 let config = CuttingConfig::new().with_pierce_candidates(4);
542 let clusters = discretize_contours(&contours, &config);
543
544 assert_eq!(clusters.len(), 1);
545 let cands = &clusters[0].candidates;
546 assert_eq!(cands.len(), 4);
547
548 assert!((cands[0].point.0 - 0.0).abs() < 1e-10);
552 assert!((cands[0].point.1 - 0.0).abs() < 1e-10);
553 assert!((cands[1].point.0 - 10.0).abs() < 1e-10);
554 assert!((cands[1].point.1 - 0.0).abs() < 1e-10);
555 assert!((cands[2].point.0 - 10.0).abs() < 1e-10);
556 assert!((cands[2].point.1 - 10.0).abs() < 1e-10);
557 assert!((cands[3].point.0 - 0.0).abs() < 1e-10);
558 assert!((cands[3].point.1 - 10.0).abs() < 1e-10);
559 }
560
561 #[test]
562 fn test_discretize_eight_candidates_midpoints() {
563 let contours = vec![make_rect(0, 0.0, 0.0, 10.0, 10.0)];
564 let config = CuttingConfig::new().with_pierce_candidates(8);
565 let clusters = discretize_contours(&contours, &config);
566
567 let cands = &clusters[0].candidates;
568 assert_eq!(cands.len(), 8);
569
570 assert!((cands[1].point.0 - 5.0).abs() < 1e-10);
573 assert!((cands[1].point.1 - 0.0).abs() < 1e-10);
574 assert!((cands[3].point.0 - 10.0).abs() < 1e-10);
575 assert!((cands[3].point.1 - 5.0).abs() < 1e-10);
576 }
577
578 #[test]
579 fn test_build_gtsp_instance_distances() {
580 let contours = vec![
581 make_rect(0, 0.0, 0.0, 10.0, 10.0),
582 make_rect(1, 20.0, 0.0, 10.0, 10.0),
583 ];
584 let config = CuttingConfig::new().with_pierce_candidates(1);
585 let clusters = discretize_contours(&contours, &config);
586 let instance = build_gtsp_instance(clusters, (0.0, 0.0));
587
588 assert_eq!(instance.total_candidates, 2);
589 assert_eq!(instance.clusters.len(), 2);
590
591 assert_eq!(instance.distances[0][0], f64::MAX);
593 assert_eq!(instance.distances[1][1], f64::MAX);
594
595 assert!((instance.distances[0][1] - 20.0).abs() < 1e-10);
597 assert!((instance.distances[1][0] - 20.0).abs() < 1e-10);
599
600 assert!((instance.home_distances[0] - 0.0).abs() < 1e-10);
602 assert!((instance.home_distances[1] - 20.0).abs() < 1e-10);
603 }
604
605 #[test]
606 fn test_global_to_local() {
607 let contours = vec![
608 make_rect(0, 0.0, 0.0, 10.0, 10.0),
609 make_rect(1, 20.0, 0.0, 10.0, 10.0),
610 ];
611 let config = CuttingConfig::new().with_pierce_candidates(4);
612 let clusters = discretize_contours(&contours, &config);
613 let instance = build_gtsp_instance(clusters, (0.0, 0.0));
614
615 assert_eq!(instance.total_candidates, 8);
616 assert_eq!(instance.global_to_local(0), (0, 0));
617 assert_eq!(instance.global_to_local(3), (0, 3));
618 assert_eq!(instance.global_to_local(4), (1, 0));
619 assert_eq!(instance.global_to_local(7), (1, 3));
620 }
621
622 #[test]
623 fn test_evaluate_solution() {
624 let contours = vec![
625 make_rect(0, 0.0, 0.0, 10.0, 10.0),
626 make_rect(1, 30.0, 0.0, 10.0, 10.0),
627 ];
628 let config = CuttingConfig::new().with_pierce_candidates(1);
629 let clusters = discretize_contours(&contours, &config);
630 let instance = build_gtsp_instance(clusters, (0.0, 0.0));
631
632 let cost = evaluate_solution(&instance, &[0, 1]);
634 assert!((cost - 30.0).abs() < 1e-10);
637 }
638
639 #[test]
640 fn test_solve_nn() {
641 let contours = vec![
642 make_rect(0, 50.0, 0.0, 10.0, 10.0),
643 make_rect(1, 10.0, 0.0, 10.0, 10.0),
644 make_rect(2, 30.0, 0.0, 10.0, 10.0),
645 ];
646 let config = CuttingConfig::new().with_pierce_candidates(1);
647 let clusters = discretize_contours(&contours, &config);
648 let instance = build_gtsp_instance(clusters, (0.0, 0.0));
649
650 let solution = solve_nn(&instance);
651 assert_eq!(solution.len(), 3);
652
653 let cluster_order: Vec<usize> = solution
655 .iter()
656 .map(|&g| instance.global_to_local(g).0)
657 .collect();
658 assert_eq!(cluster_order, vec![1, 2, 0]);
659 }
660
661 #[test]
662 fn test_solve_nn_empty() {
663 let instance = build_gtsp_instance(Vec::new(), (0.0, 0.0));
664 let solution = solve_nn(&instance);
665 assert!(solution.is_empty());
666 }
667
668 #[test]
669 fn test_nn_picks_best_candidate() {
670 let contours = vec![
676 make_rect(0, 0.0, 0.0, 10.0, 10.0),
677 make_rect(1, 12.0, 0.0, 10.0, 10.0),
678 ];
679 let config = CuttingConfig::new().with_pierce_candidates(4);
680 let clusters = discretize_contours(&contours, &config);
681 let instance = build_gtsp_instance(clusters, (0.0, 0.0));
682
683 let solution = solve_nn(&instance);
684 assert_eq!(solution.len(), 2);
685
686 let (c0, _) = instance.global_to_local(solution[0]);
688 assert_eq!(c0, 0);
689
690 let (c1, l1) = instance.global_to_local(solution[1]);
692 assert_eq!(c1, 1);
693 let picked = &instance.clusters[1].candidates[l1];
695 assert!((picked.point.0 - 12.0).abs() < 1e-10);
696 }
697
698 #[test]
699 fn test_direction_assignment() {
700 let mut contours = vec![make_rect(0, 0.0, 0.0, 10.0, 10.0)];
701 contours[0].contour_type = ContourType::Interior;
702
703 let config = CuttingConfig::default();
704 let clusters = discretize_contours(&contours, &config);
705
706 assert_eq!(clusters[0].candidates[0].direction, CutDirection::Cw);
708 }
709
710 #[test]
711 fn test_multi_candidate_improves_over_single() {
712 let contours = vec![
715 make_rect(0, 0.0, 0.0, 10.0, 10.0),
716 make_rect(1, 15.0, 5.0, 10.0, 10.0),
717 make_rect(2, 30.0, 0.0, 10.0, 10.0),
718 ];
719
720 let config1 = CuttingConfig::new().with_pierce_candidates(1);
722 let clusters1 = discretize_contours(&contours, &config1);
723 let inst1 = build_gtsp_instance(clusters1, (0.0, 0.0));
724 let sol1 = solve_nn(&inst1);
725 let cost1 = evaluate_solution(&inst1, &sol1);
726
727 let config8 = CuttingConfig::new().with_pierce_candidates(8);
729 let clusters8 = discretize_contours(&contours, &config8);
730 let inst8 = build_gtsp_instance(clusters8, (0.0, 0.0));
731 let sol8 = solve_nn(&inst8);
732 let cost8 = evaluate_solution(&inst8, &sol8);
733
734 assert!(
736 cost8 <= cost1 + 1e-6,
737 "Multi-candidate cost {} should be <= single-candidate cost {}",
738 cost8,
739 cost1
740 );
741 }
742
743 #[test]
744 fn test_solve_constrained_respects_precedence() {
745 use crate::hierarchy::CuttingDag;
746
747 let contours = vec![
749 CutContour {
750 id: 0,
751 geometry_id: "part1".to_string(),
752 instance: 0,
753 contour_type: ContourType::Exterior,
754 vertices: vec![(0.0, 0.0), (20.0, 0.0), (20.0, 20.0), (0.0, 20.0)],
755 perimeter: 80.0,
756 centroid: (10.0, 10.0),
757 },
758 CutContour {
759 id: 1,
760 geometry_id: "part1".to_string(),
761 instance: 0,
762 contour_type: ContourType::Interior,
763 vertices: vec![(5.0, 5.0), (15.0, 5.0), (15.0, 15.0), (5.0, 15.0)],
764 perimeter: 40.0,
765 centroid: (10.0, 10.0),
766 },
767 ];
768
769 let dag = CuttingDag::build(&contours);
770 let config = CuttingConfig::new().with_pierce_candidates(4);
771 let clusters = discretize_contours(&contours, &config);
772 let instance = build_gtsp_instance(clusters, (0.0, 0.0));
773
774 let solution = solve_constrained(&instance, &dag, 100);
775 assert_eq!(solution.len(), 2);
776
777 let cluster_order: Vec<usize> = solution
779 .iter()
780 .map(|&g| instance.clusters[instance.global_to_local(g).0].contour_id)
781 .collect();
782
783 let pos_interior = cluster_order.iter().position(|&id| id == 1).unwrap();
784 let pos_exterior = cluster_order.iter().position(|&id| id == 0).unwrap();
785 assert!(pos_interior < pos_exterior);
786 }
787
788 #[test]
789 fn test_solve_constrained_with_multiple_parts() {
790 use crate::hierarchy::CuttingDag;
791
792 let contours = vec![
793 make_rect(0, 0.0, 0.0, 10.0, 10.0),
794 make_rect(1, 15.0, 0.0, 10.0, 10.0),
795 make_rect(2, 30.0, 0.0, 10.0, 10.0),
796 ];
797
798 let dag = CuttingDag::build(&contours);
799 let config = CuttingConfig::new().with_pierce_candidates(4);
800 let clusters = discretize_contours(&contours, &config);
801 let instance = build_gtsp_instance(clusters, (0.0, 0.0));
802
803 let solution = solve_constrained(&instance, &dag, 100);
804 assert_eq!(solution.len(), 3);
805
806 let cluster_order: Vec<usize> = solution
808 .iter()
809 .map(|&g| instance.global_to_local(g).0)
810 .collect();
811 assert_eq!(cluster_order, vec![0, 1, 2]);
812 }
813
814 #[test]
815 fn test_2opt_improves_solution() {
816 use crate::hierarchy::CuttingDag;
817
818 let contours = vec![
823 make_rect(0, 0.0, 0.0, 10.0, 10.0),
824 make_rect(1, 20.0, 0.0, 10.0, 10.0),
825 make_rect(2, 40.0, 0.0, 10.0, 10.0),
826 ];
827
828 let dag = CuttingDag::build(&contours);
829 let config = CuttingConfig::new().with_pierce_candidates(4);
830 let clusters = discretize_contours(&contours, &config);
831 let instance = build_gtsp_instance(clusters, (0.0, 0.0));
832
833 let solution = solve_constrained(&instance, &dag, 100);
834 let cost = evaluate_solution(&instance, &solution);
835
836 assert!(
839 cost < 100.0,
840 "Solution cost {} should be < worst case 100",
841 cost
842 );
843 }
844
845 #[test]
846 fn test_constrained_empty() {
847 use crate::hierarchy::CuttingDag;
848
849 let contours: Vec<CutContour> = Vec::new();
850 let dag = CuttingDag::build(&contours);
851 let instance = build_gtsp_instance(Vec::new(), (0.0, 0.0));
852
853 let solution = solve_constrained(&instance, &dag, 100);
854 assert!(solution.is_empty());
855 }
856}