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