Skip to main content

oxilean_std/approximation_algorithms/
functions.rs

1//! Auto-generated module
2//!
3//! πŸ€– Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use 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}
58/// `OptimizationProblem : Type` β€” a minimization or maximization problem.
59pub fn optimization_problem_ty() -> Expr {
60    type0()
61}
62/// `ApproxAlgorithm : OptimizationProblem β†’ Type`
63/// An algorithm with a guaranteed approximation ratio.
64pub fn approx_algorithm_ty() -> Expr {
65    arrow(optimization_problem_ty(), type0())
66}
67/// `ApproxRatio : ApproxAlgorithm P β†’ Real`
68/// The approximation ratio achieved by the algorithm.
69pub fn approx_ratio_ty() -> Expr {
70    arrow(approx_algorithm_ty(), real_ty())
71}
72/// `IsAlphaApprox : OptimizationProblem β†’ Real β†’ Prop`
73/// There exists a polynomial-time algorithm with approximation ratio Ξ±.
74pub fn is_alpha_approx_ty() -> Expr {
75    arrow(optimization_problem_ty(), arrow(real_ty(), prop()))
76}
77/// `OptSolution : OptimizationProblem β†’ String β†’ Real`
78/// The optimal solution value for a given instance.
79pub fn opt_solution_ty() -> Expr {
80    arrow(optimization_problem_ty(), arrow(cst("String"), real_ty()))
81}
82/// `AlgSolution : ApproxAlgorithm P β†’ String β†’ Real`
83/// The solution value produced by the approximation algorithm.
84pub fn alg_solution_ty() -> Expr {
85    arrow(approx_algorithm_ty(), arrow(cst("String"), real_ty()))
86}
87/// `PTAS : OptimizationProblem β†’ Prop`
88/// The problem has a Polynomial-Time Approximation Scheme:
89/// for every Ξ΅ > 0, there is a (1+Ξ΅)-approximation running in poly(n).
90pub fn ptas_ty() -> Expr {
91    arrow(optimization_problem_ty(), prop())
92}
93/// `FPTAS : OptimizationProblem β†’ Prop`
94/// The problem has a Fully PTAS: running time is poly(n, 1/Ξ΅).
95pub fn fptas_ty() -> Expr {
96    arrow(optimization_problem_ty(), prop())
97}
98/// `EPTAS : OptimizationProblem β†’ Prop`
99/// Efficient PTAS: running time is f(1/Ξ΅)Β·poly(n) for some function f.
100pub fn eptas_ty() -> Expr {
101    arrow(optimization_problem_ty(), prop())
102}
103/// `APX : OptimizationProblem β†’ Prop`
104/// The problem is in APX: has a constant-factor approximation algorithm.
105pub fn apx_ty() -> Expr {
106    arrow(optimization_problem_ty(), prop())
107}
108/// `APXHard : OptimizationProblem β†’ Prop`
109/// The problem is APX-hard: no PTAS unless P=NP.
110pub fn apx_hard_ty() -> Expr {
111    arrow(optimization_problem_ty(), prop())
112}
113/// `APXComplete : OptimizationProblem β†’ Prop`
114/// The problem is APX-complete.
115pub fn apx_complete_ty() -> Expr {
116    arrow(optimization_problem_ty(), prop())
117}
118/// `MaxSNP : OptimizationProblem β†’ Prop`
119/// The problem is in MAX-SNP (Papadimitriou-Yannakakis class).
120pub fn max_snp_ty() -> Expr {
121    arrow(optimization_problem_ty(), prop())
122}
123/// `FPTAS_subset_PTAS : Every FPTAS problem has a PTAS`
124pub 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}
132/// `PTAS_subset_APX : Every PTAS problem is in APX`
133pub 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}
141/// `LPRelaxation : OptimizationProblem β†’ Type`
142/// The LP relaxation of an integer program.
143pub fn lp_relaxation_ty() -> Expr {
144    arrow(optimization_problem_ty(), type0())
145}
146/// `IntegralityGap : LPRelaxation P β†’ Real`
147/// The integrality gap: ratio of LP optimum to IP optimum.
148pub fn integrality_gap_ty() -> Expr {
149    arrow(lp_relaxation_ty(), real_ty())
150}
151/// `LPDominates : LPRelaxation P β†’ OptimizationProblem β†’ Prop`
152/// The LP bound dominates (is at least as tight as) the IP optimum.
153pub fn lp_dominates_ty() -> Expr {
154    arrow(lp_relaxation_ty(), arrow(optimization_problem_ty(), prop()))
155}
156/// `RandomizedRounding : LPRelaxation P β†’ ApproxAlgorithm P`
157/// Randomized rounding converts LP solutions to integer solutions.
158pub fn randomized_rounding_ty() -> Expr {
159    arrow(lp_relaxation_ty(), approx_algorithm_ty())
160}
161/// `SetCoverLPGap : The set cover LP has integrality gap Θ(log n)`
162pub fn set_cover_lp_gap_ty() -> Expr {
163    prop()
164}
165/// `VertexCoverLPGap : The vertex cover LP has integrality gap 2`
166pub fn vertex_cover_lp_gap_ty() -> Expr {
167    prop()
168}
169/// `PrimalDualAlgorithm : OptimizationProblem β†’ Type`
170/// An algorithm designed via the primal-dual schema.
171pub fn primal_dual_algorithm_ty() -> Expr {
172    arrow(optimization_problem_ty(), type0())
173}
174/// `PrimalDualGuarantee : PrimalDualAlgorithm P β†’ Real β†’ Prop`
175/// The primal-dual algorithm achieves the given approximation ratio.
176pub fn primal_dual_guarantee_ty() -> Expr {
177    arrow(primal_dual_algorithm_ty(), arrow(real_ty(), prop()))
178}
179/// `WeightedVertexCoverPD : 2-approximation via primal-dual for weighted VC`
180pub fn weighted_vertex_cover_pd_ty() -> Expr {
181    prop()
182}
183/// `SteinertreePD : The Steiner tree primal-dual gives 2(1 βˆ’ 1/l) ratio`
184pub fn steiner_tree_pd_ty() -> Expr {
185    prop()
186}
187/// `LocalSearchAlgorithm : OptimizationProblem β†’ Type`
188/// A local search algorithm iteratively improves solutions.
189pub fn local_search_algorithm_ty() -> Expr {
190    arrow(optimization_problem_ty(), type0())
191}
192/// `LocalOptimum : LocalSearchAlgorithm P β†’ String β†’ Prop`
193/// A solution is locally optimal (no improvement in the neighborhood).
194pub fn local_optimum_ty() -> Expr {
195    arrow(local_search_algorithm_ty(), arrow(cst("String"), prop()))
196}
197/// `LocalSearchRatio : LocalSearchAlgorithm P β†’ Real β†’ Prop`
198/// Local search achieves the given ratio at any local optimum.
199pub fn local_search_ratio_ty() -> Expr {
200    arrow(local_search_algorithm_ty(), arrow(real_ty(), prop()))
201}
202/// `MaxCutLocalSearch : 2-approximation for MAX-CUT via local search`
203pub fn max_cut_local_search_ty() -> Expr {
204    prop()
205}
206/// `KMedianLocalSearch : O(1)-approximation for k-median via local search`
207pub fn k_median_local_search_ty() -> Expr {
208    prop()
209}
210/// `GreedyAlgorithm : OptimizationProblem β†’ Type`
211/// A greedy algorithm making locally optimal choices at each step.
212pub fn greedy_algorithm_ty() -> Expr {
213    arrow(optimization_problem_ty(), type0())
214}
215/// `SetCoverGreedyRatio : Greedy set cover achieves H_n ratio (≀ ln n + 1)`
216pub fn set_cover_greedy_ratio_ty() -> Expr {
217    prop()
218}
219/// `MaxCoverageGreedy : (1 βˆ’ 1/e)-approximation for max coverage by greedy`
220pub fn max_coverage_greedy_ty() -> Expr {
221    prop()
222}
223/// `SubmodularGreedy : Greedy gives (1 βˆ’ 1/e) for submodular maximization`
224pub fn submodular_greedy_ty() -> Expr {
225    prop()
226}
227/// `PCPTheorem : NP = PCP(log n, 1)`
228/// Every NP language has a proof system with logarithmic randomness and
229/// constant query complexity.
230pub fn pcp_theorem_ty() -> Expr {
231    prop()
232}
233/// `MaxSATInapprox : MAX-3SAT has no (7/8 + Ξ΅)-approximation unless P=NP`
234pub fn max_sat_inapprox_ty() -> Expr {
235    prop()
236}
237/// `CliqueSizeInapprox : MAX-CLIQUE has no n^(1βˆ’Ξ΅)-approximation unless P=NP`
238pub fn clique_inapprox_ty() -> Expr {
239    prop()
240}
241/// `SetCoverInapprox : Set Cover has no (1 βˆ’ Ξ΅)Β·ln n approximation unless P=NP`
242pub fn set_cover_inapprox_ty() -> Expr {
243    prop()
244}
245/// `ChromaticInapprox : Graph coloring is hard to approximate within n^(1βˆ’Ξ΅)`
246pub fn chromatic_inapprox_ty() -> Expr {
247    prop()
248}
249/// `MaxSNPHard_implies_APXHard : MAX-SNP hardness implies APX-hardness`
250pub 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}
258/// `VertexCoverAPXHard : Vertex Cover is APX-hard`
259pub fn vertex_cover_apx_hard_ty() -> Expr {
260    prop()
261}
262/// `TSPNoApprox : Metric-free TSP has no constant approximation unless P=NP`
263pub fn tsp_no_approx_ty() -> Expr {
264    prop()
265}
266/// `MetricTSP : OptimizationProblem` β€” TSP on metric instances (triangle inequality).
267pub fn metric_tsp_ty() -> Expr {
268    optimization_problem_ty()
269}
270/// `ChristofidesSerdyukov : 3/2-approximation for metric TSP`
271/// The Christofides-Serdyukov algorithm:
272/// 1. Compute minimum spanning tree T.
273/// 2. Let O = odd-degree vertices in T; compute min-weight perfect matching M on O.
274/// 3. Form multigraph T βˆͺ M; find Eulerian circuit; shortcut to Hamiltonian cycle.
275pub fn christofides_serdyukov_ty() -> Expr {
276    prop()
277}
278/// `MST2Approx : MST-based 2-approximation for metric TSP`
279pub fn mst_2approx_ty() -> Expr {
280    prop()
281}
282/// Greedy set cover algorithm.
283/// `universe`: elements to cover (0..n).
284/// `sets`: each set is a Vec of elements.
285/// Returns indices of selected sets (greedy by max coverage).
286pub 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}
315/// Greedy max-coverage algorithm: select k sets to maximize the number of covered elements.
316/// Achieves (1 βˆ’ 1/e)-approximation.
317pub 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}
351/// 2-approximation for vertex cover via greedy edge cover.
352/// At each step pick an uncovered edge (u,v) and add both endpoints to the cover.
353pub 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}
375/// Minimum Spanning Tree using Kruskal's algorithm.
376/// Returns (total weight, list of edges in MST).
377pub 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}
414/// MST-based 2-approximation for metric TSP.
415/// Input: complete graph with metric distances (n x n matrix).
416/// Returns a Hamiltonian cycle (vertex ordering) and its total cost.
417pub 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/// FPTAS for 0-1 knapsack.
458/// Scales item values by (Ρ·max_value / n) and runs exact DP.
459/// Achieves (1 βˆ’ Ξ΅) approximation in O(n^2 / Ξ΅) time.
460#[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}
525/// Randomized rounding for weighted vertex cover (LP relaxation).
526/// Solves the LP: for each vertex v, x_v β‰₯ 0; for each edge (u,v), x_u + x_v β‰₯ 1.
527/// The LP optimum is half-integral; round x_v β‰₯ 1/2 to 1.
528/// This gives a 2-approximation in expectation.
529pub 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}
540/// Local search for MAX-CUT: start with random partition, swap vertices to improve cut.
541/// Returns (cut value, partition assignment: 0 or 1 for each vertex).
542pub 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}
591/// Primal-dual algorithm for weighted vertex cover.
592/// Maintains dual variables y_e for each edge; raises y_e until some endpoint
593/// becomes "tight" (sum of y_e = w_v), then adds that vertex.
594pub 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}
626/// Christofides-Serdyukov algorithm for metric TSP (3/2-approximation).
627/// Full implementation with minimum perfect matching on odd-degree vertices.
628pub 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}
714/// Check whether a vertex cover is valid for the given graph.
715pub 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}
735/// Check whether a set cover covers the entire universe.
736pub 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}
747/// Alias for `build_approximation_algorithms_env`.
748pub fn build_env(env: &mut Environment) -> Result<(), String> {
749    build_approximation_algorithms_env(env)
750}
751/// Populate an `Environment` with approximation algorithm axioms.
752pub 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}
946/// `DependentRounding : LPRelaxation P β†’ ApproxAlgorithm P`
947///
948/// Dependent rounding: a correlated rounding scheme for {0,1} LPs that
949/// preserves marginals and creates negative correlations. Used for degree-constrained
950/// subgraph problems and bipartite graphs.
951pub fn dependent_rounding_ty() -> Expr {
952    arrow(lp_relaxation_ty(), approx_algorithm_ty())
953}
954/// `CorrelationRounding : LPRelaxation P β†’ ApproxAlgorithm P`
955///
956/// Correlation rounding: rounds {0,1} LPs using pairwise negative correlation,
957/// with applications to constraint satisfaction and scheduling.
958pub fn correlation_rounding_ty() -> Expr {
959    arrow(lp_relaxation_ty(), approx_algorithm_ty())
960}
961/// `PipageRounding : LPRelaxation P β†’ ApproxAlgorithm P`
962///
963/// Pipage rounding (Ageev-Sviridenko): iteratively adjusts fractional values
964/// along pairs to reduce the number of fractional variables while preserving
965/// or improving an objective. Applied to submodular maximization over matroids.
966pub fn pipage_rounding_ty() -> Expr {
967    arrow(lp_relaxation_ty(), approx_algorithm_ty())
968}
969/// `RandomizedRoundingGap : Real β†’ Prop`
970///
971/// LP integrality gap theorem: for a class of LPs with gap Ξ±, any rounding
972/// scheme that is oblivious (independent of LP data) cannot beat the gap.
973pub fn randomized_rounding_gap_ty() -> Expr {
974    arrow(real_ty(), prop())
975}
976/// `LPHierarchy : OptimizationProblem β†’ Nat β†’ Type`
977///
978/// LP hierarchy: the Sherali-Adams or LovΓ‘sz-Schrijver hierarchy strengthens
979/// the LP relaxation by adding rounds of constraints from products of rows.
980pub fn lp_hierarchy_ty() -> Expr {
981    arrow(optimization_problem_ty(), arrow(nat_ty(), type0()))
982}
983/// `TightnessOfIntegralityGap : LPRelaxation P β†’ Real β†’ Prop`
984///
985/// Tightness: the integrality gap is achieved by an explicit bad instance.
986pub fn tightness_integrality_gap_ty() -> Expr {
987    arrow(lp_relaxation_ty(), arrow(real_ty(), prop()))
988}
989/// `SDPRelaxation : OptimizationProblem β†’ Type`
990///
991/// A semidefinite programming relaxation of a combinatorial problem.
992pub fn sdp_relaxation_ty() -> Expr {
993    arrow(optimization_problem_ty(), type0())
994}
995/// `GoemansWilliamsonMaxCut : 0.878-approximation for MAX-CUT via SDP`
996///
997/// Goemans-Williamson (1995): solve the SDP relaxation of MAX-CUT, then round
998/// via a random hyperplane. Achieves Ξ±_GW β‰ˆ 0.8786 approximation ratio.
999/// This is optimal assuming Unique Games Conjecture (Khot et al. 2007).
1000pub fn goemans_williamson_max_cut_ty() -> Expr {
1001    prop()
1002}
1003/// `GoemansWilliamsonRatio : Real`
1004///
1005/// The Goemans-Williamson constant Ξ±_GW = 2/Ο€ Β· min_{θ∈\[0,Ο€\]} ΞΈ/(1 - cos ΞΈ) β‰ˆ 0.8786.
1006pub fn goemans_williamson_ratio_ty() -> Expr {
1007    real_ty()
1008}
1009/// `LasserreHierarchy : OptimizationProblem β†’ Nat β†’ Type`
1010///
1011/// Lasserre SDP hierarchy (Sum-of-Squares): adds rounds of SDP constraints
1012/// corresponding to squares of polynomials, tightening the relaxation.
1013pub fn lasserre_hierarchy_ty() -> Expr {
1014    arrow(optimization_problem_ty(), arrow(nat_ty(), type0()))
1015}
1016/// `SDPIntegralityGap : SDPRelaxation P β†’ Real`
1017///
1018/// The integrality gap of an SDP relaxation.
1019pub fn sdp_integrality_gap_ty() -> Expr {
1020    arrow(sdp_relaxation_ty(), real_ty())
1021}
1022/// `UniqueGamesConj : Prop`
1023///
1024/// Unique Games Conjecture (Khot 2002): for every Ξ΅ > 0, it is NP-hard to
1025/// distinguish between Unique Games instances with value β‰₯ 1-Ξ΅ and value ≀ Ξ΅.
1026/// Has many conditional hardness implications (MAX-CUT, vertex cover, etc.).
1027pub fn unique_games_conj_ty() -> Expr {
1028    prop()
1029}
1030/// `SDPMaxCutGapOptimal : Prop`
1031///
1032/// Assuming UGC, the Goemans-Williamson 0.878 ratio is optimal for MAX-CUT.
1033pub fn sdp_max_cut_gap_optimal_ty() -> Expr {
1034    prop()
1035}
1036/// `HypergraphCutSDP : Real β†’ Prop`
1037///
1038/// SDP-based approximation for hypergraph cut problems with given ratio.
1039pub fn hypergraph_cut_sdp_ty() -> Expr {
1040    arrow(real_ty(), prop())
1041}
1042/// `SteinerTreeApprox : Real β†’ Prop`
1043///
1044/// Approximation ratio Ξ± for the Steiner tree problem.
1045/// Best known: 1.39 (Byrka et al. 2013) via iterated LP relaxation and rounding.
1046pub fn steiner_tree_approx_ty() -> Expr {
1047    arrow(real_ty(), prop())
1048}
1049/// `JainIterativeRounding : Prop`
1050///
1051/// Jain's 2-approximation for survivable network design (2001):
1052/// at each step, find a fractional solution to the cut LP; at least one variable
1053/// is β‰₯ 1/2, so round it up. Gives 2-approximation for general edge-connectivity
1054/// requirements (including Steiner tree, vertex connectivity, etc.).
1055pub fn jain_iterative_rounding_ty() -> Expr {
1056    prop()
1057}
1058/// `KConnectivityApprox : Nat β†’ Real β†’ Prop`
1059///
1060/// k-edge-connectivity approximation: a network design problem where each
1061/// pair of vertices must have k edge-disjoint paths; approximation ratio R.
1062pub fn k_connectivity_approx_ty() -> Expr {
1063    arrow(nat_ty(), arrow(real_ty(), prop()))
1064}
1065/// `SteinerForestApprox : Real β†’ Prop`
1066///
1067/// Steiner forest approximation: given a graph and pairs (sα΅’, tα΅’) to connect,
1068/// find a minimum-cost subgraph. 2-approximation via primal-dual (Agrawal et al.).
1069pub fn steiner_forest_approx_ty() -> Expr {
1070    arrow(real_ty(), prop())
1071}
1072/// `IterativeRoundingThm : Prop`
1073///
1074/// General iterative rounding theorem: for a basic feasible solution of a
1075/// natural LP, some variable has value β‰₯ 1/k, where k is the rank of the
1076/// constraint matrix restricted to the support.
1077pub fn iterative_rounding_thm_ty() -> Expr {
1078    prop()
1079}
1080/// `NetworkDesignLPRelaxation : Type`
1081///
1082/// The cut LP for network design: minimize βˆ‘ cβ‚‘ xβ‚‘ subject to
1083/// x(Ξ΄(S)) β‰₯ f(S) for all S βŠ† V, where f is the connectivity requirement function.
1084pub fn network_design_lp_ty() -> Expr {
1085    type0()
1086}
1087/// `KMeansPlusPlus : OptimizationProblem β†’ Prop`
1088///
1089/// k-means++ initialization (Arthur-Vassilvitskii 2007): choose initial centers
1090/// with probability proportional to squared distance, giving O(log k)-approximation
1091/// in expectation for k-means clustering.
1092pub fn k_means_plus_plus_ty() -> Expr {
1093    arrow(optimization_problem_ty(), prop())
1094}
1095/// `KMedianApprox : Real β†’ Prop`
1096///
1097/// k-median approximation ratio Ξ±: find k centers minimizing sum of distances.
1098/// Best known: (2.675 + Ξ΅) via local search (Byrka et al. 2017).
1099pub fn k_median_approx_ty() -> Expr {
1100    arrow(real_ty(), prop())
1101}
1102/// `FacilityLocationBicriteria : Real β†’ Real β†’ Prop`
1103///
1104/// Bicriteria approximation for facility location: simultaneously achieves
1105/// (Ξ±, Ξ²) approximation on cost and number of facilities opened.
1106pub fn facility_location_bicriteria_ty() -> Expr {
1107    arrow(real_ty(), arrow(real_ty(), prop()))
1108}
1109/// `ListSchedulingApprox : Real β†’ Prop`
1110///
1111/// List scheduling approximation (Graham 1969): assign jobs greedily to machines.
1112/// Gives (2 - 1/m)-approximation for makespan on m identical machines.
1113pub fn list_scheduling_approx_ty() -> Expr {
1114    arrow(real_ty(), prop())
1115}
1116/// `MakespanPTAS : Prop`
1117///
1118/// PTAS for makespan minimization on identical machines: for every Ξ΅ > 0,
1119/// achieves (1+Ρ) approximation in time n^{O(1/Ρ²)}.
1120pub fn makespan_ptas_ty() -> Expr {
1121    prop()
1122}
1123/// `PreemptiveScheduleApprox : Prop`
1124///
1125/// Preemptive scheduling: if jobs may be interrupted and resumed, there exist
1126/// optimal algorithms (e.g., McNaughton's algorithm for P||Cmax).
1127pub fn preemptive_schedule_approx_ty() -> Expr {
1128    prop()
1129}
1130/// `OnlineAlgorithmCompetitive : OptimizationProblem β†’ Real β†’ Prop`
1131///
1132/// An online algorithm is c-competitive: its cost on any sequence is at most
1133/// c times the cost of the offline optimal plus an additive term.
1134pub fn online_algorithm_competitive_ty() -> Expr {
1135    arrow(optimization_problem_ty(), arrow(real_ty(), prop()))
1136}
1137/// `SkiRentalBreakeven : Prop`
1138///
1139/// Ski rental problem: optimal deterministic online algorithm breaks even at
1140/// cost ratio 2 (buy when rental cost = purchase cost).
1141/// Randomized algorithm achieves e/(e-1) β‰ˆ 1.58-competitive ratio.
1142pub fn ski_rental_breakeven_ty() -> Expr {
1143    prop()
1144}
1145/// `LoadBalancingOnline : Real β†’ Prop`
1146///
1147/// Online load balancing: greedy assignment (assign job to least loaded machine)
1148/// achieves (2 - 1/m) competitive ratio for m machines.
1149pub fn load_balancing_online_ty() -> Expr {
1150    arrow(real_ty(), prop())
1151}
1152/// `AroraPTASEuclideanTSP : Prop`
1153///
1154/// Arora's PTAS for Euclidean TSP (1998): for any Ξ΅ > 0, a (1+Ξ΅)-approximation
1155/// in time O(n (log n)^{O(1/Ξ΅)}) via randomized guillotine cuts and dynamic programming.
1156pub fn arora_ptas_euclidean_tsp_ty() -> Expr {
1157    prop()
1158}
1159/// `BakeryPTAS : Prop`
1160///
1161/// Mitchell's PTAS for Euclidean TSP (alternative proof via Steiner points).
1162pub fn mitchell_ptas_euclidean_tsp_ty() -> Expr {
1163    prop()
1164}
1165/// `GapAmplification : Prop`
1166///
1167/// Gap amplification in PCP theorem: if a CSP has value ≀ 1 - Ξ΅ then
1168/// after amplification it has value ≀ 1/2, enabling NP-hardness of approximation.
1169pub fn gap_amplification_ty() -> Expr {
1170    prop()
1171}
1172/// `InapproximabilityReduction : Prop`
1173///
1174/// L-reduction and PTAS-reduction: polynomial transformations that preserve
1175/// approximability structure, used to transfer hardness between problems.
1176pub fn inapproximability_reduction_ty() -> Expr {
1177    prop()
1178}
1179/// Register all Β§9–§12 approximation algorithm axioms into `env`.
1180pub 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}