1use crate::param::{ALGORITHM_PARAM_ID, OBJSENSE_PARAM_ID, REPR_PARAM_ID};
2use crate::soplex_ptr::SoplexPtr;
3use crate::status::Status;
4use crate::{
5 ffi, BoolParam, ColBasisStatus, IntParam, ObjSense, RealParam, RowBasisStatus, Verbosity,
6};
7
8pub struct Model {
10 inner: SoplexPtr,
11}
12
13pub type RowId = usize;
15
16pub type ColId = usize;
30
31impl Default for Model {
32 fn default() -> Self {
33 Self::new()
34 }
35}
36
37impl Model {
38 pub fn new() -> Self {
40 Self {
41 inner: SoplexPtr::new(),
42 }
43 }
44
45 pub fn add_col(&mut self, mut colentries: Vec<f64>, objval: f64, lb: f64, ub: f64) -> ColId {
58 let nnonzeros = colentries.iter().filter(|&&x| x != 0.0).count();
59 let colsize = colentries.len();
60
61 unsafe {
62 ffi::SoPlex_addColReal(
63 *self.inner,
64 colentries.as_mut_ptr(),
65 colsize as i32,
66 nnonzeros as i32,
67 objval,
68 lb,
69 ub,
70 );
71 }
72
73 self.num_cols() - 1
74 }
75
76 pub fn add_row(&mut self, mut rowentries: Vec<f64>, lhs: f64, rhs: f64) -> RowId {
88 let nnonzeros = rowentries.iter().filter(|&&x| x != 0.0).count();
89 let rowsize = rowentries.len();
90
91 unsafe {
92 ffi::SoPlex_addRowReal(
93 *self.inner,
94 rowentries.as_mut_ptr(),
95 rowsize as i32,
96 nnonzeros as i32,
97 lhs,
98 rhs,
99 );
100 }
101
102 self.num_rows() - 1
103 }
104
105 pub fn optimize(self) -> SolvedModel {
107 unsafe { ffi::SoPlex_optimize(*self.inner) };
108 SolvedModel { inner: self.inner }
109 }
110
111 pub fn num_cols(&self) -> usize {
113 unsafe { ffi::SoPlex_numCols(*self.inner) as usize }
114 }
115
116 pub fn num_rows(&self) -> usize {
118 unsafe { ffi::SoPlex_numRows(*self.inner) as usize }
119 }
120
121 pub fn remove_col(&mut self, col_id: ColId) {
123 unsafe { ffi::SoPlex_removeColReal(*self.inner, col_id as i32) };
124 }
125
126 pub fn remove_row(&mut self, row_id: RowId) {
128 unsafe { ffi::SoPlex_removeRowReal(*self.inner, row_id as i32) };
129 }
130
131 pub fn read_file(&mut self, filename: &str) {
139 if !std::path::Path::new(filename).exists() {
140 panic!("File does not exist");
141 }
142
143 if !filename.ends_with(".lp") && !filename.ends_with(".mps") {
144 panic!("File is not in the correct format, must be .lp or .mps");
145 }
146
147 let c_filename = std::ffi::CString::new(filename).unwrap();
148 let success = unsafe { ffi::SoPlex_readInstanceFile(*self.inner, c_filename.as_ptr()) };
149
150 if success == 0 {
151 panic!("Unexpected failure in reading file: {}", filename);
152 }
153 }
154
155 pub fn set_bool_param(&mut self, param: BoolParam, value: bool) {
161 unsafe {
162 ffi::SoPlex_setBoolParam(*self.inner, param.into(), value as i32);
163 }
164 }
165
166 pub fn set_int_param(&mut self, param: IntParam, value: i32) {
172 unsafe {
173 ffi::SoPlex_setIntParam(*self.inner, param.into(), value);
174 }
175 }
176
177 pub fn set_real_param(&mut self, param: RealParam, value: f64) {
183 unsafe {
184 ffi::SoPlex_setRealParam(*self.inner, param.into(), value);
185 }
186 }
187
188 pub fn change_col_bounds(&mut self, col_id: ColId, lb: f64, ub: f64) {
195 unsafe {
196 ffi::SoPlex_changeVarBoundsReal(*self.inner, col_id as i32, lb, ub);
197 }
198 }
199
200 pub fn change_row_range(&mut self, row_id: RowId, lhs: f64, rhs: f64) {
207 unsafe {
208 ffi::SoPlex_changeRowRangeReal(*self.inner, row_id as i32, lhs, rhs);
209 }
210 }
211
212 pub fn set_obj_sense(&mut self, sense: ObjSense) {
217 unsafe {
218 ffi::SoPlex_setIntParam(*self.inner, OBJSENSE_PARAM_ID, sense.into());
219 }
220 }
221
222 pub fn set_algorithm(&mut self, algorithm: crate::Algorithm) {
227 unsafe {
228 ffi::SoPlex_setIntParam(*self.inner, ALGORITHM_PARAM_ID, algorithm.into());
229 }
230 }
231
232 pub fn set_representation(&mut self, representation: crate::Representation) {
237 unsafe {
238 ffi::SoPlex_setIntParam(*self.inner, REPR_PARAM_ID, representation.into());
239 }
240 }
241
242 pub fn set_verbosity(&mut self, verbosity: Verbosity) {
247 unsafe {
248 ffi::SoPlex_setIntParam(*self.inner, crate::VERBOSITY_PARAM_ID, verbosity.into());
249 }
250 }
251
252 pub fn set_factor_update_type(&mut self, factor_update_type: crate::FactorUpdateType) {
257 unsafe {
258 ffi::SoPlex_setIntParam(
259 *self.inner,
260 crate::FACTOR_UPDATE_TYPE_PARAM_ID,
261 factor_update_type.into(),
262 );
263 }
264 }
265
266 pub fn set_simplifier_type(&mut self, simplifier_type: crate::Simplifier) {
271 unsafe {
272 ffi::SoPlex_setIntParam(
273 *self.inner,
274 crate::SIMPLIFIER_PARAM_ID,
275 simplifier_type.into(),
276 );
277 }
278 }
279
280 pub fn set_starter_type(&mut self, starter_type: crate::Starter) {
285 unsafe {
286 ffi::SoPlex_setIntParam(*self.inner, crate::STARTER_PARAM_ID, starter_type.into());
287 }
288 }
289
290 pub fn set_pricer_type(&mut self, pricer_type: crate::Pricer) {
295 unsafe {
296 ffi::SoPlex_setIntParam(*self.inner, crate::PRICER_PARAM_ID, pricer_type.into());
297 }
298 }
299
300 pub fn set_ratio_tester_type(&mut self, ratio_tester_type: crate::RatioTester) {
305 unsafe {
306 ffi::SoPlex_setIntParam(
307 *self.inner,
308 crate::RATIO_TESTER_PARAM_ID,
309 ratio_tester_type.into(),
310 );
311 }
312 }
313
314 pub fn set_sync_mode(&mut self, sync_mode: crate::SyncMode) {
319 unsafe {
320 ffi::SoPlex_setIntParam(*self.inner, crate::SYNC_MODE_PARAM_ID, sync_mode.into());
321 }
322 }
323
324 pub fn set_read_mode(&mut self, read_mode: crate::ReadMode) {
329 unsafe {
330 ffi::SoPlex_setIntParam(*self.inner, crate::READ_MODE_PARAM_ID, read_mode.into());
331 }
332 }
333
334 pub fn set_solve_mode(&mut self, solve_mode: crate::SolveMode) {
339 unsafe {
340 ffi::SoPlex_setIntParam(*self.inner, crate::SOLVE_MODE_PARAM_ID, solve_mode.into());
341 }
342 }
343
344 pub fn set_check_mode(&mut self, check_mode: crate::CheckMode) {
349 unsafe {
350 ffi::SoPlex_setIntParam(*self.inner, crate::CHECK_MODE_PARAM_ID, check_mode.into());
351 }
352 }
353
354 pub fn set_timer_mode(&mut self, timer_mode: crate::Timer) {
359 unsafe {
360 ffi::SoPlex_setIntParam(*self.inner, crate::TIMER_PARAM_ID, timer_mode.into());
361 }
362 }
363
364 pub fn set_hyper_pricing(&mut self, hyper_pricing: crate::HyperPricing) {
369 unsafe {
370 ffi::SoPlex_setIntParam(
371 *self.inner,
372 crate::HYPER_PRICING_PARAM_ID,
373 hyper_pricing.into(),
374 );
375 }
376 }
377
378 pub fn set_solution_polishing(&mut self, solution_polishing: crate::SolutionPolishing) {
383 unsafe {
384 ffi::SoPlex_setIntParam(
385 *self.inner,
386 crate::SOLUTION_POLISHING_PARAM_ID,
387 solution_polishing.into(),
388 );
389 }
390 }
391
392 pub fn set_decomp_verbosity(&mut self, decomp_verbosity: crate::Verbosity) {
397 unsafe {
398 ffi::SoPlex_setIntParam(
399 *self.inner,
400 crate::DECOMP_VERBOSITY_PARAM_ID,
401 decomp_verbosity.into(),
402 );
403 }
404 }
405
406 pub fn set_stat_timer(&mut self, stat_timer: crate::Timer) {
411 unsafe {
412 ffi::SoPlex_setIntParam(*self.inner, crate::STAT_TIMER_PARAM_ID, stat_timer.into());
413 }
414 }
415
416 pub fn set_scalar_type(&mut self, scalar_type: crate::Scalar) {
421 unsafe {
422 ffi::SoPlex_setIntParam(*self.inner, crate::SCALAR_PARAM_ID, scalar_type.into());
423 }
424 }
425
426 pub fn set_obj_vals(&mut self, objvals: &mut [f64]) {
431 let num_cols = self.num_cols();
432 assert_eq!(
433 objvals.len(),
434 num_cols,
435 "objvals must have the same length as the number of columns"
436 );
437 unsafe {
438 ffi::SoPlex_changeObjReal(*self.inner, objvals.as_mut_ptr(), objvals.len() as i32);
439 }
440 }
441
442 pub fn obj_sense(&self) -> ObjSense {
444 unsafe { ffi::SoPlex_getIntParam(*self.inner, OBJSENSE_PARAM_ID) }.into()
445 }
446}
447
448pub struct SolvedModel {
450 inner: SoplexPtr,
451}
452
453impl SolvedModel {
454 pub fn num_cols(&self) -> usize {
456 unsafe { ffi::SoPlex_numCols(*self.inner) as usize }
457 }
458
459 pub fn num_rows(&self) -> usize {
461 unsafe { ffi::SoPlex_numRows(*self.inner) as usize }
462 }
463
464 pub fn status(&self) -> Status {
466 unsafe { ffi::SoPlex_getStatus(*self.inner) }.into()
467 }
468
469 pub fn obj_val(&self) -> f64 {
471 unsafe { ffi::SoPlex_objValueReal(*self.inner) }
472 }
473
474 pub fn primal_solution(&self) -> Vec<f64> {
476 let mut primal = vec![0.0; self.num_cols()];
477 unsafe {
478 ffi::SoPlex_getPrimalReal(*self.inner, primal.as_mut_ptr(), self.num_cols() as i32);
479 }
480 primal
481 }
482
483 pub fn dual_solution(&self) -> Vec<f64> {
485 let mut dual = vec![0.0; self.num_rows()];
486 unsafe {
487 ffi::SoPlex_getDualReal(*self.inner, dual.as_mut_ptr(), self.num_rows() as i32);
488 }
489 dual
490 }
491
492 pub fn solving_time(&self) -> f64 {
494 unsafe { ffi::SoPlex_getSolvingTime(*self.inner) }
495 }
496
497 pub fn reduced_costs(&self) -> Vec<f64> {
499 let mut redcosts = vec![0.0; self.num_cols()];
500 unsafe {
501 ffi::SoPlex_getRedCostReal(*self.inner, redcosts.as_mut_ptr(), self.num_cols() as i32);
502 }
503 redcosts
504 }
505
506 pub fn num_iterations(&self) -> i32 {
508 unsafe { ffi::SoPlex_getNumIterations(*self.inner) }
509 }
510
511 pub fn col_basis_status(&self, col_id: ColId) -> ColBasisStatus {
519 unsafe { ffi::SoPlex_basisColStatus(*self.inner, col_id as i32) }.into()
520 }
521
522 pub fn row_basis_status(&self, row_id: RowId) -> RowBasisStatus {
530 unsafe { ffi::SoPlex_basisRowStatus(*self.inner, row_id as i32) }.into()
531 }
532}
533
534impl From<SolvedModel> for Model {
535 fn from(solved_model: SolvedModel) -> Self {
536 Self {
537 inner: solved_model.inner,
538 }
539 }
540}
541
542#[cfg(test)]
543mod tests {
544 use super::*;
545 use crate::Algorithm;
546
547 #[test]
548 fn simple_problem() {
549 let mut lp = Model::new();
550 let col1 = lp.add_col(vec![], 1.0, 0.0, 5.0);
551 let _col2 = lp.add_col(vec![], 1.0, 0.0, 10.0);
552 let row = lp.add_row(vec![1.0, 1.0], 1.0, 5.0);
553 assert_eq!(lp.num_cols(), 2);
554 assert_eq!(lp.num_rows(), 1);
555
556 let lp = lp.optimize();
557 let result = lp.status();
558 assert_eq!(result, Status::Optimal);
559 assert!((lp.obj_val() - 5.0).abs() < 1e-6);
560 let dual_sol = lp.dual_solution();
561 assert_eq!(dual_sol.len(), 1);
562 assert!((dual_sol[0] - 1.0).abs() < 1e-6);
563
564 let mut lp = Model::from(lp);
565 lp.remove_row(row);
566 assert_eq!(lp.num_rows(), 0);
567 let lp = lp.optimize();
568 let new_result = lp.status();
569 assert_eq!(new_result, Status::Optimal);
570 assert!((lp.obj_val() - 15.0).abs() < 1e-6);
571 let primal_sol = lp.primal_solution();
572 assert_eq!(primal_sol.len(), 2);
573 assert!((primal_sol[0] - 5.0).abs() < 1e-6);
574 assert!((primal_sol[1] - 10.0).abs() < 1e-6);
575
576 let mut lp = Model::from(lp);
577 lp.remove_col(col1);
578 assert_eq!(lp.num_cols(), 1);
579 let lp = lp.optimize();
580 let new_result = lp.status();
581 assert_eq!(new_result, Status::Optimal);
582 assert!((lp.obj_val() - 10.0).abs() < 1e-6);
583
584 assert!(lp.solving_time() >= 0.0);
585 }
586
587 #[test]
588 fn read_file() {
589 let mut lp = Model::new();
590 lp.read_file("tests/data/simple.mps");
591 let lp = lp.optimize();
592 let result = lp.status();
593 assert_eq!(result, Status::Optimal);
594 assert!((lp.obj_val() - -27.66666666).abs() < 1e-6);
595 }
596
597 #[test]
598 fn num_iterations() {
599 let mut lp = Model::new();
600 lp.add_col(vec![], 1.0, 0.0, 5.0);
601 lp.add_col(vec![], 1.0, 0.0, 10.0);
602 lp.add_row(vec![1.0, 1.0], 1.0, 5.0);
603 let lp = lp.optimize();
604 let num_iterations = lp.num_iterations();
605 assert_eq!(num_iterations, 1);
606 }
607
608 #[test]
609 fn set_int_param() {
610 let mut lp = Model::new();
611 lp.set_int_param(IntParam::IterLimit, 0);
612 lp.add_col(vec![], 1.0, 0.0, 5.0);
613 lp.add_col(vec![], 1.0, 0.0, 10.0);
614 lp.add_row(vec![1.0, 1.0], 1.0, 5.0);
615 let lp = lp.optimize();
616 let num_iterations = lp.num_iterations();
617 assert_eq!(num_iterations, 0);
618 assert_eq!(lp.status(), Status::AbortIter);
619 }
620
621 #[test]
622 fn set_real_param() {
623 let mut lp = Model::new();
624 lp.set_real_param(RealParam::TimeLimit, 0.0);
625 lp.add_col(vec![], 1.0, 0.0, 5.0);
626 lp.add_col(vec![], 1.0, 0.0, 10.0);
627 lp.add_row(vec![1.0, 1.0], 1.0, 5.0);
628 let lp = lp.optimize();
629 assert_eq!(lp.status(), Status::AbortTime);
630 }
631
632 #[test]
633 fn set_bool_param() {
634 let mut lp = Model::new();
638 lp.set_bool_param(BoolParam::EqTrans, true);
639 lp.add_col(vec![], 1.0, 0.0, 5.0);
640 lp.add_col(vec![], 1.0, 0.0, 10.0);
641 lp.add_row(vec![1.0, 1.0], 1.0, 5.0);
642 let lp = lp.optimize();
643 assert_eq!(lp.status(), Status::Optimal);
644 }
645
646 #[test]
647 fn change_col_bounds() {
648 let mut lp = Model::new();
649 let col1 = lp.add_col(vec![], 1.0, 0.0, 5.0);
650 lp.change_col_bounds(col1, 0.0, 10.0);
651
652 let lp = lp.optimize();
653 let result = lp.status();
654 assert_eq!(result, Status::Optimal);
655 assert!((lp.obj_val() - 10.0).abs() < 1e-6);
656 }
657
658 #[test]
659 fn change_row_range() {
660 let mut lp = Model::new();
661 lp.add_col(vec![], 1.0, 1.0, 5.0);
662 lp.add_col(vec![], 1.0, 1.0, 10.0);
663 let row = lp.add_row(vec![1.0, 1.0], 1.0, 5.0);
664 lp.change_row_range(row, 0.0, 0.0);
665
666 let lp = lp.optimize();
667 let result = lp.status();
668 assert_eq!(result, Status::Infeasible);
669 }
670
671 #[test]
672 fn basis_status() {
673 let mut lp = Model::new();
674 let col1 = lp.add_col(vec![], 1.0, 0.0, 5.0);
675 let _col2 = lp.add_col(vec![], 1.0, 0.0, 10.0);
676 let row = lp.add_row(vec![1.0, 1.0], 1.0, 5.0);
677 let lp = lp.optimize();
678 let col_basis_status = lp.col_basis_status(col1);
679 let row_basis_status = lp.row_basis_status(row);
680 assert_eq!(col_basis_status, ColBasisStatus::AtLower);
681 assert_eq!(row_basis_status, RowBasisStatus::AtUpper);
682 }
683
684 #[test]
685 fn set_obj_sense() {
686 let mut lp = Model::new();
687 lp.set_obj_sense(ObjSense::Minimize);
688 lp.add_col(vec![], 1.0, 1.0, 5.0);
689 let lp = lp.optimize();
690 let result = lp.status();
691 assert_eq!(result, Status::Optimal);
692 assert!((lp.obj_val() - 1.0).abs() < 1e-6);
693 }
694
695 fn small_model() -> Model {
696 let mut lp = Model::new();
697 lp.add_col(vec![], 1.0, 0.0, 5.0);
698 lp.add_col(vec![], 1.0, 0.0, 10.0);
699 lp.add_row(vec![1.0, 1.0], 1.0, 5.0);
700 lp
701 }
702
703 #[test]
704 fn set_algorithm() {
705 let mut lp = small_model();
706 lp.set_algorithm(Algorithm::Primal);
707 let lp = lp.optimize();
708 let result = lp.status();
709 assert_eq!(result, Status::Optimal);
710 assert!((lp.obj_val() - 5.0).abs() < 1e-6);
711
712 let mut lp = small_model();
713 lp.set_algorithm(Algorithm::Dual);
714 let lp = lp.optimize();
715 let result = lp.status();
716 assert_eq!(result, Status::Optimal);
717 assert!((lp.obj_val() - 5.0).abs() < 1e-6);
718 }
719
720 #[test]
721 fn set_objective() {
722 let mut lp = Model::new();
723 lp.add_col(vec![], 1.0, 1.0, 1.0);
724 lp.add_col(vec![], 1.0, 1.0, 1.0);
725 lp.set_obj_vals(&mut [2.0, 3.0]);
726 let lp = lp.optimize();
727 let result = lp.status();
728 assert_eq!(result, Status::Optimal);
729 assert!((lp.obj_val() - 5.0).abs() < 1e-6);
730 }
731
732 #[test]
733 #[should_panic]
734 fn read_non_existent_file_panic() {
735 let mut lp = Model::new();
736 lp.read_file("i_do_not_exist.lp");
737 }
738
739 #[test]
740 #[should_panic]
741 fn read_incorrect_format_file_panic() {
742 let mut lp = Model::new();
743 lp.read_file("tests/data/simple.txt");
744 }
745
746 #[test]
747 fn obj_sense() {
748 let lp = Model::new();
749 assert_eq!(lp.obj_sense(), ObjSense::Maximize);
750 }
751}