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