1use 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}