1use oxilean_kernel::Node;
6use oxilean_kernel::{BinderInfo, Declaration, Environment, Expr, Level, Name};
7
8use super::types::{
9 BinPackingFFD, ChristofidesHeuristic, GoemansWilliamsonRounding, GreedySetCover, KnapsackFPTAS,
10 MetricTSPInstance, PrimalDualFacility, RandomizedRounding, SetCoverInstance, FPTAS, PTAS,
11};
12
13pub fn app(f: Expr, a: Expr) -> Expr {
14 Expr::App(Node::new(f), Node::new(a))
15}
16pub fn app2(f: Expr, a: Expr, b: Expr) -> Expr {
17 app(app(f, a), b)
18}
19pub fn app3(f: Expr, a: Expr, b: Expr, c: Expr) -> Expr {
20 app(app2(f, a, b), c)
21}
22pub fn cst(s: &str) -> Expr {
23 Expr::Const(Name::str(s), vec![])
24}
25pub fn prop() -> Expr {
26 Expr::Sort(Level::zero())
27}
28pub fn type0() -> Expr {
29 Expr::Sort(Level::succ(Level::zero()))
30}
31pub fn pi(bi: BinderInfo, name: &str, dom: Expr, body: Expr) -> Expr {
32 Expr::Pi(bi, Name::str(name), Node::new(dom), Node::new(body))
33}
34pub fn arrow(a: Expr, b: Expr) -> Expr {
35 pi(BinderInfo::Default, "_", a, b)
36}
37pub fn bvar(n: u32) -> Expr {
38 Expr::BVar(n)
39}
40pub fn nat_ty() -> Expr {
41 cst("Nat")
42}
43pub fn bool_ty() -> Expr {
44 cst("Bool")
45}
46pub fn real_ty() -> Expr {
47 cst("Real")
48}
49pub fn list_ty(elem: Expr) -> Expr {
50 app(cst("List"), elem)
51}
52pub fn pair_ty(a: Expr, b: Expr) -> Expr {
53 app2(cst("Prod"), a, b)
54}
55pub fn option_ty(a: Expr) -> Expr {
56 app(cst("Option"), a)
57}
58pub fn optimization_problem_ty() -> Expr {
60 type0()
61}
62pub fn approx_algorithm_ty() -> Expr {
65 arrow(optimization_problem_ty(), type0())
66}
67pub fn approx_ratio_ty() -> Expr {
70 arrow(approx_algorithm_ty(), real_ty())
71}
72pub fn is_alpha_approx_ty() -> Expr {
75 arrow(optimization_problem_ty(), arrow(real_ty(), prop()))
76}
77pub fn opt_solution_ty() -> Expr {
80 arrow(optimization_problem_ty(), arrow(cst("String"), real_ty()))
81}
82pub fn alg_solution_ty() -> Expr {
85 arrow(approx_algorithm_ty(), arrow(cst("String"), real_ty()))
86}
87pub fn ptas_ty() -> Expr {
91 arrow(optimization_problem_ty(), prop())
92}
93pub fn fptas_ty() -> Expr {
96 arrow(optimization_problem_ty(), prop())
97}
98pub fn eptas_ty() -> Expr {
101 arrow(optimization_problem_ty(), prop())
102}
103pub fn apx_ty() -> Expr {
106 arrow(optimization_problem_ty(), prop())
107}
108pub fn apx_hard_ty() -> Expr {
111 arrow(optimization_problem_ty(), prop())
112}
113pub fn apx_complete_ty() -> Expr {
116 arrow(optimization_problem_ty(), prop())
117}
118pub fn max_snp_ty() -> Expr {
121 arrow(optimization_problem_ty(), prop())
122}
123pub fn fptas_subset_ptas_ty() -> Expr {
125 pi(
126 BinderInfo::Default,
127 "P",
128 optimization_problem_ty(),
129 arrow(app(cst("FPTAS"), bvar(0)), app(cst("PTAS"), bvar(0))),
130 )
131}
132pub fn ptas_subset_apx_ty() -> Expr {
134 pi(
135 BinderInfo::Default,
136 "P",
137 optimization_problem_ty(),
138 arrow(app(cst("PTAS"), bvar(0)), app(cst("APX"), bvar(0))),
139 )
140}
141pub fn lp_relaxation_ty() -> Expr {
144 arrow(optimization_problem_ty(), type0())
145}
146pub fn integrality_gap_ty() -> Expr {
149 arrow(lp_relaxation_ty(), real_ty())
150}
151pub fn lp_dominates_ty() -> Expr {
154 arrow(lp_relaxation_ty(), arrow(optimization_problem_ty(), prop()))
155}
156pub fn randomized_rounding_ty() -> Expr {
159 arrow(lp_relaxation_ty(), approx_algorithm_ty())
160}
161pub fn set_cover_lp_gap_ty() -> Expr {
163 prop()
164}
165pub fn vertex_cover_lp_gap_ty() -> Expr {
167 prop()
168}
169pub fn primal_dual_algorithm_ty() -> Expr {
172 arrow(optimization_problem_ty(), type0())
173}
174pub fn primal_dual_guarantee_ty() -> Expr {
177 arrow(primal_dual_algorithm_ty(), arrow(real_ty(), prop()))
178}
179pub fn weighted_vertex_cover_pd_ty() -> Expr {
181 prop()
182}
183pub fn steiner_tree_pd_ty() -> Expr {
185 prop()
186}
187pub fn local_search_algorithm_ty() -> Expr {
190 arrow(optimization_problem_ty(), type0())
191}
192pub fn local_optimum_ty() -> Expr {
195 arrow(local_search_algorithm_ty(), arrow(cst("String"), prop()))
196}
197pub fn local_search_ratio_ty() -> Expr {
200 arrow(local_search_algorithm_ty(), arrow(real_ty(), prop()))
201}
202pub fn max_cut_local_search_ty() -> Expr {
204 prop()
205}
206pub fn k_median_local_search_ty() -> Expr {
208 prop()
209}
210pub fn greedy_algorithm_ty() -> Expr {
213 arrow(optimization_problem_ty(), type0())
214}
215pub fn set_cover_greedy_ratio_ty() -> Expr {
217 prop()
218}
219pub fn max_coverage_greedy_ty() -> Expr {
221 prop()
222}
223pub fn submodular_greedy_ty() -> Expr {
225 prop()
226}
227pub fn pcp_theorem_ty() -> Expr {
231 prop()
232}
233pub fn max_sat_inapprox_ty() -> Expr {
235 prop()
236}
237pub fn clique_inapprox_ty() -> Expr {
239 prop()
240}
241pub fn set_cover_inapprox_ty() -> Expr {
243 prop()
244}
245pub fn chromatic_inapprox_ty() -> Expr {
247 prop()
248}
249pub fn max_snp_hard_apx_hard_ty() -> Expr {
251 pi(
252 BinderInfo::Default,
253 "P",
254 optimization_problem_ty(),
255 arrow(app(cst("MaxSNP"), bvar(0)), app(cst("APXHard"), bvar(0))),
256 )
257}
258pub fn vertex_cover_apx_hard_ty() -> Expr {
260 prop()
261}
262pub fn tsp_no_approx_ty() -> Expr {
264 prop()
265}
266pub fn metric_tsp_ty() -> Expr {
268 optimization_problem_ty()
269}
270pub fn christofides_serdyukov_ty() -> Expr {
276 prop()
277}
278pub fn mst_2approx_ty() -> Expr {
280 prop()
281}
282pub fn greedy_set_cover(universe_size: usize, sets: &[Vec<usize>]) -> Vec<usize> {
287 let mut covered = vec![false; universe_size];
288 let mut num_covered = 0;
289 let mut selected = Vec::new();
290 while num_covered < universe_size {
291 let best = sets
292 .iter()
293 .enumerate()
294 .filter(|&(i, _)| !selected.contains(&i))
295 .max_by_key(|(_, s)| s.iter().filter(|&&e| !covered[e]).count());
296 match best {
297 None => break,
298 Some((idx, s)) => {
299 let new_cover: usize = s.iter().filter(|&&e| !covered[e]).count();
300 if new_cover == 0 {
301 break;
302 }
303 selected.push(idx);
304 for &e in s {
305 if e < universe_size && !covered[e] {
306 covered[e] = true;
307 num_covered += 1;
308 }
309 }
310 }
311 }
312 }
313 selected
314}
315pub fn greedy_max_coverage(universe_size: usize, sets: &[Vec<usize>], k: usize) -> Vec<usize> {
318 let mut covered = vec![false; universe_size];
319 let mut selected = Vec::new();
320 for _ in 0..k {
321 let best = sets
322 .iter()
323 .enumerate()
324 .filter(|&(i, _)| !selected.contains(&i))
325 .max_by_key(|(_, s)| {
326 s.iter()
327 .filter(|&&e| e < universe_size && !covered[e])
328 .count()
329 });
330 match best {
331 None => break,
332 Some((idx, s)) => {
333 let new_cover: usize = s
334 .iter()
335 .filter(|&&e| e < universe_size && !covered[e])
336 .count();
337 if new_cover == 0 {
338 break;
339 }
340 selected.push(idx);
341 for &e in s {
342 if e < universe_size {
343 covered[e] = true;
344 }
345 }
346 }
347 }
348 }
349 selected
350}
351pub fn vertex_cover_2approx(adj: &[Vec<usize>]) -> Vec<usize> {
354 let n = adj.len();
355 let mut in_cover = vec![false; n];
356 let mut edge_covered = vec![vec![false; n]; n];
357 for u in 0..n {
358 for &v in &adj[u] {
359 if !edge_covered[u][v] && !in_cover[u] && !in_cover[v] {
360 in_cover[u] = true;
361 in_cover[v] = true;
362 for &w in &adj[u] {
363 edge_covered[u][w] = true;
364 edge_covered[w][u] = true;
365 }
366 for &w in &adj[v] {
367 edge_covered[v][w] = true;
368 edge_covered[w][v] = true;
369 }
370 }
371 }
372 }
373 (0..n).filter(|&v| in_cover[v]).collect()
374}
375pub fn kruskal_mst(n: usize, edges: &[(usize, usize, i64)]) -> (i64, Vec<(usize, usize)>) {
378 let mut sorted_edges = edges.to_vec();
379 sorted_edges.sort_by_key(|&(_, _, w)| w);
380 let mut parent: Vec<usize> = (0..n).collect();
381 let mut rank = vec![0usize; n];
382 fn find(parent: &mut Vec<usize>, x: usize) -> usize {
383 if parent[x] != x {
384 parent[x] = find(parent, parent[x]);
385 }
386 parent[x]
387 }
388 fn union(parent: &mut Vec<usize>, rank: &mut Vec<usize>, x: usize, y: usize) -> bool {
389 let rx = find(parent, x);
390 let ry = find(parent, y);
391 if rx == ry {
392 return false;
393 }
394 if rank[rx] < rank[ry] {
395 parent[rx] = ry;
396 } else if rank[rx] > rank[ry] {
397 parent[ry] = rx;
398 } else {
399 parent[ry] = rx;
400 rank[rx] += 1;
401 }
402 true
403 }
404 let mut mst_weight = 0i64;
405 let mut mst_edges = Vec::new();
406 for (u, v, w) in sorted_edges {
407 if union(&mut parent, &mut rank, u, v) {
408 mst_weight += w;
409 mst_edges.push((u, v));
410 }
411 }
412 (mst_weight, mst_edges)
413}
414pub fn metric_tsp_2approx(dist: &[Vec<i64>]) -> (i64, Vec<usize>) {
418 let n = dist.len();
419 if n == 0 {
420 return (0, vec![]);
421 }
422 if n == 1 {
423 return (0, vec![0]);
424 }
425 let mut edges = Vec::new();
426 for i in 0..n {
427 for j in (i + 1)..n {
428 edges.push((i, j, dist[i][j]));
429 }
430 }
431 let (_, mst) = kruskal_mst(n, &edges);
432 let mut mst_adj = vec![vec![]; n];
433 for (u, v) in &mst {
434 mst_adj[*u].push(*v);
435 mst_adj[*v].push(*u);
436 }
437 let mut visited = vec![false; n];
438 let mut tour = Vec::new();
439 fn dfs_preorder(u: usize, adj: &[Vec<usize>], visited: &mut Vec<bool>, tour: &mut Vec<usize>) {
440 visited[u] = true;
441 tour.push(u);
442 for &v in &adj[u] {
443 if !visited[v] {
444 dfs_preorder(v, adj, visited, tour);
445 }
446 }
447 }
448 dfs_preorder(0, &mst_adj, &mut visited, &mut tour);
449 let mut cost = 0i64;
450 for i in 0..tour.len() {
451 let u = tour[i];
452 let v = tour[(i + 1) % tour.len()];
453 cost += dist[u][v];
454 }
455 (cost, tour)
456}
457#[allow(clippy::too_many_arguments)]
461pub fn knapsack_fptas(
462 weights: &[usize],
463 values: &[i64],
464 capacity: usize,
465 epsilon: f64,
466) -> (i64, Vec<usize>) {
467 let n = weights.len();
468 if n == 0 {
469 return (0, vec![]);
470 }
471 let max_val = *values.iter().max().unwrap_or(&1);
472 let scale = (epsilon * max_val as f64 / n as f64).max(1.0);
473 let scaled: Vec<usize> = values
474 .iter()
475 .map(|&v| (v as f64 / scale) as usize)
476 .collect();
477 let max_scaled: usize = scaled.iter().sum();
478 let mut dp = vec![usize::MAX; max_scaled + 1];
479 dp[0] = 0;
480 let mut choices: Vec<Vec<Option<bool>>> = vec![vec![None; max_scaled + 1]; n + 1];
481 for i in 0..n {
482 let mut new_dp = dp.clone();
483 for j in 0..=max_scaled {
484 if dp[j] == usize::MAX {
485 continue;
486 }
487 let nj = j + scaled[i];
488 if nj <= max_scaled {
489 let new_w = dp[j].saturating_add(weights[i]);
490 if new_w <= capacity && new_w < new_dp[nj] {
491 new_dp[nj] = new_w;
492 choices[i + 1][nj] = Some(true);
493 }
494 }
495 }
496 dp = new_dp;
497 for j in 0..=max_scaled {
498 if choices[i + 1][j].is_none() {
499 choices[i + 1][j] = Some(false);
500 }
501 }
502 }
503 let best_scaled = (0..=max_scaled)
504 .filter(|&j| dp[j] <= capacity)
505 .max()
506 .unwrap_or(0);
507 let approx_value = (best_scaled as f64 * scale) as i64;
508 let mut order: Vec<usize> = (0..n).collect();
509 order.sort_by(|&a, &b| {
510 let ra = values[a] as f64 / weights[a].max(1) as f64;
511 let rb = values[b] as f64 / weights[b].max(1) as f64;
512 rb.partial_cmp(&ra).unwrap_or(std::cmp::Ordering::Equal)
513 });
514 let mut selected = Vec::new();
515 let mut rem = capacity;
516 for &i in &order {
517 if weights[i] <= rem {
518 selected.push(i);
519 rem -= weights[i];
520 }
521 }
522 let actual: i64 = selected.iter().map(|&i| values[i]).sum();
523 (actual.max(approx_value), selected)
524}
525pub fn randomized_rounding_vertex_cover(adj: &[Vec<usize>]) -> Vec<usize> {
530 let n = adj.len();
531 let mut lp_val = vec![0.0f64; n];
532 for u in 0..n {
533 for &v in &adj[u] {
534 lp_val[u] = lp_val[u].max(0.5);
535 lp_val[v] = lp_val[v].max(0.5);
536 }
537 }
538 (0..n).filter(|&v| lp_val[v] >= 0.5).collect()
539}
540pub fn local_search_max_cut(adj: &[Vec<i64>]) -> (i64, Vec<usize>) {
543 let n = adj.len();
544 if n == 0 {
545 return (0, vec![]);
546 }
547 let mut side = vec![0usize; n];
548 for i in 0..n {
549 side[i] = i % 2;
550 }
551 let cut_value = |side: &[usize]| -> i64 {
552 let mut cut = 0i64;
553 for u in 0..n {
554 for (idx, &v) in adj[u].iter().enumerate() {
555 let w = if adj[u].len() == n {
556 adj[u][v as usize]
557 } else {
558 1
559 };
560 let _ = idx;
561 let _ = v;
562 let _ = w;
563 }
564 }
565 for u in 0..n {
566 for v in (u + 1)..n {
567 if v < adj[u].len() && side[u] != side[v] {
568 cut += adj[u][v];
569 }
570 }
571 }
572 cut
573 };
574 let mut improved = true;
575 while improved {
576 improved = false;
577 let current = cut_value(&side);
578 for v in 0..n {
579 side[v] ^= 1;
580 let new_cut = cut_value(&side);
581 if new_cut > current {
582 improved = true;
583 break;
584 } else {
585 side[v] ^= 1;
586 }
587 }
588 }
589 (cut_value(&side), side)
590}
591pub fn primal_dual_weighted_vc(n: usize, edges: &[(usize, usize, i64)]) -> Vec<usize> {
595 let weights: Vec<i64> = vec![1; n];
596 let mut dual = vec![0i64; edges.len()];
597 let mut slack: Vec<i64> = weights.clone();
598 let mut in_cover = vec![false; n];
599 for (idx, &(u, v, _w)) in edges.iter().enumerate() {
600 if in_cover[u] || in_cover[v] {
601 continue;
602 }
603 let raise = slack[u].min(slack[v]);
604 dual[idx] = raise;
605 slack[u] -= raise;
606 slack[v] -= raise;
607 if slack[u] == 0 {
608 in_cover[u] = true;
609 }
610 if slack[v] == 0 {
611 in_cover[v] = true;
612 }
613 }
614 let is_covered =
615 |cover: &[bool]| -> bool { edges.iter().all(|&(u, v, _)| cover[u] || cover[v]) };
616 for v in 0..n {
617 if in_cover[v] {
618 in_cover[v] = false;
619 if !is_covered(&in_cover) {
620 in_cover[v] = true;
621 }
622 }
623 }
624 (0..n).filter(|&v| in_cover[v]).collect()
625}
626pub fn christofides_serdyukov(dist: &[Vec<i64>]) -> (i64, Vec<usize>) {
629 let n = dist.len();
630 if n == 0 {
631 return (0, vec![]);
632 }
633 if n == 1 {
634 return (0, vec![0]);
635 }
636 if n == 2 {
637 return (dist[0][1] + dist[1][0], vec![0, 1]);
638 }
639 let mut edges = Vec::new();
640 for i in 0..n {
641 for j in (i + 1)..n {
642 edges.push((i, j, dist[i][j]));
643 }
644 }
645 let (_, mst_edges) = kruskal_mst(n, &edges);
646 let mut mst_adj = vec![vec![]; n];
647 let mut degree = vec![0usize; n];
648 for (u, v) in &mst_edges {
649 mst_adj[*u].push(*v);
650 mst_adj[*v].push(*u);
651 degree[*u] += 1;
652 degree[*v] += 1;
653 }
654 let odd_verts: Vec<usize> = (0..n).filter(|&v| degree[v] % 2 == 1).collect();
655 let mut matched = vec![false; odd_verts.len()];
656 let mut matching = Vec::new();
657 for i in 0..odd_verts.len() {
658 if matched[i] {
659 continue;
660 }
661 let best = (0..odd_verts.len())
662 .filter(|&j| j != i && !matched[j])
663 .min_by_key(|&j| dist[odd_verts[i]][odd_verts[j]]);
664 if let Some(j) = best {
665 matching.push((odd_verts[i], odd_verts[j]));
666 matched[i] = true;
667 matched[j] = true;
668 }
669 }
670 let mut multi_adj = mst_adj.clone();
671 for (u, v) in &matching {
672 multi_adj[*u].push(*v);
673 multi_adj[*v].push(*u);
674 }
675 let mut adj_idx = vec![0usize; n];
676 let mut circuit = Vec::new();
677 let mut stack = vec![0usize];
678 while let Some(&cur) = stack.last() {
679 if adj_idx[cur] < multi_adj[cur].len() {
680 let next = multi_adj[cur][adj_idx[cur]];
681 adj_idx[cur] += 1;
682 stack.push(next);
683 } else {
684 circuit.push(
685 stack
686 .pop()
687 .expect("stack is non-empty: loop condition ensures non-empty"),
688 );
689 }
690 }
691 circuit.reverse();
692 let mut visited = vec![false; n];
693 let mut tour: Vec<usize> = circuit
694 .into_iter()
695 .filter(|&v| {
696 if !visited[v] {
697 visited[v] = true;
698 true
699 } else {
700 false
701 }
702 })
703 .collect();
704 for v in 0..n {
705 if !visited[v] {
706 tour.push(v);
707 }
708 }
709 let cost: i64 = (0..tour.len())
710 .map(|i| dist[tour[i]][tour[(i + 1) % tour.len()]])
711 .sum();
712 (cost, tour)
713}
714pub fn is_vertex_cover(adj: &[Vec<usize>], cover: &[usize]) -> bool {
716 let n = adj.len();
717 let in_cover: Vec<bool> = {
718 let mut v = vec![false; n];
719 for &u in cover {
720 if u < n {
721 v[u] = true;
722 }
723 }
724 v
725 };
726 for u in 0..n {
727 for &v in &adj[u] {
728 if !in_cover[u] && !in_cover[v] {
729 return false;
730 }
731 }
732 }
733 true
734}
735pub fn is_set_cover(universe_size: usize, sets: &[Vec<usize>], selected: &[usize]) -> bool {
737 let mut covered = vec![false; universe_size];
738 for &i in selected {
739 for &e in &sets[i] {
740 if e < universe_size {
741 covered[e] = true;
742 }
743 }
744 }
745 covered.iter().all(|&c| c)
746}
747pub fn build_env(env: &mut Environment) -> Result<(), String> {
749 build_approximation_algorithms_env(env)
750}
751pub fn build_approximation_algorithms_env(env: &mut Environment) -> Result<(), String> {
753 for (name, ty) in [
754 ("OptimizationProblem", optimization_problem_ty()),
755 ("ApproxAlgorithm", approx_algorithm_ty()),
756 ("ApproxRatio", approx_ratio_ty()),
757 ("IsAlphaApprox", is_alpha_approx_ty()),
758 ("OptSolution", opt_solution_ty()),
759 ("AlgSolution", alg_solution_ty()),
760 ("PTAS", ptas_ty()),
761 ("FPTAS", fptas_ty()),
762 ("EPTAS", eptas_ty()),
763 ("APX", apx_ty()),
764 ("APXHard", apx_hard_ty()),
765 ("APXComplete", apx_complete_ty()),
766 ("MaxSNP", max_snp_ty()),
767 ("LPRelaxation", lp_relaxation_ty()),
768 ("IntegralityGap", integrality_gap_ty()),
769 ("LPDominates", lp_dominates_ty()),
770 ("RandomizedRounding", randomized_rounding_ty()),
771 ("PrimalDualAlgorithm", primal_dual_algorithm_ty()),
772 ("PrimalDualGuarantee", primal_dual_guarantee_ty()),
773 ("LocalSearchAlgorithm", local_search_algorithm_ty()),
774 ("LocalOptimum", local_optimum_ty()),
775 ("LocalSearchRatio", local_search_ratio_ty()),
776 ("GreedyAlgorithm", greedy_algorithm_ty()),
777 ("MetricTSP", metric_tsp_ty()),
778 ("WeightedVertexCover", optimization_problem_ty()),
779 ("SetCoverProblem", optimization_problem_ty()),
780 ("MaxCut", optimization_problem_ty()),
781 ("MaxCoverage", optimization_problem_ty()),
782 ("KMedian", optimization_problem_ty()),
783 ("SteinerTree", optimization_problem_ty()),
784 ("MaxCliqueProblem", optimization_problem_ty()),
785 ("GraphColoringProblem", optimization_problem_ty()),
786 ("BinPacking", optimization_problem_ty()),
787 ("JobScheduling", optimization_problem_ty()),
788 ("Knapsack01", optimization_problem_ty()),
789 ] {
790 env.add(Declaration::Axiom {
791 name: Name::str(name),
792 univ_params: vec![],
793 ty,
794 })
795 .ok();
796 }
797 for (name, ty) in [
798 ("ApproxAlg.fptas_subset_ptas", fptas_subset_ptas_ty()),
799 ("ApproxAlg.ptas_subset_apx", ptas_subset_apx_ty()),
800 ("ApproxAlg.pcp_theorem", pcp_theorem_ty()),
801 ("ApproxAlg.max_sat_inapprox", max_sat_inapprox_ty()),
802 ("ApproxAlg.clique_inapprox", clique_inapprox_ty()),
803 ("ApproxAlg.set_cover_inapprox", set_cover_inapprox_ty()),
804 ("ApproxAlg.chromatic_inapprox", chromatic_inapprox_ty()),
805 (
806 "ApproxAlg.max_snp_hard_apx_hard",
807 max_snp_hard_apx_hard_ty(),
808 ),
809 (
810 "ApproxAlg.vertex_cover_apx_hard",
811 vertex_cover_apx_hard_ty(),
812 ),
813 ("ApproxAlg.tsp_no_approx", tsp_no_approx_ty()),
814 (
815 "ApproxAlg.christofides_serdyukov",
816 christofides_serdyukov_ty(),
817 ),
818 ("ApproxAlg.mst_2approx", mst_2approx_ty()),
819 (
820 "ApproxAlg.set_cover_greedy_ratio",
821 set_cover_greedy_ratio_ty(),
822 ),
823 ("ApproxAlg.max_coverage_greedy", max_coverage_greedy_ty()),
824 ("ApproxAlg.submodular_greedy", submodular_greedy_ty()),
825 ("ApproxAlg.max_cut_local_search", max_cut_local_search_ty()),
826 (
827 "ApproxAlg.k_median_local_search",
828 k_median_local_search_ty(),
829 ),
830 (
831 "ApproxAlg.weighted_vertex_cover_pd",
832 weighted_vertex_cover_pd_ty(),
833 ),
834 ("ApproxAlg.steiner_tree_pd", steiner_tree_pd_ty()),
835 ("ApproxAlg.set_cover_lp_gap", set_cover_lp_gap_ty()),
836 ("ApproxAlg.vertex_cover_lp_gap", vertex_cover_lp_gap_ty()),
837 ] {
838 env.add(Declaration::Axiom {
839 name: Name::str(name),
840 univ_params: vec![],
841 ty,
842 })
843 .ok();
844 }
845 Ok(())
846}
847#[cfg(test)]
848mod tests {
849 use super::*;
850 #[test]
851 fn test_greedy_set_cover() {
852 let sets = vec![vec![0, 1, 2], vec![1, 2, 3], vec![3, 4], vec![0, 4]];
853 let selected = greedy_set_cover(5, &sets);
854 assert!(
855 is_set_cover(5, &sets, &selected),
856 "Greedy should produce a valid set cover"
857 );
858 }
859 #[test]
860 fn test_greedy_max_coverage() {
861 let sets = vec![vec![0, 1, 2], vec![2, 3, 4], vec![0, 3]];
862 let selected = greedy_max_coverage(5, &sets, 2);
863 assert_eq!(selected.len(), 2, "Should select exactly 2 sets");
864 let covered: std::collections::HashSet<usize> = selected
865 .iter()
866 .flat_map(|&i| sets[i].iter().cloned())
867 .collect();
868 assert!(covered.len() >= 4, "Should cover at least 4 elements");
869 }
870 #[test]
871 fn test_vertex_cover_2approx() {
872 let adj = vec![vec![1, 2], vec![0, 2], vec![0, 1]];
873 let cover = vertex_cover_2approx(&adj);
874 assert!(
875 is_vertex_cover(&adj, &cover),
876 "2-approx should produce a valid vertex cover"
877 );
878 assert!(cover.len() <= 4, "Cover size should be at most 2 * OPT = 4");
879 }
880 #[test]
881 fn test_metric_tsp_2approx() {
882 let dist = vec![
883 vec![0, 1, 1, 1],
884 vec![1, 0, 1, 1],
885 vec![1, 1, 0, 1],
886 vec![1, 1, 1, 0],
887 ];
888 let (cost, tour) = metric_tsp_2approx(&dist);
889 assert_eq!(tour.len(), 4, "Tour should visit all 4 vertices");
890 assert!(cost >= 4 && cost <= 8, "Cost {} should be in [4, 8]", cost);
891 }
892 #[test]
893 fn test_christofides_serdyukov() {
894 let dist = vec![
895 vec![0, 1, 2, 3],
896 vec![1, 0, 1, 2],
897 vec![2, 1, 0, 1],
898 vec![3, 2, 1, 0],
899 ];
900 let (cost, tour) = christofides_serdyukov(&dist);
901 assert_eq!(tour.len(), 4, "Tour should visit all 4 cities");
902 assert!(cost <= 10, "Christofides cost {} should be β€ 10", cost);
903 }
904 #[test]
905 fn test_knapsack_fptas() {
906 let weights = vec![2, 3, 4, 5];
907 let values = vec![3, 4, 5, 7];
908 let capacity = 8;
909 let (approx_val, selected) = knapsack_fptas(&weights, &values, capacity, 0.1);
910 let total_weight: usize = selected.iter().map(|&i| weights[i]).sum();
911 assert!(
912 total_weight <= capacity,
913 "Weight {} exceeds capacity {}",
914 total_weight,
915 capacity
916 );
917 let optimal = 11i64;
918 assert!(
919 approx_val >= (optimal as f64 * 0.85) as i64,
920 "FPTAS value {} should be close to optimal {}",
921 approx_val,
922 optimal
923 );
924 }
925 #[test]
926 fn test_primal_dual_weighted_vc() {
927 let adj = vec![vec![1, 2], vec![0, 2], vec![0, 1]];
928 let edges = vec![(0, 1, 1), (0, 2, 1), (1, 2, 1)];
929 let cover = primal_dual_weighted_vc(3, &edges);
930 assert!(
931 is_vertex_cover(&adj, &cover),
932 "Primal-dual should produce a valid vertex cover"
933 );
934 }
935 #[test]
936 fn test_build_approximation_algorithms_env() {
937 let mut env = Environment::new();
938 let result = build_approximation_algorithms_env(&mut env);
939 assert!(result.is_ok(), "build should succeed");
940 assert!(env.get(&Name::str("PTAS")).is_some());
941 assert!(env.get(&Name::str("FPTAS")).is_some());
942 assert!(env.get(&Name::str("APX")).is_some());
943 assert!(env.get(&Name::str("MetricTSP")).is_some());
944 }
945}
946pub fn dependent_rounding_ty() -> Expr {
952 arrow(lp_relaxation_ty(), approx_algorithm_ty())
953}
954pub fn correlation_rounding_ty() -> Expr {
959 arrow(lp_relaxation_ty(), approx_algorithm_ty())
960}
961pub fn pipage_rounding_ty() -> Expr {
967 arrow(lp_relaxation_ty(), approx_algorithm_ty())
968}
969pub fn randomized_rounding_gap_ty() -> Expr {
974 arrow(real_ty(), prop())
975}
976pub fn lp_hierarchy_ty() -> Expr {
981 arrow(optimization_problem_ty(), arrow(nat_ty(), type0()))
982}
983pub fn tightness_integrality_gap_ty() -> Expr {
987 arrow(lp_relaxation_ty(), arrow(real_ty(), prop()))
988}
989pub fn sdp_relaxation_ty() -> Expr {
993 arrow(optimization_problem_ty(), type0())
994}
995pub fn goemans_williamson_max_cut_ty() -> Expr {
1001 prop()
1002}
1003pub fn goemans_williamson_ratio_ty() -> Expr {
1007 real_ty()
1008}
1009pub fn lasserre_hierarchy_ty() -> Expr {
1014 arrow(optimization_problem_ty(), arrow(nat_ty(), type0()))
1015}
1016pub fn sdp_integrality_gap_ty() -> Expr {
1020 arrow(sdp_relaxation_ty(), real_ty())
1021}
1022pub fn unique_games_conj_ty() -> Expr {
1028 prop()
1029}
1030pub fn sdp_max_cut_gap_optimal_ty() -> Expr {
1034 prop()
1035}
1036pub fn hypergraph_cut_sdp_ty() -> Expr {
1040 arrow(real_ty(), prop())
1041}
1042pub fn steiner_tree_approx_ty() -> Expr {
1047 arrow(real_ty(), prop())
1048}
1049pub fn jain_iterative_rounding_ty() -> Expr {
1056 prop()
1057}
1058pub fn k_connectivity_approx_ty() -> Expr {
1063 arrow(nat_ty(), arrow(real_ty(), prop()))
1064}
1065pub fn steiner_forest_approx_ty() -> Expr {
1070 arrow(real_ty(), prop())
1071}
1072pub fn iterative_rounding_thm_ty() -> Expr {
1078 prop()
1079}
1080pub fn network_design_lp_ty() -> Expr {
1085 type0()
1086}
1087pub fn k_means_plus_plus_ty() -> Expr {
1093 arrow(optimization_problem_ty(), prop())
1094}
1095pub fn k_median_approx_ty() -> Expr {
1100 arrow(real_ty(), prop())
1101}
1102pub fn facility_location_bicriteria_ty() -> Expr {
1107 arrow(real_ty(), arrow(real_ty(), prop()))
1108}
1109pub fn list_scheduling_approx_ty() -> Expr {
1114 arrow(real_ty(), prop())
1115}
1116pub fn makespan_ptas_ty() -> Expr {
1121 prop()
1122}
1123pub fn preemptive_schedule_approx_ty() -> Expr {
1128 prop()
1129}
1130pub fn online_algorithm_competitive_ty() -> Expr {
1135 arrow(optimization_problem_ty(), arrow(real_ty(), prop()))
1136}
1137pub fn ski_rental_breakeven_ty() -> Expr {
1143 prop()
1144}
1145pub fn load_balancing_online_ty() -> Expr {
1150 arrow(real_ty(), prop())
1151}
1152pub fn arora_ptas_euclidean_tsp_ty() -> Expr {
1157 prop()
1158}
1159pub fn mitchell_ptas_euclidean_tsp_ty() -> Expr {
1163 prop()
1164}
1165pub fn gap_amplification_ty() -> Expr {
1170 prop()
1171}
1172pub fn inapproximability_reduction_ty() -> Expr {
1177 prop()
1178}
1179pub fn build_approximation_algorithms_ext_env(env: &mut Environment) -> Result<(), String> {
1181 for (name, ty) in [
1182 ("DependentRounding", dependent_rounding_ty()),
1183 ("CorrelationRounding", correlation_rounding_ty()),
1184 ("PipageRounding", pipage_rounding_ty()),
1185 ("RandomizedRoundingGap", randomized_rounding_gap_ty()),
1186 ("LPHierarchy", lp_hierarchy_ty()),
1187 ("TightnessIntegralityGap", tightness_integrality_gap_ty()),
1188 ("SDPRelaxation", sdp_relaxation_ty()),
1189 ("GoemansWilliamsonRatio", goemans_williamson_ratio_ty()),
1190 ("LasserreHierarchy", lasserre_hierarchy_ty()),
1191 ("SDPIntegralityGap", sdp_integrality_gap_ty()),
1192 ("UniqueGamesConj", unique_games_conj_ty()),
1193 ("HypergraphCutSDP", hypergraph_cut_sdp_ty()),
1194 ("SteinerTreeApprox", steiner_tree_approx_ty()),
1195 ("SteinerForestApprox", steiner_forest_approx_ty()),
1196 ("KConnectivityApprox", k_connectivity_approx_ty()),
1197 ("NetworkDesignLP", network_design_lp_ty()),
1198 ("KMeansPlusPlus", k_means_plus_plus_ty()),
1199 ("KMedianApprox", k_median_approx_ty()),
1200 (
1201 "FacilityLocationBicriteria",
1202 facility_location_bicriteria_ty(),
1203 ),
1204 ("ListSchedulingApprox", list_scheduling_approx_ty()),
1205 (
1206 "OnlineAlgorithmCompetitive",
1207 online_algorithm_competitive_ty(),
1208 ),
1209 ("LoadBalancingOnline", load_balancing_online_ty()),
1210 (
1211 "InapproximabilityReduction",
1212 inapproximability_reduction_ty(),
1213 ),
1214 ] {
1215 env.add(Declaration::Axiom {
1216 name: Name::str(name),
1217 univ_params: vec![],
1218 ty,
1219 })
1220 .ok();
1221 }
1222 for (name, ty) in [
1223 (
1224 "ApproxAlg.goemans_williamson_max_cut",
1225 goemans_williamson_max_cut_ty(),
1226 ),
1227 (
1228 "ApproxAlg.sdp_max_cut_gap_optimal",
1229 sdp_max_cut_gap_optimal_ty(),
1230 ),
1231 ("ApproxAlg.unique_games_conj", unique_games_conj_ty()),
1232 (
1233 "ApproxAlg.jain_iterative_rounding",
1234 jain_iterative_rounding_ty(),
1235 ),
1236 (
1237 "ApproxAlg.iterative_rounding_thm",
1238 iterative_rounding_thm_ty(),
1239 ),
1240 ("ApproxAlg.k_means_plus_plus", k_means_plus_plus_ty()),
1241 ("ApproxAlg.makespan_ptas", makespan_ptas_ty()),
1242 (
1243 "ApproxAlg.preemptive_schedule",
1244 preemptive_schedule_approx_ty(),
1245 ),
1246 ("ApproxAlg.ski_rental", ski_rental_breakeven_ty()),
1247 (
1248 "ApproxAlg.arora_ptas_euclidean_tsp",
1249 arora_ptas_euclidean_tsp_ty(),
1250 ),
1251 (
1252 "ApproxAlg.mitchell_ptas_euclidean_tsp",
1253 mitchell_ptas_euclidean_tsp_ty(),
1254 ),
1255 ("ApproxAlg.gap_amplification", gap_amplification_ty()),
1256 ] {
1257 env.add(Declaration::Axiom {
1258 name: Name::str(name),
1259 univ_params: vec![],
1260 ty,
1261 })
1262 .ok();
1263 }
1264 Ok(())
1265}
1266#[cfg(test)]
1267mod tests_ext {
1268 use super::*;
1269 fn ext_env() -> Environment {
1270 let mut env = Environment::new();
1271 build_approximation_algorithms_env(&mut env).expect("base env failed");
1272 build_approximation_algorithms_ext_env(&mut env).expect("ext env failed");
1273 env
1274 }
1275 #[test]
1276 fn test_lp_rounding_axioms_registered() {
1277 let env = ext_env();
1278 assert!(env.get(&Name::str("DependentRounding")).is_some());
1279 assert!(env.get(&Name::str("CorrelationRounding")).is_some());
1280 assert!(env.get(&Name::str("PipageRounding")).is_some());
1281 assert!(env.get(&Name::str("LPHierarchy")).is_some());
1282 }
1283 #[test]
1284 fn test_sdp_axioms_registered() {
1285 let env = ext_env();
1286 assert!(env.get(&Name::str("SDPRelaxation")).is_some());
1287 assert!(env.get(&Name::str("GoemansWilliamsonRatio")).is_some());
1288 assert!(env.get(&Name::str("LasserreHierarchy")).is_some());
1289 assert!(env.get(&Name::str("UniqueGamesConj")).is_some());
1290 assert!(env
1291 .get(&Name::str("ApproxAlg.goemans_williamson_max_cut"))
1292 .is_some());
1293 }
1294 #[test]
1295 fn test_network_design_axioms_registered() {
1296 let env = ext_env();
1297 assert!(env.get(&Name::str("SteinerTreeApprox")).is_some());
1298 assert!(env.get(&Name::str("SteinerForestApprox")).is_some());
1299 assert!(env
1300 .get(&Name::str("ApproxAlg.jain_iterative_rounding"))
1301 .is_some());
1302 }
1303 #[test]
1304 fn test_clustering_scheduling_registered() {
1305 let env = ext_env();
1306 assert!(env.get(&Name::str("KMeansPlusPlus")).is_some());
1307 assert!(env.get(&Name::str("KMedianApprox")).is_some());
1308 assert!(env.get(&Name::str("ListSchedulingApprox")).is_some());
1309 assert!(env.get(&Name::str("OnlineAlgorithmCompetitive")).is_some());
1310 assert!(env
1311 .get(&Name::str("ApproxAlg.arora_ptas_euclidean_tsp"))
1312 .is_some());
1313 assert!(env.get(&Name::str("ApproxAlg.gap_amplification")).is_some());
1314 }
1315 #[test]
1316 fn test_goemans_williamson_rounding() {
1317 let n = 4;
1318 let mut weights = vec![vec![0.0f64; n]; n];
1319 for i in 0..n {
1320 for j in 0..n {
1321 if i != j {
1322 weights[i][j] = 1.0;
1323 }
1324 }
1325 }
1326 let gw = GoemansWilliamsonRounding::new(n, weights);
1327 let ub = gw.sdp_upper_bound();
1328 assert!((ub - 6.0).abs() < 1e-9, "SDP UB = {ub}");
1329 let (cut, partition) = gw.alternating_cut();
1330 assert!(cut >= 3.0, "alternating cut = {cut}");
1331 assert_eq!(partition.len(), n);
1332 let (ls_cut, _) = gw.local_search_improve(partition);
1333 assert!(
1334 ls_cut >= cut,
1335 "local search should not worsen: {ls_cut} vs {cut}"
1336 );
1337 assert!((gw.approximation_guarantee() - 0.8786).abs() < 0.001);
1338 }
1339 #[test]
1340 fn test_greedy_set_cover_struct() {
1341 let sets = vec![vec![0, 1, 2], vec![1, 2, 3], vec![3, 4], vec![0, 4]];
1342 let gsc = GreedySetCover::new(5, sets);
1343 let selected = gsc.solve();
1344 assert!(gsc.is_valid_cover(&selected), "must be valid cover");
1345 let ratio = gsc.harmonic_ratio();
1346 assert!(ratio > 1.0 && ratio < 3.0, "harmonic ratio = {ratio}");
1347 let mc = gsc.max_coverage(2);
1348 assert!(mc.len() <= 2);
1349 }
1350 #[test]
1351 fn test_christofides_heuristic() {
1352 let dist = vec![
1353 vec![0, 2, 9, 10, 8],
1354 vec![2, 0, 6, 4, 5],
1355 vec![9, 6, 0, 8, 7],
1356 vec![10, 4, 8, 0, 3],
1357 vec![8, 5, 7, 3, 0],
1358 ];
1359 let ch = ChristofidesHeuristic::new(dist);
1360 let (cost, tour) = ch.solve();
1361 assert!(ch.is_hamiltonian(&tour), "must be Hamiltonian: {tour:?}");
1362 assert_eq!(cost, ch.tour_cost(&tour));
1363 let lb = ch.mst_lower_bound();
1364 let ratio = cost as f64 / lb as f64;
1365 assert!(ratio <= 3.0, "Christofides ratio = {ratio}");
1366 assert_eq!(ch.approximation_ratio(), 1.5);
1367 }
1368 #[test]
1369 fn test_primal_dual_facility() {
1370 let opening_costs = vec![3.0, 5.0];
1371 let connection_costs = vec![vec![1.0, 4.0], vec![2.0, 1.0], vec![1.5, 3.0]];
1372 let pdf = PrimalDualFacility::new(opening_costs, connection_costs);
1373 assert_eq!(pdf.num_facilities(), 2);
1374 assert_eq!(pdf.num_clients(), 3);
1375 let (total, open, assign) = pdf.greedy_solve();
1376 assert!(total > 0.0, "cost must be positive: {total}");
1377 assert!(pdf.is_feasible(&open, &assign), "must be feasible");
1378 let lb = pdf.lower_bound();
1379 assert!(
1380 lb <= total + 1e-9,
1381 "lower bound {lb} must not exceed cost {total}"
1382 );
1383 }
1384}
1385#[cfg(test)]
1386mod tests_approx_extra {
1387 use super::*;
1388 #[test]
1389 fn test_set_cover_greedy() {
1390 let mut sc = SetCoverInstance::new(5);
1391 sc.add_set(vec![0, 1, 2], 3.0);
1392 sc.add_set(vec![2, 3, 4], 3.0);
1393 sc.add_set(vec![0, 3], 2.0);
1394 let (cost, chosen) = sc.greedy_solve();
1395 assert!(cost > 0.0);
1396 assert!(sc.is_feasible(&chosen));
1397 }
1398 #[test]
1399 fn test_metric_tsp_nn() {
1400 let mut tsp = MetricTSPInstance::new(4);
1401 for i in 0..4 {
1402 for j in (i + 1)..4 {
1403 tsp.set_dist(i, j, 1.0);
1404 }
1405 }
1406 assert!(tsp.satisfies_triangle_inequality());
1407 let (len, tour) = tsp.nearest_neighbor_tour(0);
1408 assert_eq!(tour.first(), tour.last());
1409 assert!(len >= 4.0 - 1e-9, "tour length >= n=4 for unit distances");
1410 }
1411 #[test]
1412 fn test_knapsack_fptas() {
1413 let kfptas = KnapsackFPTAS::new(10, vec![2, 3, 4, 5], vec![3.0, 4.0, 5.0, 6.0], 0.1);
1414 let val = kfptas.solve();
1415 assert!(val >= 0.0);
1416 }
1417 #[test]
1418 fn test_bin_packing_ffd() {
1419 let bpf = BinPackingFFD::new(10.0, vec![6.0, 5.0, 5.0, 4.0, 4.0]);
1420 let (n_bins, _items) = bpf.solve();
1421 let lb = bpf.lower_bound();
1422 assert!(n_bins >= lb, "FFD should use at least lower bound bins");
1423 }
1424 #[test]
1425 fn test_randomized_rounding() {
1426 let rr = RandomizedRounding::new(vec![0.3, 0.7, 0.5, 0.9], 100);
1427 let rounded = rr.threshold_round(0.5);
1428 assert!(!rounded[0]);
1429 assert!(rounded[1]);
1430 assert!(rounded[3]);
1431 let card = rr.rounded_cardinality(0.5);
1432 assert_eq!(card, 3);
1433 let obj = rr.lp_objective(&[1.0, 2.0, 3.0, 4.0]);
1434 assert!((obj - (0.3 + 1.4 + 1.5 + 3.6)).abs() < 1e-9);
1435 }
1436}