puanrs/
polyopt.rs

1//! # Polyhedron data structures and functions
2//! 
3//! Ease work for preparing solving (integer) linear programs.
4
5use std::cmp;
6
7use linalg::Matrix;
8
9use crate::linalg::{self, CsrMatrix};
10
11/// Data structure for linear inequalities on the following form
12/// $$ c_0 * v_0 + c_1 * v_1 + ... + c_n * v_n + bias \ge 0 $$ for $ c \in $ `coeffs` and $ v $ are variables which can take on the values
13/// given by `bounds`. `Indices` represents the global indices of the variables. Note that the length of `coeffs`, `bounds` and `indices` must be the same.
14#[derive(Hash)]
15pub struct GeLineq {
16    /// `id` may be used as a reference for later purposes
17    pub id      : Option<u32>,
18    /// Coefficients of the linear inequality
19    pub coeffs  : Vec<i64>,
20    /// Bounds for every variable
21    pub bounds  : Vec<(i64, i64)>,
22    /// `d` in a linear inequality $ ax+by+cz+d >= 0 $
23    pub bias    : i64,
24    /// Id of each index
25    pub indices : Vec<u32>
26}
27
28impl Clone for GeLineq {
29    fn clone(&self) -> Self {
30        return GeLineq {
31            id: self.id,
32            coeffs: self.coeffs.to_vec(),
33            bounds: self.bounds.to_vec(),
34            bias: self.bias,
35            indices: self.indices.to_vec(),
36        }
37    }
38}
39
40impl GeLineq {
41    
42    fn _eqmax(&self) -> i64 {
43        let mut res : i64 = 0;
44        for i in 0..self.coeffs.len() {
45            if self.coeffs[i] < 0 {
46                res = res + self.coeffs[i] * self.bounds[i].0;
47            } else {
48                res = res + self.coeffs[i] * self.bounds[i].1;
49            }
50        }
51        return res;
52    }
53    
54    fn _eqmin(&self) -> i64 {
55        let mut res : i64 = 0;
56        for i in 0..self.coeffs.len() {
57            if self.coeffs[i] > 0 {
58                res = res + self.coeffs[i] * self.bounds[i].0;
59            } else {
60                res = res + self.coeffs[i] * self.bounds[i].1;
61            }
62        }
63        return res;
64    }
65    
66    /// Takes two GeLineqs and merge those into one GeLineq under the condition that one of the GeLineqs must be satisfied.
67    /// If it's not possible to merge the inequalities, i.e. it's impossible to preserve the logic, `none` is returned.
68    /// 
69    /// # Example:
70    /// 
71    /// If atleast one of the two linear inequalities $ x + y - 1 \ge 0 $ where $ x, y \in \\{0, 1 \\}$ and $ a + b - 1 \ge 0 $ where $ a, b \in \\{0, 1\\}$ must hold,
72    /// then they can be merged into one linear inequality as $ x + y + a + b - 1 \ge 0$ where $ x, y, a, b \in \\{0, 1\\}$. Note that the logic is preserved,
73    /// i.e the merged inequality will be valid if at least one of the two original inequalities are valid. Likewise, the merged constraint will not be valid if none of the original inequalites are.
74    /// ```
75    /// use puanrs::polyopt::GeLineq;
76    /// let ge_lineq1:GeLineq = GeLineq {
77    ///    id      : None,
78    ///    coeffs  : vec![1, 1],
79    ///    bounds  : vec![(0, 1), (0, 1)],
80    ///    bias    : -1,
81    ///    indices : vec![1, 2]
82    ///    };
83    /// let ge_lineq2: GeLineq = GeLineq {
84    ///    id      : None,
85    ///    coeffs  : vec![1, 1],
86    ///    bounds  : vec![(0, 1), (0, 1)],
87    ///    bias    : -1,
88    ///    indices : vec![3, 4]
89    ///  };
90    /// let expected: GeLineq = GeLineq {
91    ///    id      : None,
92    ///    coeffs  : vec![1, 1, 1, 1],
93    ///    bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
94    ///    bias    : -1,
95    ///    indices : vec![1, 2, 3, 4]
96    ///  };
97    /// let actual = GeLineq::merge_disj(&ge_lineq1, &ge_lineq2);
98    /// assert_eq!(actual.as_ref().expect("Not possible to merge lineqs").coeffs, expected.coeffs);
99    /// assert_eq!(actual.as_ref().expect("Not possible to merge lineqs").bounds, expected.bounds);
100    /// assert_eq!(actual.as_ref().expect("Not possible to merge lineqs").bias, expected.bias);
101    /// assert_eq!(actual.as_ref().expect("Not possible to merge lineqs").indices, expected.indices);
102    /// ```
103    pub fn merge_disj(ge_lineq1: &GeLineq, ge_lineq2: &GeLineq) -> Option<GeLineq> {
104        if ge_lineq1._eqmin() + ge_lineq1.bias == -1 {
105            let mut equal_indices : Vec<(usize, usize)> = Vec::new();
106            for i in 0..ge_lineq1.indices.len(){
107                for j in 0..ge_lineq2.indices.len(){
108                    if ge_lineq1.indices[i]==ge_lineq2.indices[j] {
109                        equal_indices.push((i, j));
110                    }
111                }
112            }
113            let n: usize = ge_lineq1.coeffs.len() + ge_lineq2.coeffs.len() - equal_indices.len();
114            let mut new_coeffs : Vec<i64> = Vec::with_capacity(n);
115            let mut equal_index_pointer: usize = 0;
116            let mut corrector: i64 = 0;
117            let mut new_bounds : Vec<(i64, i64)> = Vec::with_capacity(n);
118            let mut new_indices : Vec<u32> = Vec::with_capacity(n);
119            
120            for i in 0..ge_lineq1.coeffs.len() {
121                if equal_index_pointer < equal_indices.len() && equal_indices[equal_index_pointer].0 == i {
122                    corrector = ge_lineq2.coeffs[equal_indices[equal_index_pointer].1];
123                    equal_index_pointer = equal_index_pointer + 1;
124                }
125                new_coeffs.push(-ge_lineq1.coeffs[i]*(ge_lineq2._eqmin() + ge_lineq2.bias) + corrector);
126                new_indices.push(ge_lineq1.indices[i]);
127                new_bounds.push(ge_lineq1.bounds[i]);
128                corrector = 0;
129            }
130            let mut skip_equal_index = false;
131            for i in 0..ge_lineq2.coeffs.len(){
132                for j in 0..equal_indices.len(){
133                    if equal_indices[j].1 == i {
134                        equal_indices.remove(j);
135                        skip_equal_index = true;
136                        break;
137                    }
138                }
139                if !skip_equal_index {
140                    new_coeffs.push(ge_lineq2.coeffs[i]);
141                    new_indices.push(ge_lineq2.indices[i]);
142                    new_bounds.push(ge_lineq2.bounds[i]);
143                }
144                skip_equal_index = false;
145            }
146            return Some(
147                GeLineq {
148                    id: None,
149                    coeffs: new_coeffs,
150                    bounds: new_bounds,
151                    bias: ge_lineq1._eqmin()*(ge_lineq2._eqmin() + ge_lineq2.bias)+ge_lineq2.bias,
152                    indices: new_indices
153                }
154            );  
155        } else if ge_lineq2._eqmin() + ge_lineq2.bias == -1 {
156            return GeLineq::merge_disj(ge_lineq2, ge_lineq1);
157        }
158    
159        None
160        
161    }
162
163    /// Takes two GeLineqs and merge those into one GeLineq under the condition that both of the GeLineqs must be valid.
164    /// If it's not possible to merge the inequalities, i.e. it's impossible to preserve the logic, `none` is returned.
165    pub fn merge_conj(ge_lineq1: &GeLineq, ge_lineq2: &GeLineq) -> Option<GeLineq> {
166    
167        if ge_lineq1._eqmax() + ge_lineq1.bias == 0 {
168            let mut equal_indices : Vec<(usize, usize)> = Vec::new();
169            for i in 0..ge_lineq1.indices.len(){
170                for j in 0..ge_lineq2.indices.len(){
171                    if ge_lineq1.indices[i]==ge_lineq2.indices[j] {
172                        equal_indices.push((i, j));
173                    }
174                }
175            }
176            let n: usize = ge_lineq1.coeffs.len() + ge_lineq2.coeffs.len() - equal_indices.len();
177            let mut new_coeffs : Vec<i64> = Vec::with_capacity(n);
178            let mut equal_index_pointer: usize = 0;
179            let mut corrector: i64 = 0;
180            let mut new_bounds : Vec<(i64, i64)> = Vec::with_capacity(n);
181            let mut new_indices : Vec<u32> = Vec::with_capacity(n);
182            
183            for i in 0..ge_lineq1.coeffs.len() {
184                if equal_index_pointer < equal_indices.len() && equal_indices[equal_index_pointer].0 == i {
185                    corrector = ge_lineq2.coeffs[equal_indices[equal_index_pointer].1];
186                    equal_index_pointer = equal_index_pointer + 1;
187                }
188                new_coeffs.push(ge_lineq1.coeffs[i]*(cmp::max(ge_lineq2._eqmax().abs(), ge_lineq2._eqmin().abs())+1) + corrector);
189                new_indices.push(ge_lineq1.indices[i]);
190                new_bounds.push(ge_lineq1.bounds[i]);
191                corrector = 0;
192            }
193            let mut skip_equal_index = false;
194            for i in 0..ge_lineq2.coeffs.len(){
195                for j in 0..equal_indices.len(){
196                    if equal_indices[j].1 == i {
197                        equal_indices.remove(j);
198                        skip_equal_index = true;
199                        break;
200                    }
201                }
202                if !skip_equal_index {
203                    new_coeffs.push(ge_lineq2.coeffs[i]);
204                    new_indices.push(ge_lineq2.indices[i]);
205                    new_bounds.push(ge_lineq2.bounds[i]);
206                }
207                skip_equal_index = false;
208            }
209            return Some(
210                GeLineq {
211                    id: None,
212                    coeffs: new_coeffs,
213                    bounds: new_bounds,
214                    bias: ge_lineq1.bias*(cmp::max(ge_lineq2._eqmax().abs(), ge_lineq2._eqmin().abs())+1) + ge_lineq2.bias - cmp::max(ge_lineq2._eqmin() + ge_lineq2.bias, 0),
215                    indices: new_indices
216                }
217            );  
218        } else if ge_lineq2._eqmax() + ge_lineq2.bias == 0 {
219            return GeLineq::merge_conj(&ge_lineq2, &ge_lineq1);
220        }
221    
222        None
223        
224    }
225    
226    /// Takes a GeLineq, referred to as main GeLineq which has a variable (given by variable index) which in turn is a GeLineq, referred to as the sub GeLineq.
227    /// The function substitutes the variable with the sub GeLineq into the main GeLineq if possible. 
228    /// 
229    /// # Example
230    /// Given the lineq x + Y - 2 >= 0 where Y: a + b - 1 >= 0
231    /// a substitution would give 2x + a + b - 3 >= 0. Lets see it in code:
232    /// ```
233    /// use puanrs::polyopt::GeLineq;
234    /// let main_gelineq:GeLineq = GeLineq {
235    ///    id      : None,
236    ///    coeffs  : vec![1, 1],
237    ///    bounds  : vec![(0, 1), (0, 1)],
238    ///    bias    : -2,
239    ///    indices : vec![1, 2]
240    /// };
241    /// let sub_gelineq: GeLineq = GeLineq {
242    ///    id      : None,
243    ///    coeffs  : vec![1, 1],
244    ///    bounds  : vec![(0, 1), (0, 1)],
245    ///    bias    : -1,
246    ///    indices : vec![3, 4]
247    /// };
248    /// let result = GeLineq::substitution(&main_gelineq, 2, &sub_gelineq);
249    /// assert_eq!(vec![2, 1, 1], result.as_ref().expect("No result generated").coeffs);
250    /// assert_eq!(vec![(0,1), (0,1), (0,1)], result.as_ref().expect("No result generated").bounds);
251    /// assert_eq!(-3, result.as_ref().expect("No result generated").bias);
252    /// assert_eq!(vec![1, 3, 4], result.as_ref().expect("No result generated").indices);
253    /// ```
254
255    pub fn substitution(main_gelineq: &GeLineq, variable_index: u32, sub_gelineq: &GeLineq) -> Option<GeLineq> {
256        let var_to_substitute = main_gelineq.indices.iter().position(|&x| x == variable_index).expect(&format!("Variable '{variable_index}' to substitute not found"));
257        if main_gelineq.coeffs[var_to_substitute] < 0 {
258            let new_sub_coeffs: Vec<i64> = sub_gelineq.coeffs.iter().map(|x| -1*x).collect();
259            let new_sub_bias = -sub_gelineq.bias - 1;
260            let new_sub_gelineq = GeLineq {
261                id: None,
262                coeffs: new_sub_coeffs,
263                bounds: sub_gelineq.bounds.to_vec(),
264                bias: new_sub_bias,
265                indices: sub_gelineq.indices.to_vec()
266            };
267            return GeLineq::_substitution(main_gelineq, var_to_substitute, &new_sub_gelineq);
268        }
269        return GeLineq::_substitution(main_gelineq, var_to_substitute, sub_gelineq);
270    }
271    
272    fn _substitution(main_gelineq: &GeLineq, variable_index: usize, sub_gelineq: &GeLineq) -> Option<GeLineq> {
273        if !GeLineq::is_homogenous(main_gelineq) && sub_gelineq.coeffs.len() > 1 {
274            // Not possible to perform substitution
275            return None;
276        }
277        if sub_gelineq.bias < 0 {
278            if sub_gelineq._eqmax() > 0 && (main_gelineq.coeffs[variable_index]*(2*sub_gelineq._eqmax() + sub_gelineq.bias)/sub_gelineq._eqmax()) >= 2 {
279                // Not possible to perform substitution
280                return None;
281            }
282        } else {
283            if (main_gelineq.coeffs[variable_index]*(sub_gelineq._eqmax() + sub_gelineq._eqmin().abs() + sub_gelineq.bias) / sub_gelineq._eqmin().abs()) >= 2 {
284                // Not possible to perform substitution
285                return None;
286            }
287        }
288        let mut equal_indices : Vec<(usize, usize)> = Vec::new();
289        for i in 0..main_gelineq.indices.len(){
290            for j in 0..sub_gelineq.indices.len(){
291                if main_gelineq.indices[i]==sub_gelineq.indices[j] {
292                    equal_indices.push((i, j));
293                }
294            }
295        }
296        let n: usize = main_gelineq.coeffs.len() + sub_gelineq.coeffs.len() - equal_indices.len() - 1;
297        let mut new_coeffs : Vec<i64> = Vec::with_capacity(n);
298        let mut equal_index_pointer: usize = 0;
299        let mut corrector: i64 = 0;
300        let mut new_bounds : Vec<(i64, i64)> = Vec::with_capacity(n);
301        let mut new_indices : Vec<u32> = Vec::with_capacity(n);
302        
303        for i in 0..main_gelineq.coeffs.len() {
304            if i == variable_index {
305                continue;
306            }
307            if equal_index_pointer < equal_indices.len() && equal_indices[equal_index_pointer].0 == i {
308                corrector = sub_gelineq.coeffs[equal_indices[equal_index_pointer].1];
309                equal_index_pointer = equal_index_pointer + 1;
310            }
311            if sub_gelineq.bias < 0 {
312                new_coeffs.push(main_gelineq.coeffs[i]*sub_gelineq._eqmax() + corrector);
313            } else {
314                new_coeffs.push(main_gelineq.coeffs[i]*sub_gelineq._eqmin().abs() + corrector);
315            }
316            new_indices.push(main_gelineq.indices[i]);
317            new_bounds.push(main_gelineq.bounds[i]);
318            corrector = 0;
319        }
320        let mut skip_equal_index = 0;
321        for i in 0..sub_gelineq.coeffs.len(){
322            for j in 0..equal_indices.len(){
323                if equal_indices[j].1 == i {
324                    equal_indices.remove(j);
325                    skip_equal_index = 1;
326                    break;
327                }
328            }
329            if skip_equal_index < 1 {
330                new_coeffs.push(sub_gelineq.coeffs[i]);
331                new_indices.push(sub_gelineq.indices[i]);
332                new_bounds.push(sub_gelineq.bounds[i]);
333            }
334            skip_equal_index = 0;
335        }
336        let adjuster = if main_gelineq.bias != 0 {1} else {0};
337        let new_bias = if sub_gelineq.bias < 0 {(main_gelineq.bias + adjuster)*sub_gelineq._eqmax() + sub_gelineq.bias} else {(main_gelineq.bias+adjuster)*sub_gelineq._eqmin().abs() + sub_gelineq.bias};
338        return Some(
339            GeLineq {
340                id: main_gelineq.id,
341                coeffs: new_coeffs,
342                bounds: new_bounds,
343                bias: new_bias,
344                indices: new_indices
345            }
346        );  
347    }
348
349    fn is_homogenous(ge_lineq: &GeLineq) -> bool {
350        let first = ge_lineq.coeffs[0];
351        ge_lineq.coeffs.iter().all(|x| *x==first)
352    }
353    
354    fn is_mixed(gelineq: &GeLineq) -> bool {
355        let mut is_mixed = false;
356        let mut positive = false;
357        let mut negative = false;
358        let mut i: usize = 0;
359        while !is_mixed && i < gelineq.coeffs.len() {
360            if gelineq.coeffs[i]*gelineq.bounds[i].0 > 0 || gelineq.coeffs[i]*gelineq.bounds[i].1 > 0 {
361                positive = true;
362            }
363            if gelineq.coeffs[i]*gelineq.bounds[i].0 < 0 || gelineq.coeffs[i]*gelineq.bounds[i].1 < 0 {
364                negative = true
365            }
366            if positive && negative {
367                is_mixed = true;
368            }
369            i = i + 1;
370        }
371        return is_mixed;
372    }
373    
374    /// Takes a GeLineq and minimizes/maximizes the coefficients while preserving the logic
375    /// 
376    /// # Example
377    /// 
378    /// Consider the GeLineq 2x + y + z >= 1 where x, y, z takes values between 0 and 1.
379    /// This inequlity can be written as x + y + z >= 1 while preserving the logic. 
380    /// ```
381    /// use puanrs::polyopt::GeLineq;
382    /// let gelineq: GeLineq = GeLineq {
383    ///     id      : None,
384    ///     coeffs  : vec![2, 1, 1],
385    ///     bounds  : vec![(0,1), (0,1), (0,1)],
386    ///     bias    : -1,
387    ///     indices : vec![0, 1, 2]
388    ///   };
389    /// let result = GeLineq::min_max_coefficients(&gelineq);
390    /// assert_eq!(vec![1, 1, 1], result.as_ref().expect("").coeffs);
391    pub fn min_max_coefficients(gelineq: &GeLineq) -> Option<GeLineq> {
392        if GeLineq::is_mixed(gelineq){
393            return None;
394        }
395        let mut new_coeffs: Vec<i64> = Vec::with_capacity(gelineq.coeffs.len());
396        for i in 0..gelineq.coeffs.len(){
397            if gelineq.coeffs[i]*gelineq.bounds[i].0 > 0 || gelineq.coeffs[i]*gelineq.bounds[i].1 > 0 {
398                new_coeffs.push(cmp::min(gelineq.coeffs[i], cmp::max(-gelineq.bias, 0)));
399            } else if gelineq.coeffs[i]*gelineq.bounds[i].0 < 0 || gelineq.coeffs[i]*gelineq.bounds[i].1 < 0 {
400                new_coeffs.push(cmp::max(gelineq.coeffs[i], cmp::min(-gelineq.bias, 0)-1));
401            } else {
402                new_coeffs.push(0);
403            }
404        }
405        return Some(
406                GeLineq {
407                    id: gelineq.id,
408                    coeffs: new_coeffs,
409                    bounds: gelineq.bounds.to_vec(),
410                    bias: gelineq.bias, 
411                    indices: gelineq.indices.to_vec() 
412                }
413            )
414        }
415        
416        
417    }
418
419
420#[derive(Debug)]
421pub struct VariableFloat {
422    pub id      : u32,
423    pub bounds  : (f64, f64)
424}
425
426impl Clone for VariableFloat {
427    fn clone(&self) -> Self {
428        return VariableFloat { 
429            id: self.id, 
430            bounds: self.bounds 
431        }
432    }
433}
434
435impl PartialEq for VariableFloat {
436    fn eq(&self, other: &Self) -> bool {
437        return self.id == other.id;
438    }
439}
440
441
442/// Variable data structure has two properties, "id" and "bounds". An instance of Variable
443/// is used to reference to Statement or an input into a Theory. 
444#[derive(Copy, Eq, Debug)]
445pub struct Variable {
446    pub id      : u32,
447    pub bounds  : (i64, i64)
448}
449
450impl Clone for Variable {
451    fn clone(&self) -> Self {
452        return Variable {
453            id : self.id,
454            bounds: self.bounds
455        }
456    }
457}
458
459impl PartialEq for Variable {
460    fn eq(&self, other: &Self) -> bool {
461        return self.id == other.id;
462    }
463}
464
465impl Variable {
466
467    /// A negated linear inequality representation of a Variable.
468    /// 
469    /// # Example:
470    /// 
471    /// ```
472    /// use puanrs::polyopt::Variable;
473    /// use puanrs::polyopt::GeLineq;
474    /// let variable: Variable = Variable {
475    ///     id      : 0,
476    ///     bounds  : (0,1)
477    /// };
478    /// let actual: GeLineq = variable.to_lineq_neg();
479    /// assert_eq!(actual.bias, 0);
480    /// assert_eq!(actual.bounds, vec![(0,1)]);
481    /// assert_eq!(actual.coeffs, vec![-1]);
482    /// assert_eq!(actual.indices, vec![0]);
483    /// ```
484    pub fn to_lineq_neg(&self) -> GeLineq {
485        return GeLineq {
486            id: Some(self.id),
487            coeffs: vec![-1],
488            bias: 0,
489            bounds: vec![(0,1)],
490            indices: vec![self.id]
491        }
492    }
493
494    pub fn to_variable_float(&self) -> VariableFloat {
495        return VariableFloat { id: self.id, bounds: (self.bounds.0 as f64, self.bounds.1 as f64) }
496    }
497}
498
499/// Data structure for polyhedron with `a` as `Matrix` and `b` as `Vec` and `bounds` as a `Vec` of `Tuples`.
500/// Note that the number of rows of `a` must be the same as the length of `b` and the number of columns of `a`
501/// must be the same as the length of `bounds`.
502#[derive(Default, Debug)]
503pub struct Polyhedron {
504    /// The left-hand side of linear constraints on the form $ a + b + c \ge x $.
505    pub a: Matrix,
506    /// The right-hand side of linear constraints as described above.
507    pub b: Vec<f64>,
508    /// Upper and lower bounds (`lower_bound`, `upper_bound`) of the variables given by `a`.
509    pub variables: Vec<VariableFloat>,
510    // Index of rows in `a`.
511    pub index: Vec<Option<u32>>
512}
513impl Clone for Polyhedron {
514    fn clone(&self) -> Self {
515        return Polyhedron { a: self.a.clone(), b: self.b.clone(), variables: self.variables.clone(), index: self.index.clone() }
516    }
517}
518impl PartialEq for Polyhedron {
519    fn eq(&self, other: &Self) -> bool {
520        return (self.a == other.a) & (self.b == other.b) & (self.variables == other.variables) & (self.index == other.index);
521    }
522}
523impl Polyhedron {
524    /// Returns a Vec of all variable bounds generated from variables vector.
525    pub fn bounds(&self) -> Vec<(f64, f64)> {
526        return self.variables.iter().map(|x| x.bounds).collect();
527    }
528
529    /// Returns a Vec of all variable ID's generated from variables vector.
530    pub fn ids(&self) -> Vec<u32> {
531        return self.variables.iter().map(|x| x.id).collect();
532    }
533}
534
535pub struct CsrPolyhedron {
536    /// The left-hand side of linear constraints on the form $ a + b + c \ge x $ as a compressed sparse matrix.
537    pub a: CsrMatrix,
538    /// The right-hand side of linear constraints as described above.
539    pub b: Vec<f64>,
540    /// Upper and lower bounds (`lower_bound`, `upper_bound`) of the variables given by `a`.
541    pub variables: Vec<VariableFloat>,
542    // Index of rows in `a`.
543    pub index: Vec<Option<u32>>
544} 
545
546impl Clone for CsrPolyhedron {
547    fn clone(&self) -> Self {
548        return CsrPolyhedron { a: self.a.clone(), b: self.b.clone(), variables: self.variables.clone(), index: self.index.clone() }
549    }
550}
551impl PartialEq for CsrPolyhedron {
552    fn eq(&self, other: &Self) -> bool {
553        return (self.a == other.a) & (self.b == other.b) & (self.variables == other.variables) & (self.index == other.index);
554    }
555}
556impl CsrPolyhedron {
557    /// Returns a Vec of all variable bounds generated from variables vector.
558    pub fn bounds(&self) -> Vec<(f64, f64)> {
559        return self.variables.iter().map(|x| x.bounds).collect();
560    }
561
562    /// Returns a Vec of all variable ID's generated from variables vector.
563    pub fn ids(&self) -> Vec<u32> {
564        return self.variables.iter().map(|x| x.id).collect();
565    }
566
567    pub fn to_dense_polyhedron(&self) -> Polyhedron {
568        return Polyhedron {
569            a: self.a.to_matrix(),
570            b: self.b.clone(),
571            index: self.index.clone(),
572            variables: self.variables.clone()
573        }
574    }
575}