soplex_rs/
model.rs

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
8/// A linear programming model.
9pub struct Model {
10    inner: SoplexPtr,
11}
12
13/// Id of a row in the model.
14pub type RowId = usize;
15
16// impl From<RowId> for usize {
17//     fn from(row_id: RowId) -> usize {
18//         row_id.0
19//     }
20// }
21//
22// impl From<ColId> for usize {
23//     fn from(col_id: ColId) -> usize {
24//         col_id.0
25//     }
26// }
27
28/// Id of a column in the model.
29pub type ColId = usize;
30
31impl Default for Model {
32    fn default() -> Self {
33        Self::new()
34    }
35}
36
37impl Model {
38    /// Creates a new linear programming model.
39    pub fn new() -> Self {
40        Self {
41            inner: SoplexPtr::new(),
42        }
43    }
44
45    /// Adds a column to the model.
46    ///
47    /// # Arguments
48    ///
49    /// * `colentries` - An array of f64 representing the column entries.
50    /// * `objval` - The objective value of the column.
51    /// * `lb` - The lower bound of the column.
52    /// * `ub` - The upper bound of the column.
53    ///
54    /// # Returns
55    ///
56    /// The `ColId` of the added column.
57    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    /// Adds a row to the model.
77    ///
78    /// # Arguments
79    ///
80    /// * `rowentries` - An array of f64 representing the row entries.
81    /// * `lhs` - The left-hand side of the row.
82    /// * `rhs` - The right-hand side of the row.
83    ///
84    /// # Returns
85    ///
86    /// The `RowId` of the added row.
87    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    /// Optimizes the model and returns the solved model.
106    pub fn optimize(self) -> SolvedModel {
107        unsafe { ffi::SoPlex_optimize(*self.inner) };
108        SolvedModel { inner: self.inner }
109    }
110
111    /// Returns the number of columns in the model.
112    pub fn num_cols(&self) -> usize {
113        unsafe { ffi::SoPlex_numCols(*self.inner) as usize }
114    }
115
116    /// Returns the number of rows in the model.
117    pub fn num_rows(&self) -> usize {
118        unsafe { ffi::SoPlex_numRows(*self.inner) as usize }
119    }
120
121    /// Remove a column from the model.
122    pub fn remove_col(&mut self, col_id: ColId) {
123        unsafe { ffi::SoPlex_removeColReal(*self.inner, col_id as i32) };
124    }
125
126    /// Remove a row from the model.
127    pub fn remove_row(&mut self, row_id: RowId) {
128        unsafe { ffi::SoPlex_removeRowReal(*self.inner, row_id as i32) };
129    }
130
131    /// Read instance from lp/mps file.
132    ///
133    /// # Arguments
134    /// * `filename` - The name of the lp/mps file to read from.
135    ///
136    /// # Panics
137    /// if the file does not exist or the file is not in the correct format.
138    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    /// Sets boolean parameter.
156    ///
157    /// # Arguments
158    /// * `param` - which `BoolParam` to set.
159    /// * `value` - The value of the parameter.
160    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    /// Sets integer parameter.
167    ///
168    /// # Arguments
169    /// * `param` - which `IntParam` to set.
170    /// * `value` - The value of the parameter.
171    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    /// Sets real parameter.
178    ///
179    /// # Arguments
180    /// * `param` - which `RealParam` to set.
181    /// * `value` - The value of the parameter.
182    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    /// Change the bounds of a column.
189    ///
190    /// # Arguments
191    /// * `col_id` - The `ColId` of the column to change.
192    /// * `lb` - The new lower bound of the column.
193    /// * `ub` - The new upper bound of the column.
194    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    /// Change the range (bounds) of a row.
201    ///
202    /// # Arguments
203    /// * `row_id` - The `RowId` of the row to change.
204    /// * `lhs` - The new left-hand side of the row.
205    /// * `rhs` - The new right-hand side of the row.
206    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    /// Sets the objective sense of the model.
213    ///
214    /// # Arguments
215    /// * `sense` - The objective sense of the model.
216    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    /// Sets the algorithm to use.
223    ///
224    /// # Arguments
225    /// * `algorithm` - The `Algorithm` to use.
226    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    /// Sets the representation of the model.
233    ///
234    /// # Arguments
235    /// * `representation` - The `Representation` of the model.
236    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    /// Sets the verbosity level.
243    ///
244    /// # Arguments
245    /// * `verbosity` - The verbosity level.
246    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    /// Sets the factor update type.
253    ///
254    /// # Arguments
255    /// * `factor_update_type` - The factor update type.
256    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    /// Sets the simplifier type.
267    ///
268    /// # Arguments
269    /// * `simplifier_type` - The simplifier type.
270    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    /// Sets the starter type.
281    ///
282    /// # Arguments
283    /// * `starter_type` - The starter type.
284    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    /// Sets the pricer type.
291    ///
292    /// # Arguments
293    /// * `pricer_type` - The pricer type.
294    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    /// Sets the ratio tester type.
301    ///
302    /// # Arguments
303    /// * `ratio_tester_type` - The ratio tester type.
304    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    /// Sets the sync mode.
315    ///
316    /// # Arguments
317    /// * `sync_mode` - The sync mode.
318    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    /// Sets the read mode.
325    ///
326    /// # Arguments
327    /// * `read_mode` - The read mode.
328    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    /// Sets the solve mode.
335    ///
336    /// # Arguments
337    /// * `solve_mode` - The solve mode.
338    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    /// Sets the check mode.
345    ///
346    /// # Arguments
347    /// * `check_mode` - The check mode.
348    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    /// Sets the timer mode.
355    ///
356    /// # Arguments
357    /// * `timer_mode` - The timer mode.
358    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    /// Sets the hyper pricing parameter.
365    ///
366    /// # Arguments
367    /// * `hyper_pricing` - The hyper pricing parameter.
368    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    /// Sets the solution polishing type.
379    ///
380    /// # Arguments
381    /// * `solution_polishing` - The solution polishing type.
382    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    /// Sets the decomposition verbosity.
393    ///
394    /// # Arguments
395    /// * `decomp_verbosity` - The decomposition verbosity.
396    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    /// Sets the statistics timer parameter.
407    ///
408    /// # Arguments
409    /// * `stat_timer` - The statistics timer parameter.
410    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    /// Sets the scalar type.
417    ///
418    /// # Arguments
419    /// * `scalar_type` - The scalar type.
420    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    /// Sets the objective function vector.
427    ///
428    /// # Arguments
429    /// * `objvals` - The objective function vector.
430    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    /// Gets the objective sense of the model.
443    pub fn obj_sense(&self) -> ObjSense {
444        unsafe { ffi::SoPlex_getIntParam(*self.inner, OBJSENSE_PARAM_ID) }.into()
445    }
446}
447
448/// A solved linear programming model.
449pub struct SolvedModel {
450    inner: SoplexPtr,
451}
452
453impl SolvedModel {
454    /// Returns the number of columns in the model.
455    pub fn num_cols(&self) -> usize {
456        unsafe { ffi::SoPlex_numCols(*self.inner) as usize }
457    }
458
459    /// Returns the number of rows in the model.
460    pub fn num_rows(&self) -> usize {
461        unsafe { ffi::SoPlex_numRows(*self.inner) as usize }
462    }
463
464    /// Returns the `Status` of the model.
465    pub fn status(&self) -> Status {
466        unsafe { ffi::SoPlex_getStatus(*self.inner) }.into()
467    }
468
469    /// Returns the objective value of the model.
470    pub fn obj_val(&self) -> f64 {
471        unsafe { ffi::SoPlex_objValueReal(*self.inner) }
472    }
473
474    /// Returns the primal solution of the model.
475    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    /// Returns the dual solution of the model.
484    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    /// Returns the solving time of the model in seconds.
493    pub fn solving_time(&self) -> f64 {
494        unsafe { ffi::SoPlex_getSolvingTime(*self.inner) }
495    }
496
497    /// Returns the reduced costs of the model.
498    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    /// Returns the number of iterations it took to solve the model.
507    pub fn num_iterations(&self) -> i32 {
508        unsafe { ffi::SoPlex_getNumIterations(*self.inner) }
509    }
510
511    /// Returns the basis status of a column.
512    ///
513    /// # Arguments
514    /// * `col_id` - The `ColId` of the column.
515    ///
516    /// # Returns
517    /// The `BasisStatus` of the column.
518    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    /// Returns the basis status of a row.
523    ///
524    /// # Arguments
525    /// * `row_id` - The `RowId` of the row.
526    ///
527    /// # Returns
528    /// The `BasisStatus` of the row.
529    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        // TODO: think of a better test,
635        // this just sets a parameter to true and makes sure that it runs
636        // from the output, it seems that the parameter is being set
637        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}