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