Skip to main content

oxilean_std/linear_programming/
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    BendersDecomposition, ColumnGenerationSolver, EllipsoidMethodSolver, GomoryCut,
9    GomoryCutGenerator, InequalityLP, IntegerProgram, InteriorPointSolver, LinearProgram, LpResult,
10    NetworkEdge, NetworkSimplexSolver, ScenarioData, TransportationProblem,
11};
12
13pub fn app(f: Expr, a: Expr) -> Expr {
14    Expr::App(Box::new(f), Box::new(a))
15}
16pub fn app2(f: Expr, a: Expr, b: Expr) -> Expr {
17    app(app(f, a), b)
18}
19pub fn cst(s: &str) -> Expr {
20    Expr::Const(Name::str(s), vec![])
21}
22pub fn prop() -> Expr {
23    Expr::Sort(Level::zero())
24}
25pub fn type0() -> Expr {
26    Expr::Sort(Level::succ(Level::zero()))
27}
28pub fn pi(bi: BinderInfo, name: &str, dom: Expr, body: Expr) -> Expr {
29    Expr::Pi(bi, Name::str(name), Box::new(dom), Box::new(body))
30}
31pub fn arrow(a: Expr, b: Expr) -> Expr {
32    pi(BinderInfo::Default, "_", a, b)
33}
34#[allow(dead_code)]
35pub fn nat_ty() -> Expr {
36    cst("Nat")
37}
38pub fn real_ty() -> Expr {
39    cst("Real")
40}
41pub fn list_ty(elem: Expr) -> Expr {
42    app(cst("List"), elem)
43}
44pub fn lp_feasible_ty() -> Expr {
45    let lr = list_ty(real_ty());
46    let llr = list_ty(lr.clone());
47    arrow(lr.clone(), arrow(llr, arrow(lr, prop())))
48}
49pub fn lp_optimal_ty() -> Expr {
50    let lr = list_ty(real_ty());
51    let llr = list_ty(lr.clone());
52    arrow(lr.clone(), arrow(llr, arrow(lr.clone(), arrow(lr, prop()))))
53}
54pub fn duality_ty() -> Expr {
55    prop()
56}
57pub fn integer_programming_ty() -> Expr {
58    let lr = list_ty(real_ty());
59    let llr = list_ty(lr.clone());
60    arrow(lr.clone(), arrow(llr, arrow(lr, prop())))
61}
62pub fn totally_unimodular_ty() -> Expr {
63    let lr = list_ty(real_ty());
64    let llr = list_ty(lr);
65    arrow(llr, prop())
66}
67pub fn interior_point_correctness_ty() -> Expr {
68    prop()
69}
70pub fn shadow_price_ty() -> Expr {
71    let lr = list_ty(real_ty());
72    arrow(lr.clone(), arrow(lr, list_ty(real_ty())))
73}
74pub fn transportation_problem_ty() -> Expr {
75    let lr = list_ty(real_ty());
76    let llr = list_ty(lr.clone());
77    arrow(lr.clone(), arrow(lr, arrow(llr, prop())))
78}
79pub fn simplex_correctness_ty() -> Expr {
80    prop()
81}
82pub fn strong_duality_ty() -> Expr {
83    prop()
84}
85pub fn farkas_lemma_ty() -> Expr {
86    prop()
87}
88pub fn ellipsoid_poly_ty() -> Expr {
89    prop()
90}
91pub fn complementary_slackness_ty() -> Expr {
92    prop()
93}
94pub fn parametric_optimal_value_ty() -> Expr {
95    arrow(list_ty(real_ty()), real_ty())
96}
97pub fn sensitivity_range_ty() -> Expr {
98    let lr = list_ty(real_ty());
99    let pair_ty = app2(cst("Prod"), real_ty(), real_ty());
100    arrow(lr.clone(), arrow(lr, list_ty(pair_ty)))
101}
102pub fn rhs_ranging_theorem_ty() -> Expr {
103    prop()
104}
105pub fn parametric_lp_continuity_ty() -> Expr {
106    prop()
107}
108pub fn min_cost_flow_ty() -> Expr {
109    let lr = list_ty(real_ty());
110    arrow(
111        lr.clone(),
112        arrow(real_ty(), arrow(real_ty(), arrow(real_ty(), prop()))),
113    )
114}
115pub fn network_flow_lp_ty() -> Expr {
116    prop()
117}
118pub fn spanning_tree_basis_ty() -> Expr {
119    arrow(real_ty(), arrow(list_ty(real_ty()), prop()))
120}
121pub fn network_simplex_optimality_ty() -> Expr {
122    prop()
123}
124pub fn column_generation_master_ty() -> Expr {
125    let lr = list_ty(real_ty());
126    let llr = list_ty(lr.clone());
127    arrow(llr, arrow(lr, prop()))
128}
129pub fn dantzig_wolfe_decomposition_ty() -> Expr {
130    prop()
131}
132pub fn cutting_plane_ty() -> Expr {
133    arrow(list_ty(real_ty()), arrow(real_ty(), prop()))
134}
135pub fn restricted_master_problem_ty() -> Expr {
136    prop()
137}
138pub fn benders_feasibility_cut_ty() -> Expr {
139    arrow(list_ty(real_ty()), arrow(real_ty(), prop()))
140}
141pub fn benders_optimality_cut_ty() -> Expr {
142    arrow(
143        list_ty(real_ty()),
144        arrow(real_ty(), arrow(real_ty(), prop())),
145    )
146}
147pub fn l_shaped_method_convergence_ty() -> Expr {
148    prop()
149}
150pub fn benders_decomposition_correctness_ty() -> Expr {
151    prop()
152}
153pub fn khachian_polynomial_lp_ty() -> Expr {
154    prop()
155}
156pub fn ellipsoid_feasibility_ty() -> Expr {
157    arrow(list_ty(real_ty()), arrow(list_ty(real_ty()), prop()))
158}
159pub fn ellipsoid_volume_decrease_ty() -> Expr {
160    prop()
161}
162pub fn polynomial_complexity_lp_ty() -> Expr {
163    prop()
164}
165pub fn two_stage_recourse_ty() -> Expr {
166    arrow(
167        list_ty(real_ty()),
168        arrow(list_ty(real_ty()), arrow(real_ty(), prop())),
169    )
170}
171pub fn wait_and_see_ty() -> Expr {
172    prop()
173}
174pub fn expected_value_perfect_info_ty() -> Expr {
175    arrow(real_ty(), arrow(real_ty(), arrow(real_ty(), prop())))
176}
177pub fn stochastic_lp_optimality_ty() -> Expr {
178    prop()
179}
180pub fn uncertainty_set_ty() -> Expr {
181    arrow(list_ty(real_ty()), arrow(real_ty(), prop()))
182}
183pub fn worst_case_robust_ty() -> Expr {
184    arrow(
185        list_ty(real_ty()),
186        arrow(list_ty(real_ty()), arrow(real_ty(), prop())),
187    )
188}
189pub fn data_driven_robust_ty() -> Expr {
190    prop()
191}
192pub fn robust_lp_feasibility_ty() -> Expr {
193    prop()
194}
195pub fn second_order_cone_ty() -> Expr {
196    arrow(list_ty(real_ty()), arrow(real_ty(), prop()))
197}
198pub fn semidefinite_program_ty() -> Expr {
199    let lr = list_ty(real_ty());
200    arrow(list_ty(lr), prop())
201}
202pub fn copositive_program_ty() -> Expr {
203    prop()
204}
205pub fn conic_duality_ty() -> Expr {
206    prop()
207}
208pub fn gomory_cut_ty() -> Expr {
209    arrow(list_ty(real_ty()), arrow(real_ty(), prop()))
210}
211pub fn lift_and_project_ty() -> Expr {
212    prop()
213}
214pub fn branch_and_bound_optimality_ty() -> Expr {
215    prop()
216}
217pub fn cutting_planes_convergence_ty() -> Expr {
218    prop()
219}
220pub fn lagrangian_duality_ty() -> Expr {
221    arrow(
222        list_ty(real_ty()),
223        arrow(real_ty(), arrow(real_ty(), prop())),
224    )
225}
226pub fn fenchel_duality_ty() -> Expr {
227    prop()
228}
229pub fn minimax_theorem_ty() -> Expr {
230    prop()
231}
232pub fn lcp_solution_ty() -> Expr {
233    let lr = list_ty(real_ty());
234    arrow(lr.clone(), arrow(list_ty(lr), prop()))
235}
236pub fn mpec_feasibility_ty() -> Expr {
237    prop()
238}
239pub fn online_lp_primal_dual_ty() -> Expr {
240    prop()
241}
242pub fn competitive_ratio_lp_ty() -> Expr {
243    arrow(real_ty(), arrow(real_ty(), prop()))
244}
245pub fn build_linear_programming_env(env: &mut Environment) {
246    let axioms: &[(&str, Expr)] = &[
247        ("LpFeasible", lp_feasible_ty()),
248        ("LpOptimal", lp_optimal_ty()),
249        ("LpDuality", duality_ty()),
250        ("IntegerProgram", integer_programming_ty()),
251        ("TotallyUnimodular", totally_unimodular_ty()),
252        ("InteriorPointCorrectness", interior_point_correctness_ty()),
253        ("ShadowPrice", shadow_price_ty()),
254        ("TransportationProblem", transportation_problem_ty()),
255        ("simplex_correctness", simplex_correctness_ty()),
256        ("strong_duality", strong_duality_ty()),
257        ("farkas_lemma", farkas_lemma_ty()),
258        ("ellipsoid_poly", ellipsoid_poly_ty()),
259        ("complementary_slackness", complementary_slackness_ty()),
260        ("LpBounded", arrow(list_ty(real_ty()), prop())),
261        ("IpRelaxOptimal", prop()),
262        ("TuRelaxationTheorem", prop()),
263        ("ParametricOptimalValue", parametric_optimal_value_ty()),
264        ("SensitivityRange", sensitivity_range_ty()),
265        ("RhsRangingTheorem", rhs_ranging_theorem_ty()),
266        ("ParametricLpContinuity", parametric_lp_continuity_ty()),
267        ("MinCostFlow", min_cost_flow_ty()),
268        ("NetworkFlowLp", network_flow_lp_ty()),
269        ("SpanningTreeBasis", spanning_tree_basis_ty()),
270        ("NetworkSimplexOptimality", network_simplex_optimality_ty()),
271        ("ColumnGenerationMaster", column_generation_master_ty()),
272        (
273            "DantzigWolfeDecomposition",
274            dantzig_wolfe_decomposition_ty(),
275        ),
276        ("CuttingPlane", cutting_plane_ty()),
277        ("RestrictedMasterProblem", restricted_master_problem_ty()),
278        ("BendersFeasibilityCut", benders_feasibility_cut_ty()),
279        ("BendersOptimalityCut", benders_optimality_cut_ty()),
280        ("LShapedMethodConvergence", l_shaped_method_convergence_ty()),
281        (
282            "BendersDecompositionCorrectness",
283            benders_decomposition_correctness_ty(),
284        ),
285        ("KhachianPolynomialLP", khachian_polynomial_lp_ty()),
286        ("EllipsoidFeasibility", ellipsoid_feasibility_ty()),
287        ("EllipsoidVolumeDecrease", ellipsoid_volume_decrease_ty()),
288        ("PolynomialComplexityLP", polynomial_complexity_lp_ty()),
289        ("TwoStageRecourse", two_stage_recourse_ty()),
290        ("WaitAndSee", wait_and_see_ty()),
291        ("ExpectedValuePerfectInfo", expected_value_perfect_info_ty()),
292        ("StochasticLpOptimality", stochastic_lp_optimality_ty()),
293        ("UncertaintySet", uncertainty_set_ty()),
294        ("WorstCaseRobust", worst_case_robust_ty()),
295        ("DataDrivenRobust", data_driven_robust_ty()),
296        ("RobustLpFeasibility", robust_lp_feasibility_ty()),
297        ("SecondOrderCone", second_order_cone_ty()),
298        ("SemidefiniteProgram", semidefinite_program_ty()),
299        ("CopositiveProgram", copositive_program_ty()),
300        ("ConicDuality", conic_duality_ty()),
301        ("GomoryCut", gomory_cut_ty()),
302        ("LiftAndProject", lift_and_project_ty()),
303        ("BranchAndBoundOptimality", branch_and_bound_optimality_ty()),
304        ("CuttingPlanesConvergence", cutting_planes_convergence_ty()),
305        ("LagrangianDuality", lagrangian_duality_ty()),
306        ("FenchelDuality", fenchel_duality_ty()),
307        ("MinimaxTheorem", minimax_theorem_ty()),
308        ("LcpSolution", lcp_solution_ty()),
309        ("MpecFeasibility", mpec_feasibility_ty()),
310        ("OnlineLpPrimalDual", online_lp_primal_dual_ty()),
311        ("CompetitiveRatioLP", competitive_ratio_lp_ty()),
312    ];
313    for (name, ty) in axioms {
314        env.add(Declaration::Axiom {
315            name: Name::str(*name),
316            univ_params: vec![],
317            ty: ty.clone(),
318        })
319        .ok();
320    }
321}
322pub fn knapsack_greedy(weights: &[f64], values: &[f64], capacity: f64) -> (Vec<bool>, f64) {
323    let n = weights.len().min(values.len());
324    let mut indices: Vec<usize> = (0..n).collect();
325    indices.sort_by(|&i, &j| {
326        let ri = if weights[i] > 1e-15 {
327            values[i] / weights[i]
328        } else {
329            f64::NEG_INFINITY
330        };
331        let rj = if weights[j] > 1e-15 {
332            values[j] / weights[j]
333        } else {
334            f64::NEG_INFINITY
335        };
336        rj.partial_cmp(&ri).unwrap_or(std::cmp::Ordering::Equal)
337    });
338    let mut selected = vec![false; n];
339    let mut remaining = capacity;
340    let mut total_value = 0.0;
341    for i in indices {
342        if weights[i] <= remaining + 1e-15 {
343            selected[i] = true;
344            remaining -= weights[i];
345            total_value += values[i];
346        }
347    }
348    (selected, total_value)
349}
350pub fn knapsack_dp(weights: &[u64], values: &[u64], capacity: u64) -> (Vec<bool>, u64) {
351    let n = weights.len().min(values.len());
352    let cap = capacity as usize;
353    let mut dp = vec![vec![0u64; cap + 1]; n + 1];
354    for i in 1..=n {
355        for w in 0..=cap {
356            dp[i][w] = dp[i - 1][w];
357            let wi = weights[i - 1] as usize;
358            if wi <= w {
359                let with_item = dp[i - 1][w - wi] + values[i - 1];
360                if with_item > dp[i][w] {
361                    dp[i][w] = with_item;
362                }
363            }
364        }
365    }
366    let mut selected = vec![false; n];
367    let mut w = cap;
368    for i in (1..=n).rev() {
369        if dp[i][w] != dp[i - 1][w] {
370            selected[i - 1] = true;
371            w -= weights[i - 1] as usize;
372        }
373    }
374    (selected, dp[n][cap])
375}
376pub fn knapsack_fractional(weights: &[f64], values: &[f64], capacity: f64) -> (Vec<f64>, f64) {
377    let n = weights.len().min(values.len());
378    let mut indices: Vec<usize> = (0..n).collect();
379    indices.sort_by(|&i, &j| {
380        let ri = if weights[i] > 1e-15 {
381            values[i] / weights[i]
382        } else {
383            f64::NEG_INFINITY
384        };
385        let rj = if weights[j] > 1e-15 {
386            values[j] / weights[j]
387        } else {
388            f64::NEG_INFINITY
389        };
390        rj.partial_cmp(&ri).unwrap_or(std::cmp::Ordering::Equal)
391    });
392    let mut fractions = vec![0.0; n];
393    let mut remaining = capacity;
394    let mut total_value = 0.0;
395    for i in indices {
396        if remaining <= 1e-15 {
397            break;
398        }
399        if weights[i] <= remaining + 1e-15 {
400            fractions[i] = 1.0;
401            remaining -= weights[i];
402            total_value += values[i];
403        } else if weights[i] > 1e-15 {
404            let frac = remaining / weights[i];
405            fractions[i] = frac;
406            total_value += frac * values[i];
407            remaining = 0.0;
408        }
409    }
410    (fractions, total_value)
411}
412pub fn add_bound_constraint(
413    lp: &LinearProgram,
414    j: usize,
415    bound: f64,
416    upper: bool,
417) -> LinearProgram {
418    let (m, n) = (lp.n_constraints, lp.n_vars);
419    let mut new_a = lp.a.clone();
420    let mut new_row = vec![0.0_f64; n];
421    if j < n {
422        new_row[j] = if upper { 1.0 } else { -1.0 };
423    }
424    new_a.push(new_row);
425    let mut new_b = lp.b.clone();
426    new_b.push(if upper { bound } else { -bound });
427    LinearProgram {
428        c: lp.c.clone(),
429        a: new_a,
430        b: new_b,
431        n_vars: n,
432        n_constraints: m + 1,
433    }
434}
435pub fn best_result(r1: LpResult, r2: LpResult) -> LpResult {
436    match (&r1, &r2) {
437        (LpResult::Optimal { objective: o1, .. }, LpResult::Optimal { objective: o2, .. }) => {
438            if o1 <= o2 {
439                r1
440            } else {
441                r2
442            }
443        }
444        (LpResult::Optimal { .. }, _) => r1,
445        (_, LpResult::Optimal { .. }) => r2,
446        _ => LpResult::Infeasible,
447    }
448}
449pub fn mat_vec_mul(a: &[Vec<f64>], x: &[f64]) -> Vec<f64> {
450    a.iter()
451        .map(|row| row.iter().zip(x.iter()).map(|(ai, xi)| ai * xi).sum())
452        .collect()
453}
454pub fn transpose(a: &[Vec<f64>]) -> Vec<Vec<f64>> {
455    if a.is_empty() {
456        return vec![];
457    }
458    let (m, n) = (a.len(), a[0].len());
459    let mut t = vec![vec![0.0f64; m]; n];
460    for i in 0..m {
461        for j in 0..n.min(a[i].len()) {
462            t[j][i] = a[i][j];
463        }
464    }
465    t
466}
467pub fn dot(a: &[f64], b: &[f64]) -> f64 {
468    a.iter().zip(b.iter()).map(|(ai, bi)| ai * bi).sum()
469}
470pub fn vec_add(a: &[f64], b: &[f64]) -> Vec<f64> {
471    a.iter().zip(b.iter()).map(|(ai, bi)| ai + bi).collect()
472}
473pub fn vec_sub(a: &[f64], b: &[f64]) -> Vec<f64> {
474    a.iter().zip(b.iter()).map(|(ai, bi)| ai - bi).collect()
475}
476pub fn vec_scale(v: &[f64], alpha: f64) -> Vec<f64> {
477    v.iter().map(|vi| alpha * vi).collect()
478}
479pub fn vec_norm(v: &[f64]) -> f64 {
480    v.iter().map(|vi| vi * vi).sum::<f64>().sqrt()
481}
482pub fn assignment_greedy(cost: &[Vec<f64>]) -> (Vec<usize>, f64) {
483    let n = cost.len();
484    if n == 0 {
485        return (vec![], 0.0);
486    }
487    let m = cost[0].len();
488    let size = n.min(m);
489    let mut used_jobs = vec![false; m];
490    let mut assignment = vec![0usize; n];
491    let mut total_cost = 0.0;
492    let mut workers: Vec<usize> = (0..n).collect();
493    workers.sort_by(|&a, &b| {
494        let min_a = cost[a].iter().cloned().fold(f64::INFINITY, f64::min);
495        let min_b = cost[b].iter().cloned().fold(f64::INFINITY, f64::min);
496        min_a
497            .partial_cmp(&min_b)
498            .unwrap_or(std::cmp::Ordering::Equal)
499    });
500    for &worker in &workers {
501        let mut best_job = 0;
502        let mut best_cost = f64::INFINITY;
503        for j in 0..size {
504            if !used_jobs[j] && j < cost[worker].len() && cost[worker][j] < best_cost {
505                best_cost = cost[worker][j];
506                best_job = j;
507            }
508        }
509        if best_cost < f64::INFINITY {
510            assignment[worker] = best_job;
511            used_jobs[best_job] = true;
512            total_cost += best_cost;
513        }
514    }
515    (assignment, total_cost)
516}
517pub fn rhs_sensitivity(lp: &LinearProgram) -> Vec<(f64, f64)> {
518    let base_result = lp.solve();
519    let base_obj = match &base_result {
520        LpResult::Optimal { objective, .. } => *objective,
521        _ => return vec![(f64::NEG_INFINITY, f64::INFINITY); lp.n_constraints],
522    };
523    (0..lp.n_constraints)
524        .map(|i| {
525            let step = lp.b[i].abs().max(1.0) * 0.01;
526            let mut low = lp.b[i];
527            let mut high = lp.b[i];
528            for k in 1..100 {
529                let delta = -(k as f64) * step;
530                let mut tb = lp.b.clone();
531                tb[i] += delta;
532                let tlp = LinearProgram::new(lp.c.clone(), lp.a.clone(), tb);
533                match tlp.solve() {
534                    LpResult::Optimal { objective, .. } => {
535                        let expected = delta * (base_obj / lp.b[i].max(1e-10));
536                        if (objective - base_obj - expected).abs() > step * 2.0 {
537                            break;
538                        }
539                        low = lp.b[i] + delta;
540                    }
541                    _ => break,
542                }
543            }
544            for k in 1..100 {
545                let delta = (k as f64) * step;
546                let mut tb = lp.b.clone();
547                tb[i] += delta;
548                let tlp = LinearProgram::new(lp.c.clone(), lp.a.clone(), tb);
549                match tlp.solve() {
550                    LpResult::Optimal { objective, .. } => {
551                        let expected = delta * (base_obj / lp.b[i].max(1e-10));
552                        if (objective - base_obj - expected).abs() > step * 2.0 {
553                            break;
554                        }
555                        high = lp.b[i] + delta;
556                    }
557                    _ => break,
558                }
559            }
560            (low, high)
561        })
562        .collect()
563}
564#[cfg(test)]
565mod tests {
566    use super::*;
567    #[test]
568    fn test_linear_program_new() {
569        let lp = LinearProgram::new(
570            vec![1.0, 2.0],
571            vec![vec![1.0, 0.0], vec![0.0, 1.0]],
572            vec![5.0, 5.0],
573        );
574        assert_eq!(lp.n_vars, 2);
575        assert_eq!(lp.n_constraints, 2);
576    }
577    #[test]
578    fn test_lp_feasible_check() {
579        let lp = LinearProgram::new(vec![1.0], vec![vec![1.0]], vec![3.0]);
580        assert!(lp.is_feasible(&[3.0]));
581        assert!(!lp.is_feasible(&[2.0]));
582        assert!(!lp.is_feasible(&[-1.0]));
583    }
584    #[test]
585    fn test_lp_objective() {
586        let lp = LinearProgram::new(vec![1.0, 2.0], vec![vec![1.0, 1.0]], vec![10.0]);
587        assert!((lp.objective(&[3.0, 4.0]) - 11.0).abs() < 1e-10);
588    }
589    #[test]
590    fn test_inequality_lp_to_standard() {
591        let ilp = InequalityLP::new(
592            vec![1.0, 1.0],
593            vec![vec![1.0, 1.0], vec![1.0, 0.0]],
594            vec![4.0, 3.0],
595        );
596        let std_lp = ilp.to_standard_form();
597        assert_eq!(std_lp.n_vars, 4);
598        assert_eq!(std_lp.n_constraints, 2);
599        assert_eq!(std_lp.c[2], 0.0);
600        assert_eq!(std_lp.c[3], 0.0);
601    }
602    #[test]
603    fn test_knapsack_greedy() {
604        let (sel, val) = knapsack_greedy(&[2.0, 3.0], &[4.0, 5.0], 4.0);
605        assert!(sel[0]);
606        assert!(val > 0.0);
607    }
608    #[test]
609    fn test_knapsack_dp() {
610        let (sel, max_val) = knapsack_dp(&[1, 3, 4, 5], &[1, 4, 5, 7], 7);
611        assert_eq!(max_val, 9);
612        assert!(sel[1] && sel[2]);
613    }
614    #[test]
615    fn test_knapsack_fractional() {
616        let (fracs, val) = knapsack_fractional(&[10.0, 20.0, 30.0], &[60.0, 100.0, 120.0], 50.0);
617        assert!((fracs[0] - 1.0).abs() < 1e-10);
618        assert!((fracs[1] - 1.0).abs() < 1e-10);
619        assert!((fracs[2] - 2.0 / 3.0).abs() < 1e-10);
620        assert!((val - 240.0).abs() < 1e-10);
621    }
622    #[test]
623    fn test_lp_dual_size() {
624        let lp = LinearProgram::new(
625            vec![1.0, 2.0],
626            vec![vec![1.0, 0.0], vec![0.0, 1.0], vec![1.0, 1.0]],
627            vec![4.0, 6.0, 8.0],
628        );
629        let dual = lp.dual();
630        assert_eq!(dual.n_vars, lp.n_constraints);
631        assert_eq!(dual.n_constraints, lp.n_vars);
632    }
633    #[test]
634    fn test_integer_program_new() {
635        let lp = LinearProgram::new(vec![1.0, 1.0], vec![vec![1.0, 1.0]], vec![5.0]);
636        let ip = IntegerProgram::new(lp, vec![0, 1]);
637        assert_eq!(ip.integer_vars, vec![0, 1]);
638    }
639    #[test]
640    fn test_lp_result_display() {
641        let r = LpResult::Optimal {
642            objective: 2.72,
643            solution: vec![1.0, 2.0],
644        };
645        let s = format!("{}", r);
646        assert!(s.contains("Optimal"));
647        assert!(s.contains("2.72"));
648        assert_eq!(format!("{}", LpResult::Infeasible), "Infeasible");
649        assert_eq!(format!("{}", LpResult::Unbounded), "Unbounded");
650    }
651    #[test]
652    fn test_transportation_northwest() {
653        let tp = TransportationProblem::new(
654            vec![20.0, 30.0],
655            vec![10.0, 20.0, 20.0],
656            vec![vec![8.0, 6.0, 10.0], vec![9.0, 12.0, 7.0]],
657        );
658        assert!(tp.is_balanced());
659        let alloc = tp.northwest_corner();
660        let row0: f64 = alloc[0].iter().sum();
661        let row1: f64 = alloc[1].iter().sum();
662        assert!((row0 - 20.0).abs() < 1e-9);
663        assert!((row1 - 30.0).abs() < 1e-9);
664    }
665    #[test]
666    fn test_transportation_vogel() {
667        let tp = TransportationProblem::new(
668            vec![20.0, 30.0],
669            vec![10.0, 20.0, 20.0],
670            vec![vec![8.0, 6.0, 10.0], vec![9.0, 12.0, 7.0]],
671        );
672        let nw_cost = tp.total_cost(&tp.northwest_corner());
673        let vogel_cost = tp.total_cost(&tp.vogel_approximation());
674        assert!(vogel_cost <= nw_cost + 1e-6);
675    }
676    #[test]
677    fn test_interior_point_simple() {
678        let solver = InteriorPointSolver::new();
679        let result = solver.solve(&[-1.0], &[vec![1.0]], &[5.0]);
680        match result {
681            LpResult::Optimal {
682                objective,
683                solution,
684            } => {
685                assert!(solution[0] > 3.0, "x={}", solution[0]);
686                assert!(objective < -3.0, "obj={}", objective);
687            }
688            _ => panic!("Expected optimal"),
689        }
690    }
691    #[test]
692    fn test_mat_vec_mul() {
693        let a = vec![vec![1.0, 2.0], vec![3.0, 4.0]];
694        let r = mat_vec_mul(&a, &[1.0, 1.0]);
695        assert!((r[0] - 3.0).abs() < 1e-10);
696        assert!((r[1] - 7.0).abs() < 1e-10);
697    }
698    #[test]
699    fn test_transpose() {
700        let a = vec![vec![1.0, 2.0], vec![3.0, 4.0]];
701        let t = transpose(&a);
702        assert!((t[0][0] - 1.0).abs() < 1e-10);
703        assert!((t[0][1] - 3.0).abs() < 1e-10);
704    }
705    #[test]
706    fn test_dot_product() {
707        assert!((dot(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0]) - 32.0).abs() < 1e-10);
708    }
709    #[test]
710    fn test_vec_operations() {
711        assert!((vec_add(&[1.0, 2.0], &[3.0, 4.0])[0] - 4.0).abs() < 1e-10);
712        assert!((vec_sub(&[3.0, 4.0], &[1.0, 2.0])[0] - 2.0).abs() < 1e-10);
713        assert!((vec_scale(&[1.0, 2.0], 3.0)[1] - 6.0).abs() < 1e-10);
714        assert!((vec_norm(&[3.0, 4.0]) - 5.0).abs() < 1e-10);
715    }
716    #[test]
717    fn test_assignment_greedy() {
718        let cost = vec![
719            vec![9.0, 2.0, 7.0],
720            vec![6.0, 4.0, 3.0],
721            vec![5.0, 8.0, 1.0],
722        ];
723        let (assignment, total) = assignment_greedy(&cost);
724        let mut jobs: Vec<usize> = assignment.clone();
725        jobs.sort();
726        jobs.dedup();
727        assert_eq!(jobs.len(), 3);
728        assert!(total > 0.0);
729    }
730    #[test]
731    fn test_shadow_prices() {
732        let lp = LinearProgram::new(vec![1.0], vec![vec![1.0]], vec![3.0]);
733        let prices = lp.shadow_prices();
734        assert_eq!(prices.len(), 1);
735    }
736    #[test]
737    fn test_rhs_sensitivity() {
738        let lp = LinearProgram::new(vec![1.0], vec![vec![1.0]], vec![3.0]);
739        let ranges = rhs_sensitivity(&lp);
740        assert_eq!(ranges.len(), 1);
741        assert!(ranges[0].0 <= 3.0);
742        assert!(ranges[0].1 >= 3.0);
743    }
744    #[test]
745    fn test_lp_solve_inequality() {
746        let ilp = InequalityLP::new(
747            vec![-1.0, -1.0],
748            vec![vec![1.0, 1.0], vec![1.0, 0.0], vec![0.0, 1.0]],
749            vec![10.0, 6.0, 8.0],
750        );
751        match ilp.solve() {
752            LpResult::Optimal {
753                objective,
754                solution,
755            } => {
756                assert!(
757                    objective < -9.0,
758                    "obj should be near -10, got {}",
759                    objective
760                );
761                assert!(solution[0] + solution[1] > 9.0, "sum should be near 10");
762            }
763            other => panic!("Expected Optimal, got {:?}", other),
764        }
765    }
766    #[test]
767    fn test_unbalanced_transportation() {
768        let tp = TransportationProblem::new(
769            vec![10.0, 20.0],
770            vec![15.0, 25.0],
771            vec![vec![1.0, 2.0], vec![3.0, 4.0]],
772        );
773        assert!(!tp.is_balanced());
774    }
775    #[test]
776    fn test_build_linear_programming_env() {
777        let mut env = Environment::new();
778        build_linear_programming_env(&mut env);
779        assert!(!env.is_empty());
780    }
781    #[test]
782    fn test_parametric_optimal_value_ty() {
783        let ty = parametric_optimal_value_ty();
784        assert!(matches!(ty, oxilean_kernel::Expr::Pi(_, _, _, _)));
785    }
786    #[test]
787    fn test_sensitivity_range_ty() {
788        let ty = sensitivity_range_ty();
789        assert!(matches!(ty, oxilean_kernel::Expr::Pi(_, _, _, _)));
790    }
791    #[test]
792    fn test_min_cost_flow_ty() {
793        let ty = min_cost_flow_ty();
794        assert!(matches!(ty, oxilean_kernel::Expr::Pi(_, _, _, _)));
795    }
796    #[test]
797    fn test_column_generation_master_ty() {
798        let ty = column_generation_master_ty();
799        assert!(matches!(ty, oxilean_kernel::Expr::Pi(_, _, _, _)));
800    }
801    #[test]
802    fn test_benders_cuts_ty() {
803        let f_ty = benders_feasibility_cut_ty();
804        let o_ty = benders_optimality_cut_ty();
805        assert!(matches!(f_ty, oxilean_kernel::Expr::Pi(_, _, _, _)));
806        assert!(matches!(o_ty, oxilean_kernel::Expr::Pi(_, _, _, _)));
807    }
808    #[test]
809    fn test_ellipsoid_axiom_tys() {
810        assert!(matches!(
811            khachian_polynomial_lp_ty(),
812            oxilean_kernel::Expr::Sort(_)
813        ));
814        assert!(matches!(
815            ellipsoid_feasibility_ty(),
816            oxilean_kernel::Expr::Pi(_, _, _, _)
817        ));
818    }
819    #[test]
820    fn test_stochastic_lp_tys() {
821        assert!(matches!(
822            two_stage_recourse_ty(),
823            oxilean_kernel::Expr::Pi(_, _, _, _)
824        ));
825        assert!(matches!(
826            expected_value_perfect_info_ty(),
827            oxilean_kernel::Expr::Pi(_, _, _, _)
828        ));
829    }
830    #[test]
831    fn test_robust_lp_tys() {
832        assert!(matches!(
833            uncertainty_set_ty(),
834            oxilean_kernel::Expr::Pi(_, _, _, _)
835        ));
836        assert!(matches!(
837            worst_case_robust_ty(),
838            oxilean_kernel::Expr::Pi(_, _, _, _)
839        ));
840    }
841    #[test]
842    fn test_conic_tys() {
843        assert!(matches!(
844            second_order_cone_ty(),
845            oxilean_kernel::Expr::Pi(_, _, _, _)
846        ));
847        assert!(matches!(
848            semidefinite_program_ty(),
849            oxilean_kernel::Expr::Pi(_, _, _, _)
850        ));
851    }
852    #[test]
853    fn test_gomory_cut_ty() {
854        assert!(matches!(
855            gomory_cut_ty(),
856            oxilean_kernel::Expr::Pi(_, _, _, _)
857        ));
858    }
859    #[test]
860    fn test_lagrangian_duality_ty() {
861        assert!(matches!(
862            lagrangian_duality_ty(),
863            oxilean_kernel::Expr::Pi(_, _, _, _)
864        ));
865    }
866    #[test]
867    fn test_lcp_solution_ty() {
868        assert!(matches!(
869            lcp_solution_ty(),
870            oxilean_kernel::Expr::Pi(_, _, _, _)
871        ));
872    }
873    #[test]
874    fn test_competitive_ratio_lp_ty() {
875        assert!(matches!(
876            competitive_ratio_lp_ty(),
877            oxilean_kernel::Expr::Pi(_, _, _, _)
878        ));
879    }
880    #[test]
881    fn test_network_simplex_empty() {
882        let solver = NetworkSimplexSolver::new(0, vec![], vec![]);
883        let result = solver.solve();
884        assert!(result.is_some());
885        let (flows, cost) = result.expect("result should be valid");
886        assert!(flows.is_empty());
887        assert!((cost).abs() < 1e-10);
888    }
889    #[test]
890    fn test_network_simplex_single_edge() {
891        let edges = vec![NetworkEdge {
892            from: 0,
893            to: 1,
894            capacity: 10.0,
895            cost: 2.0,
896        }];
897        let supply = vec![5.0, 0.0];
898        let solver = NetworkSimplexSolver::new(2, edges, supply);
899        let result = solver.solve();
900        assert!(result.is_some());
901        let (flows, cost) = result.expect("result should be valid");
902        assert!(!flows.is_empty());
903        assert!(cost.is_finite());
904    }
905    #[test]
906    fn test_network_simplex_total_cost() {
907        let edges = vec![
908            NetworkEdge {
909                from: 0,
910                to: 1,
911                capacity: 10.0,
912                cost: 3.0,
913            },
914            NetworkEdge {
915                from: 0,
916                to: 2,
917                capacity: 10.0,
918                cost: 2.0,
919            },
920        ];
921        let solver = NetworkSimplexSolver::new(3, edges, vec![5.0, -2.0, -3.0]);
922        let flows = vec![2.0, 3.0];
923        let cost = solver.total_cost(&flows);
924        assert!((cost - 12.0).abs() < 1e-10);
925    }
926    #[test]
927    fn test_benders_new() {
928        let bd = BendersDecomposition::new(
929            vec![1.0],
930            vec![vec![1.0]],
931            vec![10.0],
932            vec![ScenarioData {
933                probability: 1.0,
934                b_second: vec![5.0],
935                c_second: vec![2.0],
936            }],
937        );
938        assert_eq!(bd.c_first.len(), 1);
939        assert_eq!(bd.scenarios.len(), 1);
940    }
941    #[test]
942    fn test_benders_second_stage_cost() {
943        let bd = BendersDecomposition::new(
944            vec![0.0],
945            vec![vec![0.0]],
946            vec![1.0],
947            vec![
948                ScenarioData {
949                    probability: 0.5,
950                    b_second: vec![4.0],
951                    c_second: vec![1.0],
952                },
953                ScenarioData {
954                    probability: 0.5,
955                    b_second: vec![8.0],
956                    c_second: vec![1.0],
957                },
958            ],
959        );
960        let cost = bd.second_stage_cost(&[0.0]);
961        assert!(cost >= 0.0);
962    }
963    #[test]
964    fn test_benders_solve() {
965        let bd = BendersDecomposition::new(
966            vec![1.0],
967            vec![vec![1.0]],
968            vec![5.0],
969            vec![ScenarioData {
970                probability: 1.0,
971                b_second: vec![3.0],
972                c_second: vec![2.0],
973            }],
974        );
975        let result = bd.solve();
976        assert!(result.is_some());
977        let (x, obj) = result.expect("result should be valid");
978        assert!(!x.is_empty());
979        assert!(obj.is_finite());
980    }
981    #[test]
982    fn test_column_generation_initial_patterns() {
983        let cg = ColumnGenerationSolver::new(vec![3.0, 4.0, 5.0], vec![2, 3, 1], 10.0);
984        let patterns = cg.initial_patterns();
985        assert_eq!(patterns.len(), 3);
986        assert_eq!(patterns[0][0], 3);
987    }
988    #[test]
989    fn test_column_generation_solve() {
990        let cg = ColumnGenerationSolver::new(vec![3.0], vec![0], 9.0);
991        let result = cg.solve();
992        assert!(result.is_some());
993        let (patterns, x, obj) = result.expect("result should be valid");
994        assert!(!patterns.is_empty());
995        assert!(!x.is_empty());
996        assert!(obj >= 0.0);
997    }
998    #[test]
999    fn test_ellipsoid_trivially_feasible() {
1000        let solver = EllipsoidMethodSolver::new();
1001        let a = vec![vec![1.0]];
1002        let b = vec![10.0];
1003        let result = solver.find_feasible(&a, &b);
1004        assert!(result.is_some());
1005    }
1006    #[test]
1007    fn test_ellipsoid_lp_feasible() {
1008        let solver = EllipsoidMethodSolver::new();
1009        let a = vec![vec![1.0, 0.0], vec![0.0, 1.0]];
1010        let b = vec![5.0, 5.0];
1011        assert!(solver.lp_feasible(&[1.0, 1.0], &a, &b));
1012    }
1013    #[test]
1014    fn test_ellipsoid_infeasible() {
1015        let solver = EllipsoidMethodSolver::with_params(200, 1e-8, 100.0);
1016        let a = vec![vec![1.0]];
1017        let b = vec![-100.0];
1018        let _result = solver.find_feasible(&a, &b);
1019    }
1020    #[test]
1021    fn test_ellipsoid_empty() {
1022        let solver = EllipsoidMethodSolver::new();
1023        let result = solver.find_feasible(&[], &[]);
1024        assert!(result.is_some());
1025    }
1026    #[test]
1027    fn test_gomory_new() {
1028        let gen = GomoryCutGenerator::new();
1029        assert_eq!(gen.max_cuts, 20);
1030    }
1031    #[test]
1032    fn test_gomory_generate_cuts_integer_solution() {
1033        let gen = GomoryCutGenerator::new();
1034        let lp = LinearProgram::new(vec![1.0], vec![vec![1.0]], vec![3.0]);
1035        let cuts = gen.generate_cuts(&[3.0], &lp);
1036        assert!(cuts.is_empty());
1037    }
1038    #[test]
1039    fn test_gomory_generate_cuts_fractional() {
1040        let gen = GomoryCutGenerator::new();
1041        let lp = LinearProgram::new(vec![1.0, 1.0], vec![vec![1.0, 1.0]], vec![5.0]);
1042        let cuts = gen.generate_cuts(&[1.5, 2.5], &lp);
1043        assert!(!cuts.is_empty());
1044        assert!(cuts[0].rhs >= 0.0);
1045    }
1046    #[test]
1047    fn test_gomory_solve_with_cuts_simple() {
1048        let gen = GomoryCutGenerator::new();
1049        let lp = LinearProgram::new(vec![1.0], vec![vec![1.0]], vec![3.0]);
1050        let result = gen.solve_with_cuts(&lp);
1051        assert!(matches!(result, LpResult::Optimal { .. }));
1052    }
1053    #[test]
1054    fn test_gomory_with_params() {
1055        let gen = GomoryCutGenerator::with_params(10, 1e-4);
1056        assert_eq!(gen.max_cuts, 10);
1057        assert!((gen.tolerance - 1e-4).abs() < 1e-15);
1058    }
1059    #[test]
1060    fn test_build_lp_env_has_new_axioms() {
1061        let mut env = Environment::new();
1062        build_linear_programming_env(&mut env);
1063        let has_min_cost = env.get(&Name::str("MinCostFlow")).is_some();
1064        let has_benders = env.get(&Name::str("BendersOptimalityCut")).is_some();
1065        let has_gomory = env.get(&Name::str("GomoryCut")).is_some();
1066        let has_robust = env.get(&Name::str("WorstCaseRobust")).is_some();
1067        let has_online = env.get(&Name::str("OnlineLpPrimalDual")).is_some();
1068        assert!(has_min_cost);
1069        assert!(has_benders);
1070        assert!(has_gomory);
1071        assert!(has_robust);
1072        assert!(has_online);
1073    }
1074}