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